18

Basically I need a version of appendContentsOf: which does not append duplicate elements.

Example

var a = [1, 2, 3]
let b = [3, 4, 5]

a.mergeElements(b)
//gives a = [1, 2, 3, 4, 5] //order does not matter
prad
  • 1,086
  • 1
  • 11
  • 19

7 Answers7

37

Simply :

let unique = Array(Set(a + b))
Oleg Gordiichuk
  • 15,240
  • 7
  • 60
  • 100
  • 1
    Altho this is elegant , just a thought :- `let unique = Array(Set(a + b)).sort()` would look better... – Dravidian Aug 31 '16 at 12:57
  • 9
    This, however, doesn't maintain the order of `a`'s elements while adding `b`'s elements to the end of it, which one could argue is the expected behavior. The order is not maintained due to the use of a `Set`. – George Marmaridis Sep 10 '18 at 10:33
  • 1
    did you meant `let unique = Array(Set(a + b)).sorted()` ? – Shaybc Oct 04 '21 at 23:07
11

This is commonly called a union, which is possible in Swift using a Set:

let a = [1, 2, 3]
let b = [3, 4, 5]

let set = Set(a)
let union = set.union(b)

Then you can just convert the set into an array:

let result = Array(union)
Graham
  • 7,431
  • 18
  • 59
  • 84
Bryan
  • 14,756
  • 10
  • 70
  • 125
  • 3
    Although the idea is clear, your example does not compile – numbers are not strings. Also `union` *returns* a new set and leaves the receiver unchanged. – Martin R Aug 31 '16 at 13:07
  • @MartinR Ah, you are correct. That's what I get for a quick copy/paste, fixed. – Bryan Aug 31 '16 at 13:10
11

Swift 5 Updated

In case you need to combine multiple arrays.

func combine<T>(_ arrays: Array<T>?...) -> Set<T> {
    return arrays.compactMap{$0}.compactMap{Set($0)}.reduce(Set<T>()){$0.union($1)}
}

Usage examples:

1.

    let stringArray1 = ["blue", "red", "green"]
    let stringArray2 = ["white", "blue", "black"]
    
    let combinedStringSet = combine(stringArray1, stringArray2)

    // Result: {"green", "blue", "red", "black", "white"}
    let numericArray1 = [1, 3, 5, 7]
    let numericArray2 = [2, 4, 6, 7, 8]
    let numericArray3 = [2, 9, 6, 10, 8]
    let numericArray4: Array<Int>? = nil
    
    let combinedNumericArray = Array(combine(numericArray1, numericArray2, numericArray3, numericArray4)).sorted()

    // Result: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Alexey Chekanov
  • 1,047
  • 14
  • 25
5

Swift 4.0 Version

extension Array where Element : Equatable {

  public mutating func mergeElements<C : Collection>(newElements: C) where C.Iterator.Element == Element{
    let filteredList = newElements.filter({!self.contains($0)})
    self.append(contentsOf: filteredList)
  }

}

As mentioned: The array passed to the function is the array of object that will be omitted from the final array

Kyle Goslan
  • 10,748
  • 7
  • 26
  • 41
3

Swift 3.0 version of the accepted answer.

extension Array where Element : Equatable{

  public mutating func mergeElements<C : Collection>(newElements: C) where C.Generator.Element == Element{
    let filteredList = newElements.filter({!self.contains($0)})
    self.append(contentsOf: filteredList)
  }

}

Note: Worth saying here that the array passed to the function is the array of object that will be omitted from the final array. Important if your merging an array of objects where the Equatable property may be the same but others may differ.

Kyle Goslan
  • 10,748
  • 7
  • 26
  • 41
1

An Array extension can be created to do this.

extension Array where Element : Equatable{

    public mutating func mergeElements<C : CollectionType where C.Generator.Element == Element>(newElements: C){
       let filteredList = newElements.filter({!self.contains($0)})
       self.appendContentsOf(filteredList)
   }
}

Of course, this is useful for only Equatable elements.

prad
  • 1,086
  • 1
  • 11
  • 19
0

I combined my extension of Sequence and Array with this answer to provide easy syntax when merging arrays with custom objects by a single property:

extension Dictionary {
    init<S>(_ values: S, uniquelyKeyedBy keyPath: KeyPath<S.Element, Key>) where S : Sequence, S.Element == Value {
        let keys = values.map { $0[keyPath: keyPath] }

        self.init(uniqueKeysWithValues: zip(keys, values))
    }
}

// Unordered example
extension Sequence {
    func merge<T: Sequence, U: Hashable>(mergeWith: T, uniquelyKeyedBy: KeyPath<T.Element, U>) -> [Element] where T.Element == Element {
        let dictOld = Dictionary(self, uniquelyKeyedBy: uniquelyKeyedBy)
        let dictNew = Dictionary(mergeWith, uniquelyKeyedBy: uniquelyKeyedBy)

        return dictNew.merging(dictOld, uniquingKeysWith: { old, new in old }).map { $0.value }
    }
}

// Ordered example
extension Array {
    mutating func mergeWithOrdering<U: Hashable>(mergeWith: Array, uniquelyKeyedBy: KeyPath<Array.Element, U>) {
        let dictNew = Dictionary(mergeWith, uniquelyKeyedBy: uniquelyKeyedBy)

        for (key, value) in dictNew {
            guard let index = firstIndex(where: { $0[keyPath: uniquelyKeyedBy] == key }) else {
                append(value)
                continue
            }

            self[index] = value
        }
    }
}

Test:

@testable import // Your project name
import XCTest

struct SomeStruct: Hashable {
    let id: Int
    let name: String
}

class MergeTest: XCTestCase {
    let someStruct1 = SomeStruct(id: 1, name: "1")
    let someStruct2 = SomeStruct(id: 2, name: "2")
    let someStruct3 = SomeStruct(id: 2, name: "3")
    let someStruct4 = SomeStruct(id: 4, name: "4")

    var arrayA: [SomeStruct]!
    var arrayB: [SomeStruct]!

    override func setUp() {
        arrayA = [someStruct1, someStruct2]
        arrayB = [someStruct3, someStruct4]
    }

    func testMerging() {
        arrayA = arrayA.merge(mergeWith: arrayB, uniquelyKeyedBy: \.id)

        XCTAssert(arrayA.count == 3)
        XCTAssert(arrayA.contains(someStruct1))
        XCTAssert(arrayA.contains(someStruct3))
        XCTAssert(arrayA.contains(someStruct4))
    }

    func testMergingWithOrdering() {
        arrayA.mergeWithOrdering(mergeWith: arrayB, uniquelyKeyedBy: \.id)

        XCTAssert(arrayA.count == 3)
        XCTAssert(arrayA[0] == someStruct1)
        XCTAssert(arrayA[1] == someStruct3)
        XCTAssert(arrayA[2] == someStruct4)
    }
}
J. Doe
  • 12,159
  • 9
  • 60
  • 114