การลบองค์ประกอบที่ซ้ำกันออกจากอาร์เรย์ใน Swift


252

ทุกวันนี้ใน Swift คุณเพียงแค่พิมพ์Set( yourArray )เพื่อสร้างอาร์เรย์ที่ไม่เหมือนใคร (หรือชุดสั่งถ้าจำเป็น)

ก่อนหน้านั้นเป็นไปได้


ฉันอาจมีอาร์เรย์ที่มีลักษณะดังนี้:

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

หรือจริงๆแล้วลำดับของส่วนของข้อมูลที่พิมพ์เหมือนกัน สิ่งที่ฉันต้องการจะทำคือให้แน่ใจว่ามีองค์ประกอบที่เหมือนกันเพียงองค์ประกอบเดียวเท่านั้น ตัวอย่างเช่นอาร์เรย์ด้านบนจะกลายเป็น:

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

โปรดสังเกตว่าลบรายการที่ซ้ำกันของ 2, 6 และ 15 ออกเพื่อให้แน่ใจว่ามีองค์ประกอบที่เหมือนกันเพียงหนึ่งรายการเท่านั้น สวิฟท์มีวิธีการทำสิ่งนี้อย่างง่ายดายหรือไม่หรือฉันต้องทำเอง


11
วิธีที่ง่ายที่สุดคือการแปลงอาเรย์ในNSSetNSSet คือชุดของวัตถุที่ไม่เรียงลำดับหากจำเป็นต้องเรียงลำดับ NSOrderedSet
Andrea

คุณสามารถใช้ฟังก์ชั่นทางแยกตามที่คุณสามารถหาได้ในคลาสนี้ด้วยฟังก์ชั่นสำหรับอาร์เรย์: github.com/pNre/ExSwift/blob/master/ExSwift/Array.swift
Edwin Vermeer

ไม่ใช่ส่วนหนึ่งของ Swift แต่ฉันใช้ Dollar $.uniq(array) github.com/ankurp/Dollar#uniq---uniq
Andy

น่าจะเป็นที่สง่างามคำตอบที่ฉลาดและเร็วที่สุดคือการให้โดยคำตอบ mxcl ของด้านล่าง ซึ่งยังช่วยรักษาความสงบ
ฮันนี่

1
ทำไมคุณไม่ใช้Setจาก Swift? คุณจะสามารถให้รายการองค์ประกอบที่ไม่ได้เรียงลำดับและไม่ซ้ำกันได้
TibiaZ

คำตอบ:


133

คุณสามารถม้วนตัวเองเช่นนี้ ( อัปเดตสำหรับ Swift 1.2 พร้อมชุด ):

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

493

คุณสามารถแปลงเป็นชุดและกลับสู่อาร์เรย์ได้อย่างง่ายดาย:

let unique = Array(Set(originals))

นี่ไม่รับประกันว่าจะรักษาลำดับเดิมของอาร์เรย์


37
มีวิธีใช้ชุดในขณะที่รักษาลำดับเดิมของอาร์เรย์หรือไม่
Crashalot

6
@Crashalot ดูคำตอบของฉัน
Jean-Philippe Pellet

5
หากคุณต้องการเก็บวัตถุที่ไม่ซ้ำกันโดยคุณสมบัติที่เฉพาะเจาะจงกว่ายังใช้โปรโตคอล Hashable และ Equatable ในชั้นเรียนนั้นแทนที่จะใช้ Array-> Set-> การแปลง Array
Fawkes

2
มีความสุข !! ความซับซ้อนของเวลาของโซลูชันนี้คืออะไร
JW.ZG

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 ขอบคุณสำหรับการกรองตามส่วนขยายคุณสมบัติ มันมีประโยชน์จริงๆ แต่คุณสามารถอธิบายวิธีการทำงานของมันได้ไหม มันยากเกินไปที่จะเข้าใจสำหรับฉัน ขอบคุณ
Mostafa Mohamed Raafat

การอัปเดตสำหรับ 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)
Cœur

7
นี่จะมีO(n²)การแสดงเวลาซึ่งไม่ดีสำหรับอาร์เรย์ขนาดใหญ่
Duncan C

คุณควรใช้ชุดเพื่อติดตามองค์ประกอบที่เห็นเพื่อนำO(n²)ความซับซ้อนที่น่ากลัวนี้กลับไปที่O(n)
Alexander - Reinstate Monica

63

สวิฟท์ 3.0

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

1
ให้ uniqueOrderedNames = Array (NSOrderedSet (อาร์เรย์: ชื่อผู้ใช้)) เป็น! [String] ถ้าคุณมีอาเรย์ของ String ไม่ใช่ของใด ๆ
Zaporozhchenko Oleksandr

ล้มเหลวถ้าองค์ประกอบในarrayไม่ได้Hashable; เพียงHashableชนิดข้อมูลที่สามารถเพิ่มไปยังชุด แต่ชนิดของข้อมูลใด ๆ ที่สามารถเพิ่มไปยังอาร์เรย์
Mecki

ทดสอบใน Swift 5.1b5 เนื่องจากองค์ประกอบต่างๆนั้นเป็น Hashable และความปรารถนาที่จะรักษาการสั่งซื้อไว้นั้น NSOrderedSet (array: array) อาร์เรย์จะเร็วกว่าเล็กน้อยอย่างรวดเร็วโดยไม่ต้องใช้ชุดกรอง ฉันทดสอบกับสตริง 5100 ที่มีค่าไม่ซ้ำกัน 13 ค่า
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 initializerนี้ได้ตลอดเวลา


โอเคเข้าใจแล้ว ฉันไม่สามารถเรียกมันได้เพราะอาเรย์ของฉันเป็นอาเรย์ของ structs ... ฉันจะจัดการมันอย่างไรในกรณีของฉัน? โครงสร้างของ 20 ตัวแปร, สตริงและ [สตริง] ต่างกัน
David Seek

@ David Seek ดูเหมือนว่าคุณยังไม่ได้ทำการแฮชอย่างเข้มงวดหรือเท่าเทียมกัน ถูกต้องหรือไม่
Jessy

1
@DavidSeek เช่นนี้ uniqueArray = nonUniqueArray.uniqueElements
Mert Celik

ใช่ไม่ต้องกังวล ทำให้มันทำงานทันที เกือบ 2 ปีแล้ว: P
David Seek

นี่จะมี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"

สำหรับSwift 3คลิกที่นี่


1
ฉันชอบสิ่งนี้มันใช้ได้กับพจนานุกรมหลายประเภทเช่นกัน!
DeyaEldeen

6
O (N ^ 2) ไม่ดี :(
Alexander - Reinstate Monica

1
@Alexander Leo Dabus ได้เข้ามาแทนที่การreduceใช้งานดังนั้นตอนนี้ความซับซ้อนแตกต่าง
Cœur

1
ผลลัพธ์ที่น่าสนใจ สำหรับรายการที่ไม่ซ้ำกัน 1 ล้านรายการและ 8 ล้านรายการรุ่นตัวกรองจะเร็วกว่า อย่างไรก็ตามรุ่นที่ใช้ตัวกรองใช้เวลานานกว่า 8.38x สำหรับรายการที่ไม่ซ้ำกัน 8 ล้านรายการ ( O(n)เวลาผ่านไป) ซึ่งเวอร์ชันที่ใช้แบนแมปนั้นจะใช้เวลานานกว่า 7.47 เท่าสำหรับรายการที่ไม่ซ้ำกันกว่า 8 ล้านรายการกว่า 7.47x8 ล้านรายการ . ยังไงก็ตามเวอร์ชันพื้นฐานของ flatmap นั้นดีกว่าO(n)เวลาเล็กน้อย!
Duncan C

1
ที่จริงแล้วเมื่อฉันรันการทดสอบที่มีรายการ 64x ขึ้นในอาร์เรย์เวอร์ชันที่ใช้ flatmap นั้นจะเร็วกว่า
Duncan C

43

สวิฟต์ 4

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

ความพยายามที่จะทุกคนinsertก็จะกลับ tuple (inserted: Bool, memberAfterInsert: Set.Element)นี้: ดูเอกสารประกอบเอกสาร

การใช้ค่าที่ส่งคืนจะช่วยให้เราหลีกเลี่ยงการวนซ้ำหรือดำเนินการอื่นใด


7
หลังจากการทำโปรไฟล์อย่างง่ายวิธีนี้เร็วมาก มันเร็วกว่าร้อยเท่าโดยใช้การลด (_: _ :) หรือลดลง (เป็น: _ :)
เคลวิน

3
@Kelvin เพราะอัลกอริทึมอื่น ๆ ทั้งหมดนั้นเป็นO(n^2)และไม่มีใครสังเกตเห็น
Alexander - Reinstate Monica

@Kelvin คำตอบนี้เหมือนกับEneko Alonso คำตอบ + ความคิดเห็นของฉัน (16 มิ.ย. 17)
Cœur

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

ผมคิดว่าการแก้ปัญหานี้มีขนาดเล็ก แต่ผมเชื่อว่าการแก้ปัญหา deanWombourneโพสต์ในปีก่อนหน้านี้อาจจะเล็กน้อยมีประสิทธิภาพมากขึ้นกว่าreduce: โดยรวมก็เป็นเพียงหนึ่งในสายอื่น ๆ var unique: [Iterator.Element] = []; for element in self where !unique.contains(element) { unique.append(element) }; return uniqueในโครงการทั้งหมดของคุณเพื่อเขียนฟังก์ชั่นของคุณเป็น: ฉันยอมรับว่าฉันยังไม่ได้ทดสอบการแสดงที่เกี่ยวข้อง
Cœur

3
นี่จะมีO(n²)การแสดงเวลาซึ่งไม่ดีสำหรับอาร์เรย์ขนาดใหญ่
Duncan C

@NickGaens O(n²)ไม่มีก็ไม่ได้ก็ ไม่มีอะไรรวดเร็วเกี่ยวกับเรื่องนี้
Alexander - Reinstate Monica

@Cur reduceหรือreduce(into:)จะไม่สร้างความแตกต่างที่สำคัญ การเขียนซ้ำสิ่งนี้เพื่อไม่ให้โทรซ้ำcontainsจะทำให้เกิดความแตกต่างมากขึ้น
Alexander - Reinstate Monica

16

นี่คือหมวดหมู่SequenceTypeที่เก็บรักษาลำดับดั้งเดิมของอาร์เรย์ แต่ใช้ a Setเพื่อทำการcontainsค้นหาเพื่อหลีกเลี่ยงO(n)ค่าใช้จ่ายในcontains(_:)วิธีการของ Array

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: ==)
    }
}

สุดท้ายคุณสามารถเพิ่มเวอร์ชันของพา ธ คีย์ของแบบไม่ซ้ำดังนี้:

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ประเภทของลำดับของคุณ


Heyyy ในที่สุดก็มีคนO(n)แก้ปัญหา คุณสามารถรวมการดำเนินการ "ตรวจสอบ" และ "แทรก" เป็นหนึ่งโดยวิธีการ ดูstackoverflow.com/a/46354989/3141234
Alexander - Reinstate Monica

โอ้นั่นเป็นคนฉลาด :)
deanWombourne

15

แรงบันดาลใจจาก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 คุณสามารถมีส่วนขยายเพิ่มเติมนี้ได้ แต่ประเภทส่งคืนจะเป็น Array เสมอ:

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
ตอนนี้เป็นการใช้งานที่ดี! ฉันเท่านั้นที่มีเส้นทางที่สำคัญที่ถูกแปลงสภาพเพื่อปิดเพื่อให้คุณสามารถใช้ ARG ปิดเพื่อสนับสนุนทั้งรหัสโดยพลการ (ในการปิด) และเพียงคุณสมบัติการค้นหา (ผ่านเส้นทางที่สำคัญ) การเปลี่ยนแปลงเท่านั้นที่ฉันจะทำคือการkeyPathเริ่มต้น\.selfเพราะอาจเป็นกรณีการใช้งานส่วนใหญ่
Alexander - Reinstate Monica

1
@Alexander ฉันพยายามที่จะเริ่มต้นกับตัวเอง แต่แล้วฉันจะต้องทำให้เสมอElement Hashableทางเลือกในการค่าเริ่มต้นคือการเพิ่มการโอเวอร์โหลดง่ายโดยไม่มีพารามิเตอร์:extension Sequence where Element: Hashable { func unique() { ... } }
Cœur

อ่าใช่เข้าท่า!
Alexander - Reinstate Monica

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 & ทำงานในเวลากำลังสอง


8
ระวังไม่มีหนึ่ง แต่สองวิธีนี้ทำงานในเวลากำลังสอง - ทั้งสองcontainsและอาร์เรย์ต่อท้ายทำงานใน O (n) แม้ว่ามันจะมีประโยชน์เพียงแค่ต้องการความเท่าเทียม แต่ไม่แฮช
ความเร็วของเครื่องบิน

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และคุณจะได้ upvote ของฉัน
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
นี่เป็นคำตอบซ้ำหลายคำที่มีอยู่แล้วที่นี่และมันก็ไม่ได้เรียงลำดับ
Alexander - Reinstate Monica

9

อีกหนึ่งโซลูชัน Swift 3.0 เพื่อลบข้อมูลซ้ำออกจากอาร์เรย์ โซลูชันนี้ปรับปรุงโซลูชันอื่น ๆ ที่เสนอโดย:

  • รักษาลำดับขององค์ประกอบในอาร์เรย์อินพุต
  • ความซับซ้อนเชิงเส้น O (n): ตัวกรอง pass เดียว 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]

รหัสนี้ใช้ประโยชน์จากผลลัพธ์ที่ส่งคืนโดยการinsertดำเนินการSetซึ่งเปิดใช้งานO(1)และส่งคืน tuple ที่ระบุว่ารายการถูกแทรกหรือมีอยู่ในชุด

หากรายการอยู่ในชุดfilterจะแยกออกจากผลลัพธ์สุดท้าย


1
ไม่ต้องจู้จี้จุกจิก แต่คุณจะทำการทดสอบการแทรกและการทดสอบการเป็นสมาชิกหลายครั้งตามที่มีองค์ประกอบดังนั้นคุณควรนับค่าใช้จ่ายของพวกเขาเป็น O (n) เช่นกัน นี่ไม่ได้หมายถึง 3xO (n) อย่างไรก็ตามเนื่องจาก O เหล่านี้และไม่มีค่าใช้จ่ายเท่ากันกับตัวกรองดังนั้นการเพิ่ม O (n) จึงเป็นแอปเปิ้ลไปสู่ส้ม หากเราพิจารณาตั้งค่าการปฏิบัติงานให้เป็นส่วนหนึ่งของต้นทุนตัวกรอง O (1) ความซับซ้อนนั้นเป็นเพียง O (n) แม้ว่าจะมีขนาดใหญ่กว่า "O" คุณสามารถหลีกเลี่ยงการแทรกเมื่อองค์ประกอบมีอยู่แล้วในชุด
Alain T.

คุณมีสิทธิใช้deferรหัสจะดำเนินการทดสอบชุดสองครั้งหนึ่งและเป็นหนึ่งเดียวกับcontains insertอ่านเพิ่มเติมเอกสาร Swift ฉันพบว่าinsertคืน tuple ระบุว่าองค์ประกอบถูกแทรกหรือไม่ดังนั้นฉันได้ง่ายรหัสถอดcontainsเช็ค
Eneko Alonso

2
ดี ส่วนขยายของคุณอาจจะดีที่สุดด้วยการทำมันบนextension Sequence where Iterator.Element: Hashable { ... }
Cœur

@AlainT Nope ทั้งสองinsertและcontainsมีO(1)ความซับซ้อน O(1) + O(1) = O(1). การดำเนินการทั้งสองนี้เสร็จสิ้นแล้วnเวลา (หนึ่งครั้งต่อการเรียกของการปิดที่ผ่านไปfilterซึ่งถูกเรียกหนึ่งครั้งต่อองค์ประกอบ) คือถ้าการดำเนินการใช้เวลาเป็นจำนวนคงที่โดยไม่คำนึงถึงขนาดที่ใส่จากนั้นการทำสองครั้งก็ยังคงใช้เวลา ที่ไม่คำนึงถึงขนาดอินพุต O(n)ความซับซ้อนทั้งหมดนี้คือ
Alexander - Reinstate Monica

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)นี่คือ อย่าทำอย่างนี้
Alexander - Reinstate Monica

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คุณกำลังเข้าถึง "ประเภทหน่วย" (ประเภทที่มีค่าที่เป็นไปได้เพียงค่าเดียว) ประเภทหน่วยของ Swift คือVoidค่าที่มีเพียง()(หรือที่รู้จักคือ tuple ที่ว่างเปล่า) [T: Void]ดังนั้นคุณก็สามารถใช้ Setแม้ว่าคุณไม่ควรทำอย่างนั้นเพราะคุณได้เป็นเพียงการประดิษฐ์คิดค้น ใช้Setแทน ดูstackoverflow.com/a/55684308/3141234โปรดลบคำตอบนี้
Alexander - Reinstate Monica

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 ยังคงรักษาความสงบเรียบร้อยไว้และยังเร็วกว่ามาก ดังนั้นถ้าประเภทองค์ประกอบของคุณไม่แฮชหรือคุณแค่ต้องการซับอย่างรวดเร็วหนึ่งครั้งฉันก็ขอแนะนำให้ใช้วิธีการแก้ปัญหาของพวกเขา

นี่คือการทดสอบสองสามอย่างเกี่ยวกับ MacBook Pro (2014) บน Xcode 11.3.1 (Swift 5.1) ในโหมด Release

ฟังก์ชัน profiler และสองวิธีในการเปรียบเทียบ:

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
Porter Child

1
... และไม่เหมือนกับArray(Set(myArray))ลำดับของอาเรย์ของคุณ
Sander Saelmans

ดูเหมือนว่าคำตอบที่ดีที่สุดสำหรับฉันอย่างน้อยก็ในปัจจุบันเมื่อ Swift 5 เป็นรุ่นปัจจุบันอยู่แล้ว
oradyvan

นี่เป็นทางออกที่หรูหรามาก น่าเสียดายที่มันค่อนข้างช้า
โคลินสตาร์ค

1
@TimMB โอ้ฉันอ่านผิดโพสต์ของคุณ ฉันเห็นการปรับตัวของใครบางคนที่lastIndex(of:)ใช้ ฉันไม่เห็นด้วยกับความคมชัดเทียบกับจุดที่เหมาะสมที่สุดในกรณีนี้ ฉันไม่คิดว่าการติดตั้งนี้จะชัดเจนเป็นพิเศษโดยเฉพาะอย่างยิ่งเมื่อเทียบกับโซลูชันที่ใช้งานง่าย ไม่ว่าในกรณีใดรหัสดังกล่าวควรถูกแยกออกไปยังฟังก์ชันส่วนขยาย อัลกอริทึมนี้ไม่สามารถใช้งานได้ที่ขนาดอินพุตต่ำเช่นในหลักพันถึงหลักหมื่น ไม่ยากที่จะหาชุดข้อมูลดังกล่าวผู้คนสามารถมีหลายพันเพลงไฟล์ผู้ติดต่อ ฯลฯ
Alexander - Reinstate Monica

6

สำหรับอาร์เรย์ที่องค์ประกอบนั้นไม่ใช่ Hashable หรือ Comparable (เช่นวัตถุที่ซับซ้อนพจนานุกรมหรือ structs) ส่วนขยายนี้ให้วิธีการทั่วไปในการลบรายการที่ซ้ำกัน:

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

คุณไม่ต้องกังวลกับการสร้างค่า Hashable และอนุญาตให้คุณใช้ชุดค่าผสมของเขตข้อมูลที่แตกต่างกันเพื่อความเป็นเอกลักษณ์

หมายเหตุ: สำหรับแนวทางที่มีประสิทธิภาพยิ่งขึ้นโปรดดูวิธีแก้ปัญหาที่เสนอโดย 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}     
    }
}

ไวยากรณ์การโทรแตกต่างกันเล็กน้อยเนื่องจากการปิดรับพารามิเตอร์เพิ่มเติมที่มีฟังก์ชั่นการแฮชจำนวนตัวแปรค่า (ซึ่งจะต้อง Hashable ทีละรายการ)

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

นอกจากนี้ยังจะทำงานร่วมกับค่าความเป็นเอกลักษณ์เดียว (ใช้ $ 1 และละเว้น $ 0)

peopleArray = peopleArray.filterDuplicate{ $1.name } 

สิ่งนี้สามารถให้ผลลัพธ์แบบสุ่มขึ้นอยู่กับพฤติกรรมของ"\()"เนื่องจากอาจไม่ให้คุณค่าที่ไม่ซ้ำกับคุณอย่างที่Hashableควรจะเป็น ตัวอย่างเช่นหากองค์ประกอบของคุณสอดคล้องกับการPrintableกลับมาเหมือนเดิมทุกครั้งการdescriptionกรองของคุณจะล้มเหลว
C Junur

ตกลง การเลือกเขตข้อมูล (หรือสูตร) ​​ที่จะสร้างรูปแบบที่ไม่ซ้ำกันที่ต้องการจะต้องคำนึงถึงสิ่งนี้ สำหรับกรณีการใช้งานจำนวนมากสิ่งนี้นำเสนอโซลูชัน Ad-hoc แบบง่ายที่ไม่ต้องมีการแก้ไขคลาสหรือโครงสร้างขององค์ประกอบ
Alain T.

2
@AlainT อย่าทำแบบนี้จริงๆ จุดประสงค์ของสตริงไม่ใช่กลไกการสร้างคีย์แบบสลัมแบบเฉพาะกิจ เพียง จำกัดเพื่อความเป็นอยู่T Hashable
Alexander - Reinstate Monica

@Alexander ผมได้ใช้ความคิดนี้ในคำตอบใหม่: stackoverflow.com/a/55684308/1033581
Cœur

คำตอบที่สมบูรณ์แบบตามที่ฉันต้องการ ขอบคุณมาก.
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]

"จากนั้นคุณสามารถสั่งซื้ออาเรย์ของคุณตามที่คุณต้องการ" จะเป็นอย่างไรถ้าฉันต้องการการเรียงลำดับแบบเดิมจากอาเรย์เดิม? มันไม่ง่ายเลย
Alexander - Reinstate Monica

3

เวอร์ชั่นไวยากรณ์ที่กระชับยิ่งขึ้นเล็กน้อยของคำตอบ Swift 2ของDaniel Kromโดยใช้การปิดท้ายและชื่ออาร์กิวเมนต์แบบย่อซึ่งดูเหมือนจะเป็นไปตามคำตอบดั้งเดิมของ 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()) รหัสที่ถูกใส่เครื่องหมายแสดงความคิดเห็นแสดงให้เห็นถึงการขยายการตรวจสอบความเท่าเทียมกันซึ่งsomeOtherEquatablePropertyเป็นคุณสมบัติของEquatableประเภทอื่น (แต่อาจเป็นวิธีที่ส่งกลับ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คุณกำลังเข้าถึง "ประเภทหน่วย" (ประเภทที่มีค่าที่เป็นไปได้เพียงค่าเดียว) ประเภทหน่วยของ Swift คือVoidค่าที่มีเพียง()(หรือที่รู้จักคือ tuple ที่ว่างเปล่า) [T: Void]ดังนั้นคุณก็สามารถใช้ Setแม้ว่าคุณไม่ควรทำอย่างนั้นเพราะคุณได้เป็นเพียงการประดิษฐ์คิดค้น ใช้Setแทน ดูstackoverflow.com/a/55684308/3141234
Alexander - Reinstate Monica

3

ในกรณีที่คุณต้องการเรียงลำดับค่างานนี้ (Swift 4)

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


2
คุณสูญเสียองค์ประกอบตามลำดับในกรณีนี้
Shmidt

ไม่เลยนั่นคือสิ่งที่เกิด.sorted()ขึ้นในตอนท้าย ความนับถือ.
Mauricio Chirino

@MauricioChirino และถ้าอาร์เรย์เดิมของคุณคือ[2, 1, 1]อะไร? มันจะออกมา[1, 2]ที่ไม่ได้รับคำสั่ง: p
Alexander - Reinstate Monica

2
@MauricioChirino ไม่ฉันไม่ หากเป้าหมายคือการลบค่าที่ซ้ำกันออกจากลำดับในขณะที่รักษาลำดับที่องค์ประกอบปรากฏโดยไม่ซ้ำกันสิ่งนี้จะไม่ทำเช่นนั้น [2, 1, 1]ตัวอย่างที่เคาน์เตอร์ที่ชัดเจนมากคือ [2, 1]ปรากฏตัวครั้งแรกขององค์ประกอบที่ไม่ซ้ำกันในการสั่งซื้อคือ นั่นคือคำตอบที่ถูกต้อง แต่การใช้ (ถูกต้อง) อัลกอริทึมของคุณคุณจะได้รับ[1, 2]ซึ่งจะเรียง แต่ไม่ได้อยู่ในที่ถูกต้องเป็นต้นฉบับการสั่งซื้อ
Alexander - Reinstate Monica

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
แทนที่จะใช้ a Setพร้อมกับกำหนดเองDistinctWrapperคุณควรใช้Dictionaryจาก เมื่อคุณทำตามตรรกะนั้นในที่สุดคุณก็จะสามารถใช้ [ Dictionary.init(_:uniquingKeysWith:)] pastebin.com/w90pVe0p(https://developer.apple.com/documentation/…ซึ่งในตอนนี้สร้างไว้ในไลบรารีมาตรฐานลองดูว่านี่เป็นเรื่องง่ายเพียงใดpastebin.com/w90pVe0p
Alexander - Reinstate Monica

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คุณกำลังเข้าถึง "ประเภทหน่วย" (ประเภทที่มีค่าที่เป็นไปได้เพียงค่าเดียว) ประเภทหน่วยของ Swift คือVoidค่าที่มีเพียง()(หรือที่รู้จักคือ tuple ที่ว่างเปล่า) [T: Void]ดังนั้นคุณก็สามารถใช้ Setแม้ว่าคุณไม่ควรทำอย่างนั้นเพราะคุณได้เป็นเพียงการประดิษฐ์คิดค้น ใช้Setแทน ดูstackoverflow.com/a/55684308/3141234
Alexander - Reinstate Monica

2

นี่เป็นเพียงการใช้งานที่ง่ายและสะดวก คุณสมบัติที่คำนวณได้ในส่วนขยายของอาร์เรย์ที่มีองค์ประกอบที่เท่าเทียมกัน

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

1
O(n^2)และนี่ก็เป็น
Alexander - Reinstate Monica

2
func removeDublicate (ab: [Int]) -> [Int] {
var answer1:[Int] = []
for i in ab {
    if !answer1.contains(i) {
        answer1.append(i)
    }}
return answer1
}

การใช้งาน:

let f = removeDublicate(ab: [1,2,2])
print(f)

ฉันคิดว่านี่เป็นวิธีที่ง่ายที่สุด
Jack Rus

มันเก็บคำสั่งซื้อและมอบอาร์เรย์ที่คุณต้องการ
Jack Rus

O(n²)และนี่ก็เป็น
Alexander - Reinstate Monica

2
  1. ก่อนอื่นให้เพิ่มองค์ประกอบทั้งหมดของอาร์เรย์ไปยัง NSOrderedSet
  2. นี่จะเป็นการลบรายการที่ซ้ำกันทั้งหมดในอาร์เรย์ของคุณ
  3. แปลงชุดคำสั่งนี้เป็นอาร์เรย์อีกครั้ง

เสร็จสิ้น ....

ตัวอย่าง

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

นี่เป็นการดำเนินการแฮ็กสองรายการต่อองค์ประกอบซึ่งไม่จำเป็น insertส่งคืน tuple ที่บอกคุณว่าองค์ประกอบนั้นมีอยู่แล้วหรือถูกเพิ่มเป็นครั้งแรก stackoverflow.com/a/55684308/3141234โปรดลบคำตอบนี้
Alexander - Reinstate Monica

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

อย่างที่คุณเห็นอาร์เรย์ผลลัพธ์จะไม่อยู่ใน 'ลำดับ' เสมอไป หากคุณต้องการเรียงลำดับ / สั่งซื้อ Array ให้เพิ่มสิ่งนี้:

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)

ฉันสงสัยว่าการแสดงนี้เปรียบเทียบกับคำตอบที่ดีกว่าได้อย่างไรจากที่นี่ คุณเปรียบเทียบหรือยัง
Alexander - Reinstate Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.