Swiftで配列から重複要素を削除する


252

最近のSwiftでは、単純に入力Set( yourArray )して配列を一意にしています。(または、必要に応じて注文セット。)

それが可能になる前に、それはどのように行われましたか?


次のような配列があるかもしれません。

[1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]

または、実際には、データの同じタイプの部分のシーケンス。私がしたいことは、同一の各要素が1つだけであることを確認することです。たとえば、上記の配列は次のようになります。

[1, 4, 2, 6, 24, 15, 60]

2、6、および15の重複が削除され、各同一要素が1つだけ存在するようになっていることに注意してください。Swiftはこれを簡単に行う方法を提供していますか、それとも自分で行う必要がありますか?


11
最も簡単な方法は、配列をに変換することですNSSet。NSSetは、順序NSOrderedSetを維持する必要がある場合、オブジェクトの順序なしコレクションです。
Andrea

このクラスにある配列の関数を使用して、交差関数を使用できます:github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift
Edwin Vermeer

Swiftの一部ではありませんが、私はDollarを使用しています。$.uniq(array) github.com/ankurp/Dollar#uniq---uniq
アンディ

おそらく最もエレガントでスマートで最も速い答えは、以下のmxclの答えによって提供されます。これは秩序を維持するのにも役立ちます
Honey

1
なぜSetSwiftから使用しないのですか?順序付けられていない一意の要素のリストを提供できます。
TibiaZ

回答:


133

たとえば、次のように自分でロールすることができます(Swift 1.2でSetを使用して更新

func uniq<S : SequenceType, T : Hashable where S.Generator.Element == T>(source: S) -> [T] {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

let vals = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let uniqueVals = uniq(vals) // [1, 4, 2, 6, 24, 15, 60]

Swift 3バージョン:

func uniq<S : Sequence, T : Hashable>(source: S) -> [T] where S.Iterator.Element == T {
    var buffer = [T]()
    var added = Set<T>()
    for elem in source {
        if !added.contains(elem) {
            buffer.append(elem)
            added.insert(elem)
        }
    }
    return buffer
}

そしての拡張としてArray

extension Array where Element: Hashable {
    var uniques: Array {
        var buffer = Array()
        var added = Set<Element>()
        for elem in self {
            if !added.contains(elem) {
                buffer.append(elem)
                added.insert(elem)
            }
        }
        return buffer
    }
}

12
その関数の本体を次のように実装することもできますvar addedDict = [T:Bool](); return filter(source) { addedDict(true, forKey: $0) == nil }
Airspeed Velocity

1
@AirspeedVelocity:もしかしてupdateValue(true, forKey: $0)...代わりにaddedDict(true, forKey: $0)...
Jawwad

1
おっとはい、申し訳ありませんが誤ってメソッドを使用しました!あなたがreturn filter(source) { addedDict.updateValue(true, forKey: $0) == nil }言うとおりでなければなりません。
対気速度

21
注意点:このような単純な関数のパフォーマンスについては、そのパフォーマンスに依存することが証明できるまでは説明しないでください。仮定をしているために、メンテナンスできないコードやパフォーマンスの低いコードを目にすることが多すぎます。:)また、これはおそらく理解しやすいでしょうlet uniques = Array(Set(vals))
。– Blixt

11
@Blixt同意。繰り返しますが、ここでの利点は、元の配列の要素の順序を尊重することです。
Jean-Philippe Pellet 2016

493

セットに変換したり、配列に戻したりするのは非常に簡単です。

let unique = Array(Set(originals))

これは、配列の元の順序を維持することを保証するものではありません。


37
配列の元の順序を維持しながらセットを使用する方法はありますか?
Crashalot、2015

6
@Crashalot私の答えを見てください。
Jean-Philippe Pellet 2015

5
特定のプロパティによってオブジェクトを一意に保つ必要がある場合は、単にArray-> Set-> Array変換を使用する代わりに、そのクラスにHashableおよびEquatableプロトコルを実装する
Fawkes

2
いいね!このソリューションの複雑さはどのくらいですか?
JW.ZG 2016

2
の要素originalsがそうでない場合は失敗しHashableます。Hashableセットに追加できるのはデータ型のみですが、配列には任意のデータ型を追加できます。
Mecki

69

ここには多くの回答がありますが、Swift 2以降に適したこの単純な拡張機能がありませんでした。

extension Array where Element:Equatable {
    func removeDuplicates() -> [Element] {
        var result = [Element]()

        for value in self {
            if result.contains(value) == false {
                result.append(value)
            }
        }

        return result
    }
}

超シンプルに。このように呼び出すことができます:

let arrayOfInts = [2, 2, 4, 4]
print(arrayOfInts.removeDuplicates()) // Prints: [2, 4]

プロパティに基づくフィルタリング

プロパティに基づいて配列をフィルタリングするには、次の方法を使用できます。

extension Array {

    func filterDuplicates(@noescape includeElement: (lhs:Element, rhs:Element) -> Bool) -> [Element]{
        var results = [Element]()

        forEach { (element) in
            let existingElements = results.filter {
                return includeElement(lhs: element, rhs: $0)
            }
            if existingElements.count == 0 {
                results.append(element)
            }
        }

        return results
    }
}

次のように呼び出すことができます:

let filteredElements = myElements.filterDuplicates { $0.PropertyOne == $1.PropertyOne && $0.PropertyTwo == $1.PropertyTwo }

@Antoineプロパティ拡張に基づくフィルタリングをありがとうございます。本当に便利です。しかし、それがどのように機能するか説明していただけますか。私には理解するのが難しすぎます。ありがとう
モスタファモハメドラーファト2016年

swift 3の更新:func filterDuplicates(_ includeElement:(_ lhs:Element、_ rhs:Element)-> Bool)-> [Element] {
cbartel

この回答の最初の部分(extension Array where Element: Equatable)は、より強力なソリューションを提供するstackoverflow.com/a/36048862/1033581に置き換えられています(extension Sequence where Iterator.Element: Equatable)。
・クール

7
これにはO(n²)時間のパフォーマンスがありますが、これは大規模な配列にとっては本当に悪いことです。
ダンカンC

あなたは、この恐ろしいもたらすために、これまで見て要素を追跡するためにセットを使用する必要がありますO(n²)バックダウンに複雑さをO(n)
アレクサンダー-復活モニカ

63

Swift 3.0

let uniqueUnordered = Array(Set(array))
let uniqueOrdered = Array(NSOrderedSet(array: array))

1
させますuniqueOrderedNames = Array(NSOrderedSet(array:userNames))as![文字列] uが文字列の配列ではなく、文字列の配列を持っている場合
Zaporozhchenko Oleksandr

の要素arrayがそうでない場合は失敗しHashableます。Hashableセットに追加できるのはデータ型のみですが、配列には任意のデータ型を追加できます。
Mecki

要素がHashableであり、順序を保持したいという前提で、Swift 5.1b5でテストされました。NSOrderedSet(array:array).arrayは、フィルター付きのセットを使用する純粋な高速func uniqued()よりもわずかに高速です。13個の一意の値が得られる5100個の文字列でテストしました。
dlemex

62

コードに両方の拡張機能を含めるHashableと、可能な場合はより高速なバージョンがEquatable使用され、そのバージョンがフォールバックとして使用されます。

public extension Sequence where Element: Hashable {
  var firstUniqueElements: [Element] {
    var set: Set<Element> = []
    return filter { set.insert($0).inserted }
  }
}

public extension Sequence where Element: Equatable {
  var firstUniqueElements: [Element] {
    reduce(into: []) { uniqueElements, element in
      if !uniqueElements.contains(element) {
        uniqueElements.append(element)
      }
    }
  }
}

順序が重要でない場合は、いつでもこのSet初期化子を使用できます


わかった。私の配列は構造体の配列なので、呼び出すことができませんでした...私の場合、どのように処理しますか?20の異なる変数の構造体、文字列と[文字列]
David Seek

@David Seek厳密なハッシュ可能または赤道化していないようです。あれは正しいですか?
Jessy、2016

1
@DavidSeek like this、uniqueArray = nonUniqueArray.uniqueElements
Mert Celik

うん、心配しないで。すぐに機能しました。ほぼ2年になった:P
デビッドシーク

これにはO(n²)時間のパフォーマンスがありますが、これは大規模な配列にとっては本当に悪いことです。
Duncan C

44

Swift 4以降の編集/更新

RangeReplaceableCollectionプロトコルを拡張してStringProtocolタイプでも使用できるようにすることもできます。

extension RangeReplaceableCollection where Element: Hashable {
    var orderedSet: Self {
        var set = Set<Element>()
        return filter { set.insert($0).inserted }
    }
    mutating func removeDuplicates() {
        var set = Set<Element>()
        removeAll { !set.insert($0).inserted }
    }
}

let integers = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let integersOrderedSet = integers.orderedSet // [1, 4, 2, 6, 24, 15, 60]

"abcdefabcghi".orderedSet  // "abcdefghi"
"abcdefabcghi".dropFirst(3).orderedSet // "defabcghi"

変異方法:

var string = "abcdefabcghi"
string.removeDuplicates() 
string  //  "abcdefghi"

var substring = "abcdefabcdefghi".dropFirst(3)  // "defabcdefghi"
substring.removeDuplicates()
substring   // "defabcghi"

スウィフト3クリックしてくださいここに


1
私はこれが好きです、それは辞書の配列でも動作します!
DeyaEldeen

6
O(N ^ 2)は悪いです:(
アレクサンダー-モニカを復活させます

1
@Alexander Leo Dabusがreduce実装を置き換えたため、複雑さが異なります。
・クール

1
結果は興味深いものです。100万個の一意のアイテムと800万個のアイテムの両方で、フィルターバージョンの方が高速です。ただし、フィルターベースのバージョンは、800万個の一意の項目(O(n)時間の経過に伴うヘア)の8.38倍の時間がかかります。フラットマップベースのバージョンは、100万個よりも800万個の一意のエントリの7.47倍の時間がかかるため、フラットマップベースのバージョンはより適切にスケーリングされます。 。どういうわけか、フラットマップベースのバージョンは、O(n)時間よりもわずかに優れています!
ダンカンC

1
実際、配列内の64倍の項目でテストを実行すると、フラットマップベースのバージョンの方が高速です。
ダンカンC

43

スウィフト4

public extension Array where Element: Hashable {
    func uniqued() -> [Element] {
        var seen = Set<Element>()
        return filter{ seen.insert($0).inserted }
    }
}

を試みるたびにinsertタプルも返されます:(inserted: Bool, memberAfterInsert: Set.Element)ドキュメントを参照してください。

戻り値を使用すると、ループやその他の操作を回避できます。


7
単純なプロファイリングの後、この方法は非常に高速です。reduce(_:_ :)またはreduce(into:_ :)を使用するよりも数百倍高速
ケルビン

3
@Kelvin他のすべてのアルゴリズムがO(n^2)であり、誰も気づいていないため。
アレクサンダー-モニカを復活させる

@Kelvinこの回答は、Eneko Alonsoの回答 +私のコメント(Jun 16 '17)と同じです。
・クール

27

スウィフト4

引き続きご注文いただけます。

extension Array where Element: Equatable {
    func removingDuplicates() -> Array {
        return reduce(into: []) { result, element in
            if !result.contains(element) {
                result.append(element)
            }
        }
    }
}

私はこれを今使用し、メソッド名をremoveDuplicatesに変更しただけです:)
J. Doe

このソリューションはコンパクトだと思いますが、1年前に投稿されたdeanWombourneソリューションは、全体よりもわずかに効率的かもしれないとreduce思いますvar unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return unique。相対的なパフォーマンスをまだテストしていないことは認めます。
・クール

3
これにはO(n²)時間のパフォーマンスがありますが、これは大規模な配列にとっては本当に悪いことです。
Duncan C

@NickGaensいいえ、そうではありませんO(n²)。これについては迅速なことは何もありません。
アレクサンダー-モニカを復活させる

@Cœur reduceまたはreduce(into:)重要な違いはありません。繰り返し呼び出さcontainsないようにこれを書き換えると、はるかに大きな違いが生じます。
アレクサンダー-モニカを復活させる

16

SequenceTypeこれは、配列の元の順序を保持するカテゴリSetですが、配列のメソッドのコストcontainsを回避するためにa を使用してルックアップを行います。O(n)contains(_:)

public extension Sequence where Element: Hashable {

    /// Return the sequence with all duplicates removed.
    ///
    /// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]`
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234, as 
    ///         per @Alexander's comment.
    func uniqued() -> [Element] {
        var seen = Set<Element>()
        return self.filter { seen.insert($0).inserted }
    }
}

HashableまたはEquatableでない場合は、述語を渡して等価チェックを実行できます。

extension Sequence {

    /// Return the sequence with all duplicates removed.
    ///
    /// Duplicate, in this case, is defined as returning `true` from `comparator`.
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    func uniqued(comparator: @escaping (Element, Element) throws -> Bool) rethrows -> [Element] {
        var buffer: [Element] = []

        for element in self {
            // If element is already in buffer, skip to the next element
            if try buffer.contains(where: { try comparator(element, $0) }) {
                continue
            }

            buffer.append(element)
        }

        return buffer
    }
}

Hashableがなく Equatableである場合は、このメソッドを使用できます。

extension Sequence where Element: Equatable {

    /// Return the sequence with all duplicates removed.
    ///
    /// i.e. `[ 1, 2, 3, 1, 2 ].uniqued() == [ 1, 2, 3 ]`
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    func uniqued() -> [Element] {
        return self.uniqued(comparator: ==)
    }
}

最後に、次のようにキーパスバージョンのuniquedを追加できます。

extension Sequence {

    /// Returns the sequence with duplicate elements removed, performing the comparison usinig the property at
    /// the supplied keypath.
    ///
    /// i.e.
    ///
    /// ```
    /// [
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "World")
    ///  ].uniqued(\.value)
    /// ```
    /// would result in
    ///
    /// ```
    /// [
    ///   MyStruct(value: "Hello"),
    ///   MyStruct(value: "World")
    /// ]
    /// ```
    ///
    /// - note: Taken from stackoverflow.com/a/46354989/3141234
    ///
    func uniqued<T: Equatable>(_ keyPath: KeyPath<Element, T>) -> [Element] {
        self.uniqued { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
    }
}

これらの両方をアプリに貼り付けることができ、SwiftはシーケンスのIterator.Elementタイプに応じて適切なものを選択します。


やっとO(n)解決策を手に入れました。ちなみに、「チェック」と「挿入」のセット操作を1つに組み合わせることができます。stackoverflow.com/a/46354989/3141234を
アレクサンダー

おお、それは賢いです:)
deanWombourne

14

https://www.swiftbysundell.com/posts/the-power-of-key-paths-in-swiftに触発されて、任意のkeyPathの単一性をフィルタリングできるより強力なツールを宣言できます。複雑さに関するさまざまな回答についてのAlexanderコメントのおかげで、以下の解決策はほぼ最適になるはずです。

非変異ソリューション

任意のkeyPathで一意性をフィルタリングできる関数を拡張します。

extension RangeReplaceableCollection {
    /// Returns a collection containing, in order, the first instances of
    /// elements of the sequence that compare equally for the keyPath.
    func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> Self {
        var unique = Set<T>()
        return filter { unique.insert($0[keyPath: keyPath]).inserted }
    }
}

注:オブジェクトがRangeReplaceableCollectionに準拠しているが、Sequenceには準拠している場合、この追加の拡張機能を使用できますが、戻り値の型は常に配列になります。

extension Sequence {
    /// Returns an array containing, in order, the first instances of
    /// elements of the sequence that compare equally for the keyPath.
    func unique<T: Hashable>(for keyPath: KeyPath<Element, T>) -> [Element] {
        var unique = Set<T>()
        return filter { unique.insert($0[keyPath: keyPath]).inserted }
    }
}

使用法

質問のように、要素自体に単一性が必要な場合は、keyPathを使用します\.self

let a = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
let b = a.unique(for: \.self)
/* b is [1, 4, 2, 6, 24, 15, 60] */

idのオブジェクト(オブジェクトのコレクションなど)の単一性が必要な場合は、選択したkeyPathを使用します。

let a = [CGPoint(x: 1, y: 1), CGPoint(x: 2, y: 1), CGPoint(x: 1, y: 2)]
let b = a.unique(for: \.y)
/* b is [{x 1 y 1}, {x 1 y 2}] */

変異ソリューション

任意のkeyPathで単一性をフィルタリングできる変異関数を使用して拡張します。

extension RangeReplaceableCollection {
    /// Keeps only, in order, the first instances of
    /// elements of the collection that compare equally for the keyPath.
    mutating func uniqueInPlace<T: Hashable>(for keyPath: KeyPath<Element, T>) {
        var unique = Set<T>()
        removeAll { !unique.insert($0[keyPath: keyPath]).inserted }
    }
}

使用法

質問のように、要素自体に単一性が必要な場合は、keyPathを使用します\.self

var a = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
a.uniqueInPlace(for: \.self)
/* a is [1, 4, 2, 6, 24, 15, 60] */

idのオブジェクト(オブジェクトのコレクションなど)の単一性が必要な場合は、選択したkeyPathを使用します。

var a = [CGPoint(x: 1, y: 1), CGPoint(x: 2, y: 1), CGPoint(x: 1, y: 2)]
a.uniqueInPlace(for: \.y)
/* a is [{x 1 y 1}, {x 1 y 2}] */

1
これは良い実装です!キーパスだけがクロージャに変換可能だったので、クロージャ引数を使用して、任意のコード(クロージャ内)と単なるプロパティルックアップ(キーパス経由)の両方をサポートできます。私が行う唯一の変更は、keyPathデフォルトを\.selfにすることです。これはおそらくほとんどのユースケースであるためです。
アレクサンダー-モニカの復活

1
@Alexander私はデフォルトでSelfを使おうとしましたが、それをElement常に行う必要がありHashableます。デフォルト値の代わりに、パラメータなしの単純なオーバーロードを追加することもできます。extension Sequence where Element: Hashable { func unique() { ... } }
CœurApr

ああ、そうですね。
アレクサンダー-モニカの復活

1
ブリリアント...シンプル、そして何よりも「柔軟」。どうも。
BonanzaDriver

12

変数ではなく不変の型を使用した、ここからの代替の(最適でない場合)ソリューション:

func deleteDuplicates<S: ExtensibleCollectionType where S.Generator.Element: Equatable>(seq:S)-> S {
    let s = reduce(seq, S()){
        ac, x in contains(ac,x) ? ac : ac + [x]
    }
    return s
}

Jean-Pillippeの命令型アプローチと機能的アプローチを対比するために含まれています。

おまけとして、この関数は文字列と配列の両方で機能します!

編集:この回答は2014年にSwift 1.0向けに作成されました(以前SetはSwiftで利用可能でした)。Hashableに準拠する必要はなく、2次時間で実行されます。


8
注意してください、これは二次時間で実行する方法は1つではありませんが、2つあります。両方ともcontainsO(n)で配列追加実行します。ただし、ハッシュ可能ではなく、赤道のみを必要とするという利点があります。
対気速度14

これは本当に複雑な書き方filterです。これはO(n ^ 2)(Hashable準拠を要求しない場合は必須)ですが、少なくとも明示的に呼び出す必要があります
Alexander-Reinstate Monica

10

迅速2

uniqの関数の答え:

func uniq<S: SequenceType, E: Hashable where E==S.Generator.Element>(source: S) -> [E] {
    var seen: [E:Bool] = [:]
    return source.filter({ (v) -> Bool in
        return seen.updateValue(true, forKey: v) == nil
    })
}

使用する:

var test = [1,2,3,4,5,6,7,8,9,9,9,9,9,9]
print(uniq(test)) //1,2,3,4,5,6,7,8,9

Boolあなたのコードがそれを読み込むことがないよう値は明らかに、冗長です。のSet代わりにを使用するDictionaryと、私の賛成票が取得されます。
Nikolai Ruhe

10

Swift 5で

 var array: [String] =  ["Aman", "Sumit", "Aman", "Sumit", "Mohan", "Mohan", "Amit"]

 let uniq = Array(Set(array))
 print(uniq)

出力は

 ["Sumit", "Mohan", "Amit", "Aman"]

2
これはすでにここにある多くの答えの繰り返しであり、順序を保持しません。
アレクサンダー-モニカを

9

配列から重複を削除するもう1つのSwift 3.0ソリューション。このソリューションは、既に提案されている他の多くのソリューションを改善します。

  • 入力配列の要素の順序を保持する
  • 線形複雑度O(n):シングルパスフィルターO(n)+セット挿入O(1)

整数配列が与えられます:

let numberArray = [10, 1, 2, 3, 2, 1, 15, 4, 5, 6, 7, 3, 2, 12, 2, 5, 5, 6, 10, 7, 8, 3, 3, 45, 5, 15, 6, 7, 8, 7]

機能コード:

func orderedSet<T: Hashable>(array: Array<T>) -> Array<T> {
    var unique = Set<T>()
    return array.filter { element in
        return unique.insert(element).inserted
    }
}

orderedSet(array: numberArray)  // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

配列拡張コード:

extension Array where Element:Hashable {
    var orderedSet: Array {
        var unique = Set<Element>()
        return filter { element in
            return unique.insert(element).inserted
        }
    }
}

numberArray.orderedSet // [10, 1, 2, 3, 15, 4, 5, 6, 7, 12, 8, 45]

このコードは、inserton Setで実行される操作on によって返された結果を利用してO(1)、アイテムが挿入されたか、またはセットにすでに存在していたかを示すタプルを返します。

アイテムがセットに含まれていた場合、filter最終結果から除外されます。


1
うるさいというわけではありませんが、要素の数だけ挿入とメンバーシップのテストを実行するので、それらのコストもO(n)として数える必要があります。これは3xO(n)を意味するものではありませんが、これらのOはフィルターと同じコストではないため、O(n)の追加はオレンジへのリンゴです。集合演算をフィルターコストのO(1)部分と見なす場合、より大きな "O"を使用しても、複雑度はO(n)にすぎません。これを限界まで押し上げると、要素が既にセットにあるときに挿入を回避することもできます。
アランT.

そのとおりです。deferコードを使用すると、setテスト操作が2回行わcontainsinsertます。Swiftのドキュメントをさらに読むとinsert、要素が挿入されたかどうかを示すタプルを返すことがわかりました。そのため、containsチェックを削除するコードを簡略化しました。
エネコアロンソ

2
いいね。あなたの拡張機能は、上でそれをすることによって、最適な可能性extension Sequence where Iterator.Element: Hashable { ... }
・クール

@AlainT。いいえ。どちらinsertcontains持っているO(1)複雑さを。O(1) + O(1) = O(1)。次に、これらの2つの操作がn時間実行されます(filter要素に1回呼び出される、に渡されるクロージャの呼び出しごとに1回)。つまり、入力サイズに関係なく操作に一定の時間がかかる場合、2回実行すると、一定の時間がかかります。入力サイズに関係ありません。これの全体的な複雑さはですO(n)
アレクサンダー-モニカを復活させる

9

Swift 4.x:

extension Sequence where Iterator.Element: Hashable {
  func unique() -> [Iterator.Element] {
    return Array(Set<Iterator.Element>(self))
  }

  func uniqueOrdered() -> [Iterator.Element] {
    return reduce([Iterator.Element]()) { $0.contains($1) ? $0 : $0 + [$1] }
  }
}

使用法:

["Ljubljana", "London", "Los Angeles", "Ljubljana"].unique()

または

["Ljubljana", "London", "Los Angeles", "Ljubljana"].uniqueOrdered()

これですO(n^2)。これを行わないでください。
アレクサンダー-モニカを復活させる

8

スウィフト5

extension Sequence where Element: Hashable {
    func unique() -> [Element] {
        NSOrderedSet(array: self as! [Any]).array as! [Element]
    }
}

比較するキーを選択できるように、いくつかのバリエーションを作りました。 extension Sequence { // Returns distinct elements based on a key value. func distinct<key: Hashable>(by: ((_ el: Iterator.Element) -> key)) -> [Iterator.Element] { var existing = Set<key>() return self.filter { existing.insert(by($0)).inserted } } }
Marcelo de Aguiar

使用するBool値がだけの場合、を使用する必要はありませんtrue。「ユニットタイプ」(可能な値が1つだけのタイプ)に到達しています。SwiftのユニットタイプはでVoid、その唯一の値は()(別名空のタプル)です。したがって、そのまま使用できます[T: Void]。基本的に発明しSetただけなので、そうすべきではありません。Set代わりに使用してください。stackoverflow.com/a/55684308/3141234を参照してください。この回答を削除してください。
アレクサンダー-モニカを

8

関数型プログラマのように考える:)

要素がすでに発生しているかどうかに基づいてリストをフィルタリングするには、インデックスが必要です。を使用enumeratedしてインデックスを取得し、map値のリストに戻ることができます。

let unique = myArray
    .enumerated()
    .filter{ myArray.firstIndex(of: $0.1) == $0.0 }
    .map{ $0.1 }

これは順序を保証します。順序を気にしない場合は、の既存の答えArray(Set(myArray))が単純で、おそらくより効率的です。


更新:効率と正確さに関する注意事項

数人が効率についてコメントしています。私は間違いなく、最初に正確で単純なコードを書き、それから後でボトルネックを見つけることに取り組んでいますが、これがより明確であるかどうかは議論の余地がありArray(Set(array))ます。

この方法は、に比べてかなり時間がかかりますArray(Set(array))。コメントで述べたように、それは順序を保持し、ハッシュ可能でない要素で動作します。

ただし、@ Alain Tのメソッドは順序も保持し、速度も大幅に向上します。したがって、要素タイプがハッシュ可能でない場合、または簡単なワンライナーが必要な場合を除いて、それらのソリューションを使用することをお勧めします。

リリースモードのXcode 11.3.1(Swift 5.1)上のMacBook Pro(2014)でのいくつかのテストを次に示します。

プロファイラ関数と比較する2つの方法:

func printTimeElapsed(title:String, operation:()->()) {
    var totalTime = 0.0
    for _ in (0..<1000) {
        let startTime = CFAbsoluteTimeGetCurrent()
        operation()
        let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
        totalTime += timeElapsed
    }
    let meanTime = totalTime / 1000
    print("Mean time for \(title): \(meanTime) s")
}

func method1<T: Hashable>(_ array: Array<T>) -> Array<T> {
    return Array(Set(array))
}

func method2<T: Equatable>(_ array: Array<T>) -> Array<T>{
    return array
    .enumerated()
    .filter{ array.firstIndex(of: $0.1) == $0.0 }
    .map{ $0.1 }
}

// Alain T.'s answer (adapted)
func method3<T: Hashable>(_ array: Array<T>) -> Array<T> {
    var uniqueKeys = Set<T>()
    return array.filter{uniqueKeys.insert($0).inserted}
}

そして、いくつかのテスト入力:

func randomString(_ length: Int) -> String {
  let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
  return String((0..<length).map{ _ in letters.randomElement()! })
}

let shortIntList = (0..<100).map{_ in Int.random(in: 0..<100) }
let longIntList = (0..<10000).map{_ in Int.random(in: 0..<10000) }
let longIntListManyRepetitions = (0..<10000).map{_ in Int.random(in: 0..<100) }
let longStringList = (0..<10000).map{_ in randomString(1000)}
let longMegaStringList = (0..<10000).map{_ in randomString(10000)}

出力として与える:

Mean time for method1 on shortIntList: 2.7358531951904296e-06 s
Mean time for method2 on shortIntList: 4.910230636596679e-06 s
Mean time for method3 on shortIntList: 6.417632102966309e-06 s
Mean time for method1 on longIntList: 0.0002518167495727539 s
Mean time for method2 on longIntList: 0.021718120217323302 s
Mean time for method3 on longIntList: 0.0005312927961349487 s
Mean time for method1 on longIntListManyRepetitions: 0.00014377200603485108 s
Mean time for method2 on longIntListManyRepetitions: 0.0007293639183044434 s
Mean time for method3 on longIntListManyRepetitions: 0.0001843773126602173 s
Mean time for method1 on longStringList: 0.007168249964714051 s
Mean time for method2 on longStringList: 0.9114790915250778 s
Mean time for method3 on longStringList: 0.015888616919517515 s
Mean time for method1 on longMegaStringList: 0.0525397013425827 s
Mean time for method2 on longMegaStringList: 1.111266262292862 s
Mean time for method3 on longMegaStringList: 0.11214958941936493 s

1
とは異なりArray(Set(myArray))、これはそうではないものに機能しますHashable
ポーターチャイルド

1
...とは異なりArray(Set(myArray))、配列の順序は維持されます。
Sander Saelmans

それは私にとって最良の答えのようです。少なくとも現在のところ、Swift 5がすでに現在のバージョンであるときは。
oradyvan

これは非常にエレガントなソリューションです。残念ながら、それもかなり遅いです。
コリンスターク

1
@TimMBああ、私はあなたの投稿を読み違えました。を使用した誰かの適応を見ましたlastIndex(of:)。この場合の透明度と最適化ポイントについては完全に反対です。特に単純なセットベースのソリューションと比較して、この実装は特に明確ではないと思います。いずれの場合も、そのようなコードは拡張関数に抽出する必要があります。このアルゴリズムは、数千から数万のように、低い入力サイズでも基本的に使用できなくなります。それは、このようなデータセットを見つけるのは難しいことではありません、人々は歌、ファイル、連絡先などの何千も持つことができます
復活モニカ-アレクサンダー

6

要素がハッシュ可能でも比較可能でもない配列(たとえば、複雑なオブジェクト、辞書、または構造体)の場合、この拡張機能は、重複を削除する一般的な方法を提供します。

extension Array
{
   func filterDuplicate<T:Hashable>(_ keyValue:(Element)->T) -> [Element]
   {
      var uniqueKeys = Set<T>()
      return filter{uniqueKeys.insert(keyValue($0)).inserted}
   }

   func filterDuplicate<T>(_ keyValue:(Element)->T) -> [Element]
   { 
      return filterDuplicate{"\(keyValue($0))"}
   }
}

// example usage: (for a unique combination of attributes):

peopleArray = peopleArray.filterDuplicate{ ($0.name, $0.age, $0.sex) }

or...

peopleArray = peopleArray.filterDuplicate{ "\(($0.name, $0.age, $0.sex))" }

値をハッシュ可能にすることに煩わされる必要はなく、一意性のためにフィールドのさまざまな組み合わせを使用できます。

注:より堅牢なアプローチについては、以下のコメントでCoeurによって提案されたソリューションを参照してください。

stackoverflow.com/a/55684308/1033581

[編集] Swift 4の代替

Swift 4.2では、Hasherクラスを使用してハッシュをはるかに簡単に構築できます。上記の拡張機能は、これを活用するように変更できます。

extension Array
{
    func filterDuplicate(_ keyValue:((AnyHashable...)->AnyHashable,Element)->AnyHashable) -> [Element]
    {
        func makeHash(_ params:AnyHashable ...) -> AnyHashable
        { 
           var hash = Hasher()
           params.forEach{ hash.combine($0) }
           return hash.finalize()
        }  
        var uniqueKeys = Set<AnyHashable>()
        return filter{uniqueKeys.insert(keyValue(makeHash,$0)).inserted}     
    }
}

呼び出し構文は少し異なります。クロージャーは、可変数の値をハッシュする関数を含む追加のパラメーターを受け取るためです(個別にハッシュ可能でなければなりません)。

peopleArray = peopleArray.filterDuplicate{ $0($1.name, $1.age, $1.sex) } 

また、単一の一意性値($ 1を使用し、$ 0を無視)でも機能します。

peopleArray = peopleArray.filterDuplicate{ $1.name } 

これは、に"\()"準拠するような独自の値を提供しない可能性があるため、の動作に応じてランダムな結果をもたらす可能性がHashableあります。たとえば、Printableすべてが同じを返すことで要素が準拠する場合description、フィルタリングは失敗します。
・クール

同意した。必要な一意性パターンを生成するフィールド(または式)の選択では、これを考慮する必要があります。多くのユースケースで、これは要素のクラスまたは構造の変更を必要としないシンプルなアドホックソリューションを提供します。
アランT.

2
@AlainT。本当にこれをしないでください。Stringの目的は、ゲットーのアドホックキー生成メカニズムではありません。に制限TするだけHashableです。
アレクサンダー-モニカを復活

私は、新しい答えにこのアイデアを適用した@Alexander:stackoverflow.com/a/55684308/1033581
・クールを

私が望むように完璧な答え。どうもありがとうございます。
Hardik Thakkar

4

セットのコレクションを直接使用して重複を削除し、それを配列にキャストすることができます

var myArray = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
var mySet = Set<Int>(myArray)

myArray = Array(mySet) // [2, 4, 60, 6, 15, 24, 1]

次に、必要に応じてアレイを注文できます

myArray.sort{$0 < $1} // [1, 2, 4, 6, 15, 24, 60]

「それで、あなたはあなたが望むようにあなたの配列を注文することができます」私がオリジナルの配列からと同じ注文をしたい場合はどうなりますか?それは簡単ではありません。
アレクサンダー-モニカを復活させる

3

Daniel KromのSwift 2回答のもう少し簡潔な構文バージョン。末尾のクロージャーと短縮引数名を使用しており、Airspeed Velocityの元の回答に基づいているようです。

func uniq<S: SequenceType, E: Hashable where E == S.Generator.Element>(source: S) -> [E] {
  var seen = [E: Bool]()
  return source.filter { seen.updateValue(true, forKey: $0) == nil }
}

で使用することができるカスタム・タイプ実装の例uniq(_:)(に準拠する必要がありHashable、したがって、およびEquatableので、Hashable延びていますEquatable)。

func ==(lhs: SomeCustomType, rhs: SomeCustomType) -> Bool {
  return lhs.id == rhs.id // && lhs.someOtherEquatableProperty == rhs.someOtherEquatableProperty
}

struct SomeCustomType {

  let id: Int

  // ...

}

extension SomeCustomType: Hashable {

  var hashValue: Int {
    return id
  }

}

上記のコードでは...

id過負荷で使用されるように==、任意であってもよいEquatable(返し又はメソッドタイプEquatable、例えば、タイプsomeMethodThatReturnsAnEquatableType())。コメントアウトされたコードは、等しいかどうかのチェックを拡張する方法を示しています。ここで、someOtherEquatablePropertyEquatableタイプの別のプロパティです(ただし、Equatableタイプを返すメソッドの場合もあります)。

idは、hashValue計算されたプロパティ(に準拠する必要があるHashable)で使用されるように、任意のHashable(したがってEquatable)プロパティ(またはHashable型を返すメソッド)にすることができます。

使用例uniq(_:)

var someCustomTypes = [SomeCustomType(id: 1), SomeCustomType(id: 2), SomeCustomType(id: 3), SomeCustomType(id: 1)]

print(someCustomTypes.count) // 4

someCustomTypes = uniq(someCustomTypes)

print(someCustomTypes.count) // 3

使用するBool値がだけの場合、を使用する必要はありませんtrue。「ユニットタイプ」(可能な値が1つだけのタイプ)に到達しています。SwiftのユニットタイプはでVoid、その唯一の値は()(別名空のタプル)です。したがって、そのまま使用できます[T: Void]。基本的に発明しSetただけなので、そうすべきではありません。Set代わりに使用してください。stackoverflow.com/a/55684308/3141234を
アレクサンダー-モニカ

3

値をソートする必要がある場合、これは機能します(Swift 4)

let sortedValues = Array(Set(array)).sorted()


2
この場合、要素の順序を失います。
Shmidt

まったくそうではありません.sorted()。それが最後の目的です。よろしく。
Mauricio Chirino

@MauricioChirinoそして、あなたの元の配列ですか[2, 1, 1]?それは出てくるだろう[1, 2]、それは注文されていない:p
アレクサンダー-モニカを復活させる

2
@MauricioChirinoいいえ、違います。重複する値をシーケンスから削除することが目的であり、要素が一意に出現する順序を維持する場合、これはそれを行いません。非常に明確な反例は[2, 1, 1]です。ユニークな要素の最初の出現は、です[2, 1]。それが正解です。しかし、(正しくない)アルゴリズムを使用すると、が得られます[1, 2]。これは、並べ替えられていますが、正しい元の順序ではありません
アレクサンダー-モニカを復活させる'12

2
の要素arrayがそうでない場合は失敗しHashableます。Hashableセットに追加できるのはデータ型のみですが、配列には任意のデータ型を追加できます。
Mecki

3

ここに解決策があります

  • レガシーNSタイプを使用しない
  • かなり速い O(n)
  • 簡潔です
  • 要素の順序を保持
extension Array where Element: Hashable {

    var uniqueValues: [Element] {
        var allowed = Set(self)
        return compactMap { allowed.remove($0) }
    }
}

2

ここでは、オブジェクトのO(n)ソリューションをいくつか実行しました。数行のソリューションではありませんが...

struct DistinctWrapper <T>: Hashable {
    var underlyingObject: T
    var distinctAttribute: String
    var hashValue: Int {
        return distinctAttribute.hashValue
    }
}
func distinct<S : SequenceType, T where S.Generator.Element == T>(source: S,
                                                                distinctAttribute: (T) -> String,
                                                                resolution: (T, T) -> T) -> [T] {
    let wrappers: [DistinctWrapper<T>] = source.map({
        return DistinctWrapper(underlyingObject: $0, distinctAttribute: distinctAttribute($0))
    })
    var added = Set<DistinctWrapper<T>>()
    for wrapper in wrappers {
        if let indexOfExisting = added.indexOf(wrapper) {
            let old = added[indexOfExisting]
            let winner = resolution(old.underlyingObject, wrapper.underlyingObject)
            added.insert(DistinctWrapper(underlyingObject: winner, distinctAttribute: distinctAttribute(winner)))
        } else {
            added.insert(wrapper)
        }
    }
    return Array(added).map( { return $0.underlyingObject } )
}
func == <T>(lhs: DistinctWrapper<T>, rhs: DistinctWrapper<T>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

// tests
// case : perhaps we want to get distinct addressbook list which may contain duplicated contacts like Irma and Irma Burgess with same phone numbers
// solution : definitely we want to exclude Irma and keep Irma Burgess
class Person {
    var name: String
    var phoneNumber: String
    init(_ name: String, _ phoneNumber: String) {
        self.name = name
        self.phoneNumber = phoneNumber
    }
}

let persons: [Person] = [Person("Irma Burgess", "11-22-33"), Person("Lester Davidson", "44-66-22"), Person("Irma", "11-22-33")]
let distinctPersons = distinct(persons,
    distinctAttribute: { (person: Person) -> String in
        return person.phoneNumber
    },
    resolution:
    { (p1, p2) -> Person in
        return p1.name.characters.count > p2.name.characters.count ? p1 : p2
    }
)
// distinctPersons contains ("Irma Burgess", "11-22-33") and ("Lester Davidson", "44-66-22")

1
むしろ使用するよりもSetカスタムしてDistinctWrapper、あなたが使用する必要があるDictionaryオブジェクトにdistinctAttributesから。そのロジックをたどると、最終的に[ Dictionary.init(_:uniquingKeysWith:)] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/…)が実装され、標準ライブラリに組み込まれます。これがいかに簡単かを確認してください。pastebin.com/w90pVe0p
アレクサンダー-モニカを復活させる'16

2

私は@ Jean-Philippe Pelletの回答を使用して、要素の順序を維持しながら、配列に対してセットのような操作を行うArray拡張機能を作成しました。

/// Extensions for performing set-like operations on lists, maintaining order
extension Array where Element: Hashable {
  func unique() -> [Element] {
    var seen: [Element:Bool] = [:]
    return self.filter({ seen.updateValue(true, forKey: $0) == nil })
  }

  func subtract(takeAway: [Element]) -> [Element] {
    let set = Set(takeAway)
    return self.filter({ !set.contains($0) })
  }

  func intersect(with: [Element]) -> [Element] {
    let set = Set(with)
    return self.filter({ set.contains($0) })
  }
}

使用するBool値がだけの場合、を使用する必要はありませんtrue。「ユニットタイプ」(可能な値が1つだけのタイプ)に到達しています。SwiftのユニットタイプはでVoid、その唯一の値は()(別名空のタプル)です。したがって、そのまま使用できます[T: Void]。基本的に発明しSetただけなので、そうすべきではありません。Set代わりに使用してください。stackoverflow.com/a/55684308/3141234を
アレクサンダー-モニカ

2

これは非常にシンプルで便利な実装です。equatable要素を持つArrayの拡張の計算プロパティ。

extension Array where Element: Equatable {
    /// Array containing only _unique_ elements.
    var unique: [Element] {
        var result: [Element] = []
        for element in self {
            if !result.contains(element) {
                result.append(element)
            }
        }

        return result
    }
}



2
  1. まず、配列のすべての要素をNSOrderedSetに追加します。
  2. これにより、配列内のすべての重複が削除されます。
  3. このorderedsetを再び配列に変換します。

完了...。

let array = [1,1,1,1,2,2,2,2,4,6,8]

let orderedSet : NSOrderedSet = NSOrderedSet(array: array)

let arrayWithoutDuplicates : NSArray = orderedSet.array as NSArray

arrayWithoutDuplicatesの出力-[1,2,4,6,8]


2

@ Jean-Philippe Pelletの配列拡張の回答に基づいた、少し短めのバージョン:

extension Array where Element: Hashable {

    var uniques: Array {
        var added = Set<Element>()
        return filter { element in
            defer { added.insert(element) }
            return !added.contains(element)
        }
    }
}

これは、要素ごとに2つのハッシュ操作を実行しますが、これは不要です。insert要素がすでに存在しているか、初めて追加されたかを示すタプルを返します。stackoverflow.com/a/55684308/3141234この回答を削除してください。
アレクサンダー-

1

辞書は一意の値しか保持できないため、常に辞書を使用できます。例えば:

var arrayOfDates: NSArray = ["15/04/01","15/04/01","15/04/02","15/04/02","15/04/03","15/04/03","15/04/03"]

var datesOnlyDict = NSMutableDictionary()
var x = Int()

for (x=0;x<(arrayOfDates.count);x++) {
    let date = arrayOfDates[x] as String
    datesOnlyDict.setValue("foo", forKey: date)
}

let uniqueDatesArray: NSArray = datesOnlyDict.allKeys // uniqueDatesArray = ["15/04/01", "15/04/03", "15/04/02"]

println(uniqueDatesArray.count)  // = 3

ご覧のとおり、結果の配列は常に「順序」とは限りません。配列を並べ替え/順序付けする場合は、これを追加します。

var sortedArray = sorted(datesOnlyArray) {
(obj1, obj2) in

    let p1 = obj1 as String
    let p2 = obj2 as String
    return p1 < p2
}

println(sortedArray) // = ["15/04/01", "15/04/02", "15/04/03"]


1

最も簡単な方法は、一意の要素を格納し、要素の順序を保持するNSOrderedSetを使用することです。お気に入り:

func removeDuplicates(from items: [Int]) -> [Int] {
    let uniqueItems = NSOrderedSet(array: items)
    return (uniqueItems.array as? [Int]) ?? []
}

let arr = [1, 4, 2, 2, 6, 24, 15, 2, 60, 15, 6]
removeDuplicates(from: arr)

このパフォーマンスは、ここでのより良い回答とどのように比較されるのでしょうか。比較しましたか?
アレクサンダー-モニカを復活させる
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.