実行環境
Xcode14.1
前置き
swift-algorithmsを利用してうまくリファクタリングできたので紹介したい。
この中のchunkedを使用した。
どのような機能かはこちらがわかりやすかった。
https://qiita.com/jollyjoester/items/4e5df18e4f5c0429ee9e#chunked
Chunkは「かたまり」のことです。
コレクションを複数のサブシーケンスに分割します。
やりたいこと
SwiftのArrayを特定の要素で区切りたいことがあった。
具体的なケースとして、NaturalLanguage Frameworkの利用時に発生した例を示す。
NaturalLanguage Frameworkで文字列を解析したときに、文字列を記号や単語などにタグ付してくれる。この中の単語の部分を記号で区切りたかった。
具体例のコードを示す。
文字列を解析するコード
import NaturalLanguage
typealias TextTag = (text: String, tag: NLTag)
func makeTagByNL(text: String) -> [TextTag] {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = text
let options: NLTagger.Options = [.joinNames, .omitWhitespace]
var array = [(String, NLTag)]()
tagger.enumerateTags(in: text.startIndex..<text.endIndex, unit: .word, scheme: .lexicalClass, options: options) { tag, tokenRange in
if let tag = tag {
array.append((String(text[tokenRange]), tag))
}
return true
}
return array
}
利用例
let text = "野菜(じゃがいも(国産)、にんじん、とうもろこし)、トマトペースト"
let service = LinguisticService()
let result = service.makeTagByNL(text: text)
下記のようなデータが得られた。
text: 野菜, tag: OtherWord
text: (, tag: Punctuation
text: じゃがいも, tag: OtherWord
text: (, tag: Punctuation
text: 国産, tag: OtherWord
text: )、, tag: Punctuation
text: に, tag: OtherWord
text: ん, tag: OtherWord
text: じん, tag: OtherWord
text: 、, tag: Punctuation
text: とうもろこし, tag: OtherWord
text: )、, tag: Punctuation
text: トマト, tag: OtherWord
text: ペースト, tag: OtherWord
ここから記号を除去したいのだが、問題として野菜、じゃがいもなどはいいが、にんじんは文字列がバラバラになってしまっていた。バラバラな文字列は結合したい。そこで、結合のために単語を記号ごとにグループ化しようとした。
// 欲しいデータ
["野菜", "じゃがいも", "国産", "にんじん", "とうもろこし", "トマトペースト"]
実装
chunkedを使わない、リファクタリング前のコード。
事前にグループ化用のArray(oneWordArray)を用意して、記号が来たら保存用のArray(oneWordResult)に追加していった。 oneWordArray.reduce("") { $0 + $1 }
で結合を行った。
func ommitPunctuation(array: [TextTag]) -> [String] {
var oneWordArray = [String]()
var oneWordResult = [String]()
for wordAndTag in array {
if wordAndTag.1 == NLTag.punctuation {
if oneWordArray.count > 0 {
let reduced = oneWordArray.reduce("") { $0 + $1 }
oneWordResult.append(reduced)
oneWordArray.removeAll()
}
continue
}
oneWordArray.append(wordAndTag.0)
}
if !oneWordArray.isEmpty {
let reduced = oneWordArray.reduce("") { $0 + $1 }
oneWordResult.append(reduced)
}
return oneWordResult
}
chunkedを使うリファクタリング後のコード
chunkedでtagが異なる場合に別のグループとするように指定した。
fileterで単語のみにした。
mapでグループごとに結合を行った。
func ommitPunctuation(array: [TextTag]) -> [String] {
return array.chunked(by: {
$0.tag == $1.tag
}).filter {
$0.first?.tag == .otherWord
}.map {
$0.reduce("") { $0 + $1.text }
}
}
chunkedを使うことで見通しの良いコードを書くことができた。