57

I have two arrays:

fruitsArray = ["apple", "mango", "blueberry", "orange"]
vegArray = ["tomato", "potato", "mango", "blueberry"]

How can I get the list of common items in those two array which gives

ouptput = ["mango", "blueberry"]

I can't use if contains(array, string) as I want to compare 2 arrays.

Dávid Pásztor
  • 51,403
  • 9
  • 85
  • 116
AAA
  • 1,957
  • 4
  • 23
  • 42
  • duplicate of - http://stackoverflow.com/questions/12173903/how-to-intersect-two-arrays-in-objective-c – Rajat Sep 07 '15 at 12:57
  • possible duplicate of [How to compare 2 nsarray](http://stackoverflow.com/questions/13815097/how-to-compare-2-nsarray) – Anbu.Karthik Sep 07 '15 at 12:57
  • 1
    Yes my question is similar to first link. But I want in swift – AAA Sep 07 '15 at 13:01
  • 3
    possible duplicate of [Set operations (union, intersection) on Swift array?](http://stackoverflow.com/questions/24589181/set-operations-union-intersection-on-swift-array) – Larme Sep 07 '15 at 13:03
  • I think this answer can help you http://stackoverflow.com/questions/6023548/finding-intersection-of-nsmutablearrays – jogshardik Sep 07 '15 at 13:26
  • please help me here http://stackoverflow.com/questions/41448605/ios-union-of-multiple-arrays-of-object-in-swift-3-without-using-sets – rd_ Jan 05 '17 at 05:06

7 Answers7

121

You can also use filter and contains in conjunction:

let fruitsArray = ["apple", "mango", "blueberry", "orange"]
let vegArray = ["tomato", "potato", "mango", "blueberry"]

// only Swift 1
let output = fruitsArray.filter{ contains(vegArray, $0) }

// in Swift 2 and above
let output = fruitsArray.filter{ vegArray.contains($0) }
// or
let output = fruitsArray.filter(vegArray.contains)

Set vs Array for a single computation of common elements

We consider the following code snippet:

let array1: Array = ...
let array2: Array = ...

// `Array`
let commonElements = array1.filter(array2.contains)

// vs `Set`
let commonElements = Array(Set(array1).intersection(Set(array2)))
// or (performance wise equivalent)
let commonElements: Array = Set(array1).filter(Set(array2).contains)

I have made some (artificial) benchmarks with Int and short/long Strings (10 to 100 Characters) (all randomly generated). I always use array1.count == array2.count

I get the following results:

If you have more than critical #(number of) elements converting to a Set is preferable

data         |  critical #elements
-------------|--------------------
         Int |        ~50
short String |       ~100
 long String |       ~200

Explanation of the results

Using the Array approach uses "Brute force"-search which has time complexity O(N^2) where N = array1.count = array2.count which is in contrast to the Set approach O(N). However the conversion from Array to Set and back is very expensive for large data which explains the increase of critical #elements for bigger data types.


Conclusion

For small Arrays with about 100 elements the Array approach is fine but for larger ones you should use the Set approach.

If you want to use this "common elements"-operation multiple times it is advisable to use Sets only if possible (the type of the elements has to be Hashable).

Final Remarks

A conversion from Array to Set is kind of expensive while the conversion from Set to Array is in contrast very inexpensive.

Using filter with .filter(array1.contains) is performance wise faster than .filter{ array1.contains($0) } since:

  • the last one creates a new closure (only once) whereas the first one passes only a function pointer
  • for the last one the call of the closure creates an additional stack frame which costs space and time (multiple times: O(N))
Qbyte
  • 12,753
  • 4
  • 41
  • 57
  • 2
    Whenever applicable, @Mousavian's `Set`-based answer is better, but this is good in the rare circumstance when the type is `Equatable` but not `Hashable`. – Alexander Dec 10 '17 at 00:03
33

Convert them to Set and use intersect() function:

let fruitsArray = ["apple", "mango", "blueberry", "orange"]
let vegArray = ["tomato", "potato", "mango", "blueberry"]
let fruitsSet = Set(fruitsArray)
let vegSet = Set(vegArray)
let output = Array(fruitsSet.intersection(vegSet))
ScottyBlades
  • 12,189
  • 5
  • 77
  • 85
Mousavian
  • 1,435
  • 1
  • 15
  • 23
  • 3
    This should really be the accepted answer. BTW you can intersect a `Set` with any collection (without a loss of performance), so you don't need to generate `vegSet` (which is O(n)` in itself) – Alexander Dec 10 '17 at 00:01
  • Thank you Alexander. I have Set implementations as an extension on Array, e.g.: extension Array where Element: Hashable { public func isSubset(of other: [Element]) -> Bool { let selfSet = Set(self) return selfSet.isSubset(of: other) } } – Giles Feb 28 '18 at 12:05
15

You don't need a Set (as the comments above have mentioned).

You could instead use a generic function, similar to the one Apple use in their Swift Tour, and thus avoid casting:

func anyCommonElements <T, U where T: SequenceType, U: SequenceType, T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, rhs: U) -> Bool {
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}

This function can take any two arrays (SequenceTypes) and if any of their elements are the same it returns true.

You could simply modify this generic function to package up an array of strings and return that instead.

For example like this:

func arrayOfCommonElements <T, U where T: SequenceType, U: SequenceType, T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, rhs: U) -> [T.Generator.Element] {
    var returnArray:[T.Generator.Element] = []
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                returnArray.append(lhsItem)
            }
        }
    }
    return returnArray
}

Usage like this:

var one = ["test2", "dog", "cat"]
var other = ["test2", "cat", "dog"]


var result = arrayOfCommonElements(one,other)

print(result) //prints [test2, dog, cat]

The added benefit here is that this function also works with all same typed arrays. So later if you need to compare two [myCustomObject] arrays, once they both conform to equatable, you're all set! (pun intended)

Edit: (For non common elements) you could do something like this

func arrayOfNonCommonElements <T, U where T: SequenceType, U: SequenceType, T.Generator.Element: Equatable, T.Generator.Element == U.Generator.Element> (lhs: T, rhs: U) -> [T.Generator.Element] {

    var returnArray:[T.Generator.Element] = []
    var found = false

    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                found = true
                break
            }
        }

        if (!found){
            returnArray.append(lhsItem)
        }

        found = false
    }
    for rhsItem in rhs {
        for lhsItem in lhs {
            if rhsItem == lhsItem {
                found = true
                break
            }
        }

        if (!found){
            returnArray.append(rhsItem)
        }

        found = false
    }
    return returnArray
}

This implementation is ugly though.

Woodstock
  • 22,184
  • 15
  • 80
  • 118
  • Hi, this is working for me... Can you please tell me how to get the non-common elements of the `fruitsArray` ? – AAA Sep 10 '15 at 12:13
  • What is the drawback in using Set? – AAA Sep 13 '15 at 09:24
  • 1
    @AAA there is no drawback to using sets or even other high level functions like *filter* and *contains*. However, since you're starting with Arrays I wanted to show you that you didn't have to change their type to find common elements. Furthermore, its always good to understand the fundamentals, Sets will (most likely) in their implementation use something like the above (although it will be more elegant I'm quite sure). – Woodstock Sep 13 '15 at 09:39
  • 1
    The drawback to using `Set` is that your items must be `Hashable` where basing the solution on `Array` only requires that they be `Equatable` – David Berry Mar 16 '16 at 16:25
  • 1
    You should offer the Set-based, O(n) solutions as default, and offer these crappy (O(N^2)) array-based solutions only as a backup for the very niche case of equatable but not hashable items. – Alexander Dec 09 '17 at 23:33
5

A generic method, inspired by The Swift Programming Language (Swift 3) exercise:

func commonElements<T: Sequence, U: Sequence>(_ lhs: T, _ rhs: U) -> [T.Iterator.Element]
    where T.Iterator.Element: Equatable, T.Iterator.Element == U.Iterator.Element {
        var common: [T.Iterator.Element] = []

        for lhsItem in lhs {
            for rhsItem in rhs {
                if lhsItem == rhsItem {
                    common.append(lhsItem)
                }
            }
        }
        return common
}

Then, use it like this:

var a = [3,88,74]
var b = [1,3,88]

print("commons: \(commonElements(a, b))")

--> commons: [3, 88]
Nicolas Buquet
  • 3,880
  • 28
  • 28
4

The following works with swift 4:

   let fruitsArray = ["apple", "mango", "blueberry", "orange"]
   let vegArray = ["tomato", "potato", "mango", "blueberry"]

   var someHash: [String: Bool] = [:]

   fruitsArray.forEach { someHash[$0] = true }

   var commonItems = [String]()

   vegArray.forEach { veg in
    if someHash[veg] ?? false {
        commonItems.append(veg)
    }
   }

   print(commonItems)
Mehul Parmar
  • 3,599
  • 3
  • 26
  • 42
4

The following works with swift 4,5

let fruitsArray = ["apple", "mango", "blueberry", "orange"]
let vegArray = ["tomato", "potato", "mango", "blueberry"]
     
//You can using filter function
let commonArray = fruitsArray.filter { fruit in
    vegArray.contains(fruit)
}

print(commonArray)
    

Output: ["mango", "blueberry"]

stackich
  • 3,607
  • 3
  • 17
  • 41
Priyank Patel
  • 791
  • 8
  • 6
1

Using Set and also intersection as follows:

func findIntersection (firstArray : [Int], secondArray : [Int]) -> [Int]
{
    return [Int](Set<Int>(firstArray).intersection(secondArray))
}

print (findIntersection(firstArray: [2,3,4,5], secondArray: [1,2,3]))
casillas
  • 16,351
  • 19
  • 115
  • 215