web-dev-qa-db-ja.com

Swiftを使用して配列内の重複する要素を見つける

配列内の重複要素を見つける方法は?電話番号の配列があるので、電話番号で右側から左側に向かって検索を開始し、同様の6つの整数を見つける必要があります。それから私はそれらを印刷する必要があります。

22
C0mrade

重複を見つけるには、電話番号で相互参照を作成し、それを重複のみに絞り込みます。たとえば、次のことを考慮してください。

let contacts = [
    Contact(name: "Rob",     phone: "555-1111"),
    Contact(name: "Richard", phone: "555-2222"),
    Contact(name: "Rachel",  phone: "555-1111"),
    Contact(name: "Loren",   phone: "555-2222"),
    Contact(name: "Mary",    phone: "555-3333"),
    Contact(name: "Susie",   phone: "555-2222")
]

Swift 4では、以下を使用して相互参照辞書を作成できます。

let crossReference = Dictionary(grouping: contacts, by: { $0.phone })

または

let crossReference = contacts.reduce(into: [String: [Contact]]()) {
    $0[$1.phone, default: []].append($1)
}

次に、重複を見つけます。

let duplicates = crossReference
    .filter { $1.count > 1 }                 // filter down to only those with multiple contacts
    .sorted { $0.1.count > $1.1.count }      // if you want, sort in descending order by number of duplicates

明らかに、あなたにとって意味のあるモデルタイプを使用してください。ただし、上記では次のContactタイプを使用します。

struct Contact {
    let name: String
    let phone: String
}

これを実装するには多くの方法がありますので、上記の実装の詳細に焦点を当てるのではなく、コンセプトに焦点を当てます:いくつかのキー(電話番号など)で相互参照元の配列を構築し、結果をフィルタリングします値が重複するキーのみ。


重複を反映するこの構造を1つの連絡先の配列にフラット化するように聞こえます(相互の重複を識別する構造が失われるため、なぜそうする必要があるのか​​わかりません)が、それをしたい場合は、flatMap it:

let flattenedDuplicates = crossReference
    .filter { $1.count > 1 }                 // filter down to only those with multiple contacts
    .flatMap { $0.1 }                        // flatten it down to just array of contacts that are duplicates of something else

Swift 2または3レンディションについては、 この回答の以前のレンディション を参照してください。

23
Rob

〜気分〜。 Intsの配列が与えられた場合

let x = [1, 1, 2, 3, 4, 5, 5]
let duplicates = Array(Set(x.filter({ (i: Int) in x.filter({ $0 == i }).count > 1})))
// [1, 5]

これは、コンパイラーを含むすべての関係者とあなたにとって恐ろしく効率的です。

私はただ誇示しています。

編集:誰かがこれをダウン票したので、念のため繰り返しますが、本番環境や他の場所でこれを使用しないでください。

42
Patrick Perini

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

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
    }
}

Robの連絡先の例に基づいて、次のように呼び出すことができます。

let filteredContacts = myContacts.filterDuplicates { $0.name == $1.name && $0.phone == $1.phone }
6
Antoine

"Merge sort" を使用して実装することもできますが、1つの変更を加える必要があります。マージ手順中に重複を無視する必要があります。

重複する要素を見つける最も簡単な方法は、電話番号が6桁の数字で、タイプがIntの場合、電話番号の配列をソートし、それをフィルタリングして重複を見つけることです。

var phoneNumbers = [123456, 234567, 345678, 123456, 456789, 135790, 456789, 142638]

func findDuplicates(sortedArray array: [Int]) -> [Int]
{
    var duplicates: [Int] = []

    var prevItem: Int = 0
    var addedItem: Int = 0

    for item in array
    {
        if(prevItem == item && addedItem != item)
        {
            duplicates.append(item)
            addedItem = item
        }

        prevItem = item
    }

    return duplicates
}

func sortPhoneNumbers(phoneNumbers: [Int]) -> [Int]
{
    return phoneNumbers.sorted({ return $0<$1 })
}

sortPhoneNumbers(phoneNumbers)
findDuplicates(sortPhoneNumbers(phoneNumbers))

さらに、findDuplicatesメソッドをさまざまな方法で実装できます。

セットの使用(Swift 1.2+):

func findDuplicates(array: [Int]) -> [Int]
{
    var duplicates = Set<Int>()
    var prevItem = 0       

    for item in array
    {
        if(prevItem == item)
        {
            duplicates.insert(item)
        }

        prevItem = item
    }

    return Array(duplicates)
}

等々。

5
tikhop

Rob's answer に基づいて、重複を見つけるための配列拡張は次のとおりです。

extension Array where Element: Hashable {
    func duplicates() -> Array {
        let groups = Dictionary(grouping: self, by: {$0})
        let duplicateGroups = groups.filter {$1.count > 1}
        let duplicates = Array(duplicateGroups.keys)
        return duplicates
    }
}
3
Benjohn

アントワーヌの解 inSwift 3 +構文

extension Array {

    func filterDuplicates(includeElement: @escaping (_ lhs: Element, _ rhs: Element) -> Bool) -> [Element] {

        var results = [Element]()

        forEach { (element) in

            let existingElements = results.filter {
                return includeElement(element, $0)
            }

            if existingElements.count == 0 {
                results.append(element)
            }
        }
        return results
    }
}
2
Maverick

スイフト4

2行の高速ソリューション:

var numbers = [1,2,3,4,5,6,6,6,7,8,8]
let dups = Dictionary(grouping: numbers, by: {$0}).filter { $1.count > 1 }.keys
1
Rob Caraway

@ tikhopの回答と同じですが、配列拡張(Swift 3)として:

extension Array where Element: Comparable & Hashable {

   public var duplicates: [Element] {

      let sortedElements = sorted { $0 < $1 }
      var duplicatedElements = Set<Element>()

      var previousElement: Element?
      for element in sortedElements {
         if previousElement == element {
            duplicatedElements.insert(element)
         }
         previousElement = element
      }

      return Array(duplicatedElements)
   }

}
1
Vlad

Reduceを使用する方法を見つけました。コードは次のとおりです(Swift 4):

let testNumbers = [1,1,2,3,4,5,2]
let nondupicate = testNumbers.reduce(into: [Int]()) {
    if !$0.contains($1) {
        $0.append($1)
    } else {
        print("Found dupicate: \($1)")
    }
}

副作用として、配列に不正な要素がないことを返します。

重複する要素の数を数えたり、文字列配列をチェックしたりするために簡単に変更できます。

1
Eric Yuan

私も同様の問題を抱えており、次の方法で克服しました。 (Xcode 8.3.2)

let a = [123456, 234567, 345678, 123456, 456789, 135790, 456789, 142638]
var b = a // copy-on-write so that "a" won't be modified

while let c = b.popLast() {
  b.forEach() {
    if $0 == c {
      Swift.print("Duplication: \(c)")
    }
  }
}

//  Duplication: 456789
//  Duplication: 123456

ポイントは、比較の回数ということです。他のものよりも小さいでしょう。

配列内のアイテムの数がNであると仮定します。各ループで、数は1ずつ減少します。したがって、合計数は(N-1)+(N-2)+(N-3)+ ... + 2 + 1 = N *(N-1)/ 2 N = 10の場合、 9 + 8 + ... = 45

対照的に、一部のアルゴリズムの値はN * Nになる場合があります。N= 10の場合は100になります。

それにもかかわらず、ディープコピーまたはシャローコピーのコストを考慮すると、@ Patrick Periniの優れた方法は、状況によってはこれがN * Nであってもこれよりも優れていることに同意します。

編集:

IteratorProtocolの代替方法

let a = [123456, 234567, 345678, 123456, 456789, 135790, 456789, 142638]
var i = a.makeIterator()

while let c = i.next() {
  var j = i
  while let d = j.next() {
    if c == d {
      Swift.print("Duplication: \(c)")
    }
  }
}

//  Duplication: 123456
//  Duplication: 456789

これはもっと複雑に見えますが、以前と同じ考え方を使用しています。これには、不必要なメモリの割り当てやコピーはありません。

私の懸念は効率です。つまり、UI応答の高速化、バッテリ寿命の延長、メモリフットプリントの縮小などです。背後でSwift競争力のある製品を提供する場合に重要です(-;

0
Tora

すべての重複を保持する非常に簡単な答え

let originalNums = [5, 3, 2, 3 , 7 , 5,3]
var nums = Array(originalNums)

let numSet = Set(nums)

for num in numSet {
  if let index = nums.index(of: num) {
     nums.remove(at: index)
  }
}

出力

[3, 5, 3]
0
Ryan Heitner