5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

【Swift】構造体のArrayから重複要素を除去する

5
Last updated at Posted at 2020-06-18

はじめに

次のように、重複要素を持つ Array から順序を維持して重複要素を除去したい場合を考えます。

before
["a", "x", "b", "x", "c", "x", "a", "x", "d"]
after
["a", "x", "b", "c", "d"]

NSOrderedSet を使う

Foundation フレームワークには順序付き集合を扱うためのクラス NSOrderedSet が提供されています。これを用いることで、順序を維持したまま重複要素を除去できます。

実装例
extension Array {
  func uniqued() -> Array {
    return NSOrderedSet(array: self).array as! [Element]
  }
}
利用例
let uniqued = ["a", "x", "b", "x", "c", "x", "a", "x", "d"].uniqued()
dump(uniqued)
出力結果
▿ 5 elements
  - "a"
  - "x"
  - "b"
  - "c"
  - "d"

とてもカンタンで、とても便利ですね。

構造体の Array から重複要素を除去する

ところで、NSOrderedSet は Foundation フレームワークで提供されたクラスです。ということは、つまり、Objective-C 互換なオブジェクトでないと使えないのでは?と考えていたのですが、Hashable に適合していれば、NSOrderedSet を利用することができます。

こんなカンジの適当な Hashable な構造体があって、

適当なHashable
struct Foo: Hashable {
  var foo: Int
  var bar: Int
}

適当に重複要素を持つリストを作ります。

適当な重複要素を持つリスト
let array: [Foo] = (0...99999).map {
  Foo(foo: $0 % 2, bar: $0 % 3)
}

すると、たしかに順序を維持して、重複を除去することが確認できました。

重複を除去して出力
dump(array.uniqued())
出力結果
▿ 6 elements
  ▿ ***.Foo
    - foo: 0
    - bar: 0
  ▿ ***.Foo
    - foo: 1
    - bar: 1
  ▿ ***.Foo
    - foo: 0
    - bar: 2
  ▿ ***.Foo
    - foo: 1
    - bar: 0
  ▿ ***.Foo
    - foo: 0
    - bar: 1
  ▿ ***.Foo
    - foo: 1
    - bar: 2

純粋な Swift の値型にも適用できて、とても便利ですね。

なぜ構造体を Foundation フレームワークで扱えるのか?

NSOrderedSet は Foundation フレームワークで提供されたクラスなので、Objective-C 互換なオブジェクトでないと使えないのでは?と、考えていたのですが、実はこれ自体は正しいです。

Swift では、構造体を暗黙的に Objective-C 互換なオブジェクトに変換する仕組みを提供しています。
値型をいったん Any 型に変換して、更に AnyObject に変換することで、値型から参照型を生成することが可能です。

参照型を生成して出力する
let foo = (Foo(foo: 0, bar: 1) as Any) as AnyObject
dump(foo)

出力結果から、NSObject のサブクラスであることがわかります。

出力結果
- ***.Foo(foo: 0, bar: 1) #0
  - super: NSObject

また、クラス名は type(of:) 関数を使用することで、 __SwiftValue であることがわかります。

参照型を生成してクラス名を出力する
let foo = (Foo(foo: 0, bar: 1) as Any) as AnyObject
dump(type(of: foo))
出力結果
- __SwiftValue #0

逆に、このオブジェクトに対して、AnyFoo の順番で変換することで、もとの値型として扱うこともできるみたいです。

もとの値型を出力する
let foo = (Foo(foo: 0, bar: 1) as Any) as AnyObject
let value = (foo as Any) as! Foo
dump(value)
出力結果
***.Foo
  - foo: 0
  - bar: 1

__SwiftValueHashable

__SwiftValue は NSObject のサブクラスなので、isEqual メソッドと hash メソッドを実装しています。

isEqualhash の実装は、変換前の値型が Hashable かどうか判定して、Hashable な場合はこの ==hash(into:) の実装を呼び出しているように見えました。

swift/SwiftValue.mm at swift-5.2-RELEASE · apple/swift

この実装のおかげで、Swift の Hashable が、Objective-C 側の世界の一意性やハッシュ値に一致するので NSOrderedSet で重複要素を除去することができるみたいです。

Hashable な構造体はこのように、isEqualhash を実装した NSObject に変換されるので、NSOrderedSet に限らず、Foundation のコレクションの要素として自由に扱えるみたいです。
たとえば、NSCache<AnyObject, AnyObject> のキーとしても機能することが確認できました。

とても便利なので、使っていきたい機能だと思いました。

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?