SwiftでArrayから重複なしの順序を保ったままのリストを生成したい!
と思って調べてみると、この辺の記事が参考になりますが、reduce()を使って愚直に全てをなめていく実装が散見されます
今回はもっとスマートさを求めてシンプルに書けないか考えていきます
(よりシンプルに書くことに特化しているのでパフォーマンスは気にしてません)
Setは使える?
SwiftにはSetがあります。が、答えはノーです。
なぜなら、Setは重複しない順序を持たないリストだからです。
やってみます。
let array: [String] = ["hoge", "fuga", "hoge", "piyo", "piyo"]
// 最初に出てきた順でユニークになってほしい -> ["hoge", "fuga", "piyo"]
print(Set(array)) // "["fuga", "piyo", "hoge"]\n"
print(Set(array)) // "["piyo", "hoge", "fuga"]\n"
print(Set(array)) // "["piyo", "hoge", "fuga"]\n"
毎回結果が変わってしまいました。これでは順序を保つという条件が保証できません
順序付きのSetってないの?
Swift用はSet一択のようで、存在しないようです。
ですが、Foundation FrameworkにはNSOrderedSetという昔ながらのImmutableな順序付きSetのクラスが現在もサポートされています。
NSOrderedSetを使って順序付きのユニークリストを作る
さっそく書いていきましょう。
import Foundation
extension Array where Element: Hashable {
func unique() -> [Element] {
return NSOrderedSet(array: self).array as! [Element]
}
}
こんな感じに書けるかと思います。
どうでしょう、めちゃくちゃシンプルですよね
NSOrderedSetに入れて吐き出すだけです!
NSOrderedSetはObjective-C時代のものなので、インターフェースがNSArray時代のように[Any]
となってしまいます。
そのため、入力時も[Any]
として扱われ、出力ももちろん[Any]
として返ってきてしまいます。
そこで、SwiftのArrayのextensionとしてメソッドを用意し、Elementの型を入出力で縛ってあげることによって、使用する側は型安全でSwiftらしく書けるようにしています。
(as!
のような汚い実装はこうしてextensionの中に隠蔽してしまいましょう)
では、最後に結果を確認していきます。
let array: [String] = ["hoge", "fuga", "hoge", "piyo", "piyo"]
print(array.unique()) // "["hoge", "fuga", "piyo"]\n"
print(array.unique()) // "["hoge", "fuga", "piyo"]\n"
print(array.unique()) // "["hoge", "fuga", "piyo"]\n"
いかがでしょうか?
呼び出しもシンプルで今回の目的を達成することができました
こちらは順序をサポートしているので、何度実行しても結果は変わりません!
P.S.
- Advent Calendarに登録しようと思っていたのにすっかり忘れてしまったので、普通の投稿です(笑)