【Swift5】キーワードの重複で「関連記事」を取得してみる


今回は、ニュースアプリの記事ページの一番下にある「関連記事」を選ぶ関数を作ったので共有したいと思います。

使用するクラス

class Entry: NSObject, NSCoding {
    var title:String?
    //省略
}

自分のアプリでは、titleの他にURLなどのプロパティも持たせています。

完成品

func getRelatedArticle(entry:Entry, entries:[Entry]) -> [Entry] {

        let exempt = ["で","の","は","を","も","が","に","万","円"]
        var words:[String] = []
        var counts:Dictionary<Entry,Int> = [:]
        let checkEntries = entries.filter {$0 != entry}

        let wordTokenizer = NLTokenizer(unit: .word)
        wordTokenizer.string = entry.title
        wordTokenizer.enumerateTokens(in: entry.title!.startIndex..<entry.title!.endIndex) { tokenRange, _ in
            let word = String(entry.title![tokenRange]).localizedUppercase
            if !exempt.contains(word) {
                words.append(word)
                print(word)
            }
            return true
        }
        //print("contains:\(words)")

        for e in checkEntries {
            var count = 0
            let text = e.title!
            wordTokenizer.string = text
            wordTokenizer.enumerateTokens(in: text.startIndex..<text.endIndex) { tokenRange, _ in
                //print( text[tokenRange])
                if words.contains(String(text[tokenRange].localizedUppercase)) {
                    count += 1
                }
                return true
            }
            counts.updateValue(count, forKey: e)
        }
        let sorted = Array(counts).sorted {$0.1 > $1.1}.prefix(3)
        var related:[Entry] = []
        for (key,value) in (sorted) {
            //print("\(key.title!) 単語数:\(value)")
            related.append(key)
        }
        return related
}

全ての記事名を単語に分解して、重複する単語が多かった記事を関連記事として選んでいます。 分解には標準ライブラリのNaturalLanguageを使っているので忘れずにimportしてください。

import NaturalLanguage

使用例

//nowEntry -> 今見ている記事
//AllEntries -> 全ての記事の配列
let related = getRelatedArticle(entry: nowEntry, entries: AllEntries)
print(related.map({$0.title!}))

出力結果

この記事で実験してみました。 また、全記事のリストはいくつかニュースサイトのRSSから取得しています。

コメントアウトしてあるprintを使えばログで確認できます。 抽出した単語と、関連記事はこんな感じになりました。

contains:[“藤井”, “七”, “段”, “充実”, “感”, “棋聖”, “戦”, “第”, “一”, “局”, “勝利”, “6”, “8”, “21”, “33”, “更新”]
藤井七段、棋聖戦第一局で渡辺三冠に勝利 6/8 20:08更新 単語数:12
藤井七段が最年少で挑戦 「棋聖戦」始まる 6/8 12:05更新 単語数:8
経団連会長 第2次補正予算案を評価 6/8 21:48更新 単語数:5

藤井七段の他の記事は取得できているので、問題ありません。 ただ、やっぱり精度が微妙なので「この記事もオススメ」みたいな感じで表示させた方がいいかも。

補足、解説

let exempt = ["で","の","は","を","も","が","に","万","円"]

ここで除外する単語を指定しています。 英語のサイトでは「a」「the」「in」などのワードが余計でした。 後々、ひらがな一文字とアルファベット一文字を自動で除外できるようにしたい。

for (key,value) in (Array(counts).sorted {$0.1 > $1.1}.prefix(3)) {
    //print("\(key.title!) 単語数:\(value)")
    related.append(key)
}

ここのprefix(3)を変更することで取得する記事の数が変更できます。

課題

  • この記事のタイトルのように括弧などの記号などが含まれていると単語が取得できない

参考

Comments

Show Comments