ネット上の記事を拾ってきてリライトする仕事をしていたことがありますが、地味に面倒くさいです。そこで既存の文章をMecabで形態素解析して単語に分解します。これを単にランダムに並べ替えるだけではメチャクチャな文章にしかならないので、少し工夫します。
それぞれの単語の後ろにどのような単語がくるのかを調べます。特定の単語の後ろには別の特定の単語が来ることが多いことがわかるので、あとはその単語をつなぎ合わせて文章をつくります。
最初に2つのクラスを定義します。
WordInfoクラスは単語と品詞名、その次に続く単語とその回数を格納しているクラス(NextWord)のリストを保持します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class WordInfo { public string Word = ""; public string PartOfSpeech = ""; public List<NextWord> NextWords = new List<NextWord>(); public void Add(string newWord) { if (NextWords.Any(_ => _.Word == newWord)) NextWords.First(_ => _.Word == newWord).Count++; else { NextWord nextWord = new NextWord(); nextWord.Word = newWord; NextWords.Add(nextWord); } } } public class NextWord { public string Word = ""; public int Count = 1; } |
デザイナで以下のようなものをつくります。
文章を入力してボタンをクリックしたら、Mecabで単語に分解して単語と品詞名のリストを作成します。そしてこれを自作メソッド SaveWordsに渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Windows.Forms; using System.Xml.Serialization; using NMeCab; public partial class Form1 : Form { private void button1_Click(object sender, EventArgs e) { string path = "C:\\Program Files (x86)\\MeCab\\dic\\ipadic"; MeCabTagger _tagger = MeCabTagger.Create(new MeCabParam { DicDir = path, }); string resultText = _tagger.Parse(richTextBox1.Text); string[] results = resultText.Split('\n'); List<string> words = new List<string>(); List<string> partOfSpeechs = new List<string>(); foreach (string str in results) { string[] strings = str.Split('\t'); if (strings.Length < 2) continue; words.Add(str.Split()[0]); // 単語 partOfSpeechs.Add(strings[1].Split(',')[0]); // 品詞名 } SaveWords(words, partOfSpeechs); MessageBox.Show("完了"); } } |
もしwordInfosのなかに単語が格納されていないのであればWordInfoオブジェクトを生成してこれを追加します。WordInfoオブジェクトには単語とそれに続く単語を格納します。最初に出現した単語であれば、それに続く単語もひとつしかなく、使用回数も1回だけです。すでに出現した単語であれば、それに続く単語の情報もすでに格納されているかもしれません。その場合は使用回数を1増やします。格納されていない場合は新しく追加します。
wordInfosにすべての情報を格納したら、これをXMLファイルとして保存します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public partial class Form1 : Form { void SaveWords(List<string> words, List<string> partOfSpeechs) { List<WordInfo> wordInfos = new List<WordInfo>(); for (int i = 0; i < words.Count; i++) { string word = words[i]; if (!wordInfos.Any(_ => _.Word == word)) { WordInfo wordInfo = new WordInfo(); wordInfo.Word = word; wordInfo.PartOfSpeech = partOfSpeechs[i]; if (i + 1 < words.Count) { string nextWord = words[i + 1]; wordInfo.Add(nextWord); wordInfos.Add(wordInfo); } } else { WordInfo wordInfo = wordInfos.First(_ => _.Word == word); if (i + 1 < words.Count) { string nextWord = words[i + 1]; wordInfo.Add(nextWord); } } } XmlSerializer serializer = new XmlSerializer(typeof(List<WordInfo>)); StreamWriter sw = new StreamWriter("./text.txt"); serializer.Serialize(sw, wordInfos); sw.Close(); } } |
文章生成のボタンがクリックされたら保存されているファイルからデータを読み取り、文章を生成します。最初はWordInfoのリストのなかから適当に選びますが、それが名詞でない場合は選び直します(あまりに文章が不自然になるため)。
あとはNextWord.Countを調べて値が大きいものを選ばれやすくしてからランダムに選びます。そして単語をつなげて文章をつくります。このままつなげていくと永久に終わらない可能性があるので適当なところで「。」を見つけたら処理を終了しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
public partial class Form1 : Form { private void button2_Click(object sender, EventArgs e) { if (!File.Exists("./text.txt")) { richTextBox1.Text = "データが保存されていません"; return; } Random random = new Random(); List<WordInfo> wordInfos = new List<WordInfo>(); XmlSerializer serializer = new XmlSerializer(typeof(List<WordInfo>)); StreamReader sr = new StreamReader("./text.txt"); wordInfos = (List<WordInfo>)serializer.Deserialize(sr); sr.Close(); int r = random.Next(wordInfos.Count); WordInfo cur = wordInfos[r]; while (cur.PartOfSpeech != "名詞") { r = random.Next(wordInfos.Count); cur = wordInfos[r]; } string str = cur.Word; int i = 0; while (cur != null) { List<NextWord> curNextWords = cur.NextWords; List<string> nextWords = new List<string>(); foreach (NextWord nextWord in curNextWords) { for (int k = 0; k < nextWord.Count; k++) nextWords.Add(nextWord.Word); } r = random.Next(nextWords.Count); cur = wordInfos.FirstOrDefault(_ => _.Word == nextWords[r]); str += nextWords[r]; i++; if (i > 300 && nextWords[r] == "。") break; if (i > 1000) break; } richTextBox1.Text = str; } } |
これが元の文章です。
こんなに素晴らしい文章が生成されました。