どうやら魅力のタイトルづけは完全無敵のようですね

## どうやら魅力のタイトルづけは完全無敵のようですね
### 概要
なろう小説のタイトルをパロった技術ブログのタイトル自動生成プログラムを作成しました。

[2020/03/07追記]
Webアプリにしました。
[なろうパロディ☆タイトルジェネレータ](https://aqueous-wildwood-63896.herokuapp.com/)

Webアプリ化の記事はこちら
[思いつきで作ったプログラムをSinatraに組み込みHerokuにデプロイする](https://qiita.com/ObaTakeshi/items/0bce3c2ded3db4bb40d3)

プログラムの実行結果
キーワード: 魅力, タイトルづけ

```text:出力
"どうやら魅力のタイトルづけは完全無敵のようですね"
"どうやら魅力の身体はタイトルづけ無敵のようですね"
"どうやら魅力の身体は完全タイトルづけのようですね"
"どうやら魅力の身体は完全無敵のタイトルづけですね"
"どうやらタイトルづけの魅力は完全無敵のようですね"
"どうやら私の魅力はタイトルづけ無敵のようですね"
"どうやら私の魅力は完全タイトルづけのようですね"
"どうやら私の魅力は完全無敵のタイトルづけですね"
"どうやらタイトルづけの身体は魅力無敵のようですね"
"どうやら私のタイトルづけは魅力無敵のようですね"
"どうやら私の身体は魅力タイトルづけのようですね"
"どうやら私の身体は魅力無敵のタイトルづけですね"
"どうやらタイトルづけの身体は完全魅力のようですね"
"どうやら私のタイトルづけは完全魅力のようですね"
"どうやら私の身体はタイトルづけ魅力のようですね"
"どうやら私の身体は完全魅力のタイトルづけですね"
"どうやらタイトルづけの身体は完全無敵の魅力ですね"
"どうやら私のタイトルづけは完全無敵の魅力ですね"
"どうやら私の身体はタイトルづけ無敵の魅力ですね"
"どうやら私の身体は完全タイトルづけの魅力ですね"
```

### はじめに
最近の技術系記事は「中身の新鮮さ・分かりやすさ」の他に、以下のような「目を引くタイトル」というのが重要視されているような気がします。
[あまり強い言葉を使うなよ…弱く見えるぞ　〜AIによって”やさしく”言い換えできてるかチェックしよう〜](https://qiita.com/mosamosa/items/d7f3a8afc2982cd99385)
[…Outlookの送信メールが……消えた…？(12/24改修されたよ)](https://qiita.com/tak_ham/items/6243ebc0db467329a8bc)

そのような目を引くタイトルの中でも、以下のライトノベルのようなタイトルをもじったタイトルが多数存在します。
[この TypeScript が Hello, world! のくせに慎重すぎる](https://qiita.com/matzkoh/items/90baab22ad489b78384b)
[このPHPがテンプレートエンジンのくせに慎重すぎる (前篇)](https://qiita.com/tadsan/items/bf61520eb2d455e0e8b4)

しかし、このような秀逸なタイトルはつけることが難しいし、タイトルに時間をかけるのはたるいみたいな人もいると思います。

そこで、今回は技術記事のパロディタイトルを自動生成するプログラムを作成しました。

    1. 「小説家になろう」からタイトルを収集する
    2. タイトルを形態素解析する
    3. タイトルの候補を複数出力する

### プログラム
#### 「小説家になろう」からタイトルを収集する
「小説家になろう」よりタイトルを収集するため、以下のドキュメントを頼りにAPIを叩きます。
[https://dev.syosetu.com/man/api/](https://dev.syosetu.com/man/api/)
有名なものをとりたい。タイトルだけ欲しい。なるべく多くとりたい。出力はjsonで欲しい。という条件からパラメータには

```
order = favnovelcnt
of = t
lim = 500
out = json
```
を指定します。
実行のたびにAPIを叩くのは迷惑なので、収集したタイトルを保存します。
一連のコードは以下です。

```ruby
require "uri"
require 'net/http'
require 'openssl'
require 'json'

def narou_get(title_path)
    uri = "https://api.syosetu.com/novelapi/api/?order=favnovelcnt&of=t&lim=500&out=json"

    uri = URI.parse(uri)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = true
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE

    req = Net::HTTP::Get.new(uri.request_uri)
    res = http.request(req)
    res_body = res.body
    
    hash = JSON.parse(res_body)
    titles = []
    for title in hash do
        titles.push(title["title"])
    end

    open(title_path, 'w') do |f|
        titles.each { |s| f.puts(s) }
    end
    return titles
end
```

#### タイトルを形態素解析する
収集したタイトルをどうパロディにしているか。
という問題に対して、今回は「タイトルの名詞の部分を指定したキーワードに置換する」という方法をとります。
MeCabを用いてタイトルを形態素解析し、タイトルの分かち書きと名詞の添字を返す関数を作成します。
少しややこしい処理になっているのは、形態素解析の結果が一つのStringで返ってくるためです。

```ruby
require 'mecab'

def tagger_title(title)
    tagger = MeCab::Tagger.new
    t_tagger = tagger.parse(title).split()
    noun_idx = []
    original = []
    for i in (0..t_tagger.length-2).step(2) do
        if t_tagger[i+1].start_with?('名詞')
            noun_idx.push(i/2)
        end
        original.push(t_tagger[i])
    end
    return original, noun_idx
end
```

#### タイトルの候補を複数出力する
得られたタイトルをパロディタイトルに書き換えます。
タイトル中の名詞の数に対し、記事のキーワードを一意に決めるのではなく、以下の順列に従い候補を複数出力します。

```math
{}_{タイトル中の名詞数} \mathrm{P}_{記事のキーワード数}
```

キーワードの方が多い時は、名詞数になるまで打ち切ります。
タイトルは乱数で適当に決めます。


```ruby
keyword = ['魅力', 'タイトルづけ']
if File.exist?(title_path)
    titles = File.open(title_path, 'r').readlines
else
    titles = narou_get(title_path)
end

idx = rand(1..titles.length)
split_title, noun_idx = tagger_title(titles[idx])

if keyword.length > noun_idx.length
    keyword = keyword.slice(0..noun_idx.length)
end

candidate = noun_idx.permutation(keyword.length)

for c in candidate do
    original = Marshal.load(Marshal.dump(split_title))
    c.each_with_index do |i, j|
        original[i] = keyword[j]
    end
    p original.join
end
```

### 結果
複数のキーワードに対して、うまいタイトルが生成できるか試します。
キーワード: Python, Deep learning

```text:出力
"PythonのDeep learning　異端召喚者はシナリオブレイカー"
"Pythonのカンナ　Deep learning召喚者はシナリオブレイカー"
"Pythonのカンナ　異端Deep learning者はシナリオブレイカー"
"Pythonのカンナ　異端召喚Deep learningはシナリオブレイカー"
"Pythonのカンナ　異端召喚者はDeep learning"
"Deep learningのPython　異端召喚者はシナリオブレイカー"
"カンナのPython　Deep learning召喚者はシナリオブレイカー"
"カンナのPython　異端Deep learning者はシナリオブレイカー"
"カンナのPython　異端召喚Deep learningはシナリオブレイカー"
"カンナのPython　異端召喚者はDeep learning"
"Deep learningのカンナ　Python召喚者はシナリオブレイカー"
"カンナのDeep learning　Python召喚者はシナリオブレイカー"
"カンナのカンナ　PythonDeep learning者はシナリオブレイカー"
"カンナのカンナ　Python召喚Deep learningはシナリオブレイカー"
"カンナのカンナ　Python召喚者はDeep learning"
"Deep learningのカンナ　異端Python者はシナリオブレイカー"
"カンナのDeep learning　異端Python者はシナリオブレイカー"
"カンナのカンナ　Deep learningPython者はシナリオブレイカー"
"カンナのカンナ　異端PythonDeep learningはシナリオブレイカー"
"カンナのカンナ　異端Python者はDeep learning"
"Deep learningのカンナ　異端召喚Pythonはシナリオブレイカー"
"カンナのDeep learning　異端召喚Pythonはシナリオブレイカー"
"カンナのカンナ　Deep learning召喚Pythonはシナリオブレイカー"
"カンナのカンナ　異端Deep learningPythonはシナリオブレイカー"
"カンナのカンナ　異端召喚PythonはDeep learning"
"Deep learningのカンナ　異端召喚者はPython"
"カンナのDeep learning　異端召喚者はPython"
"カンナのカンナ　Deep learning召喚者はPython"
"カンナのカンナ　異端Deep learning者はPython"
"カンナのカンナ　異端召喚Deep learningはPython"
```

キーワード: JavaScript, Github, Heroku, Bootstrap, Laravel

```text:出力
"自称！平凡魔Laravelの英雄ライフ～BGithub魔HerokuなのにBootstrapを作ってしまった結果JavaScript"
"自称！平凡魔族のLaravelライフ～BGithub魔HerokuなのにBootstrapを作ってしまった結果JavaScript"
"自称！平凡魔族の英雄Laravel～BGithub魔HerokuなのにBootstrapを作ってしまった結果JavaScript"
"自称！平凡魔族の英雄ライフLaravelBGithub魔HerokuなのにBootstrapを作ってしまった結果JavaScript"
"自称！平凡魔族の英雄ライフ～LaravelGithub魔HerokuなのにBootstrapを作ってしまった結果JavaScript"
"自称！平凡魔族の英雄ライフ～BGithubLaravelHerokuなのにBootstrapを作ってしまった結果JavaScript"
"自称！平凡魔族の英雄ライフ～BGithub魔HerokuなのにBootstrapを作ってしまったLaravelJavaScript"
"Laravel！平凡魔族の英雄ライフ～BGithub魔Herokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！Laravel魔族の英雄ライフ～BGithub魔Herokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！平凡Laravel族の英雄ライフ～BGithub魔Herokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！平凡魔Laravelの英雄ライフ～BGithub魔Herokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！平凡魔族のLaravelライフ～BGithub魔Herokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！平凡魔族の英雄Laravel～BGithub魔Herokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！平凡魔族の英雄ライフLaravelBGithub魔Herokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！平凡魔族の英雄ライフ～LaravelGithub魔Herokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！平凡魔族の英雄ライフ～BGithubLaravelHerokuなのにチートダンジョンを作ってしまったBootstrapJavaScript"
"自称！平凡魔族の英雄ライフ～BGithub魔HerokuなのにLaravelを作ってしまったBootstrapJavaScript"
"Bootstrap！Laravel魔族の英雄ライフ～BGithub魔族なのにHerokuを作ってしまった結果JavaScript"
"Bootstrap！平凡Laravel族の英雄ライフ～BGithub魔族なのにHerokuを作ってしまった結果JavaScript"
"Bootstrap！平凡魔Laravelの英雄ライフ～BGithub魔族なのにHerokuを作ってしまった結果JavaScript"
"Bootstrap！平凡魔族のLaravelライフ～BGithub魔族なのにHerokuを作ってしまった結果JavaScript"
"Bootstrap！平凡魔族の英雄Laravel～BGithub魔族なのにHerokuを作ってしまった結果JavaScript"
"Bootstrap！平凡魔族の英雄ライフLaravelBGithub魔族なのにHerokuを作ってしまった結果JavaScript"
"Bootstrap！平凡魔族の英雄ライフ～LaravelGithub魔族なのにHerokuを作ってしまった結果JavaScript"
"Bootstrap！平凡魔族の英雄ライフ～BGithubLaravel族なのにHerokuを作ってしまった結果JavaScript"
"Bootstrap！平凡魔族の英雄ライフ～BGithub魔LaravelなのにHerokuを作ってしまった結果JavaScript"
```
数が多いので抜粋しました。
しかし、キーワードの数が多くても個人的には面白いものが生成できているので、有効そうです。

### あとがき
思ったよりうまい生成ができているので、これからたくさん技術記事を書こうと思っています。