How do I check if an array of tuples contains a particular one in Swift?
Asked Answered
P

6

11

Consider the following Swift code.

var a = [(1, 1)]

if contains(a, (1, 2)) {
    println("Yes")
}

All I need is to check if a contains the tuple but the code leads to error.

Cannot find an overload for 'contains' that accepts an argument list of type '([(Int, Int)], (Int, Int))'

Why so and how to use contains properly?

Paletot answered 19/4, 2015 at 21:42 Comment(0)
B
9

Add the following to your code:

func contains(a:[(Int, Int)], v:(Int,Int)) -> Bool {
  let (c1, c2) = v
  for (v1, v2) in a { if v1 == c1 && v2 == c2 { return true } }
  return false
}

Swift is not that flexible when it comes to tuples. They do not conform to the Equatable protocol. So you must define that or use the above function.

Bootblack answered 19/4, 2015 at 21:49 Comment(2)
It sounds very odd that tuples are not Equatable and one cannot use == to check if two tuples are equal.Paletot
@Jay Tuples as a whole could not be Equatable, since they might contain inequatable types. Same goes for optionals and arrays, which can (and do) also have an == but aren’t Equatable. Tuples have the added complication that there’s no way to write generic code that works for 2-tuples, 3-tuples, n-tuples etc., hence no default == defined for them.Imbecile
C
14

You can use a predicate and check for equality:

let tuples = [(1, 1), (0, 1)]

let tuple1 = (1, 2)
let tuple2 = (0, 1)

if tuples.contains(where: {$0 == tuple1}) {
    print(true)
} else {
    print(false)    // false
}

if tuples.contains(where: {$0 == tuple2}) {
    print(true)    // true
} else {
    print(false)
}

You can also create your own contains methods that takes generic tuples:

extension Sequence  {
    func contains<T, U>(_ tuple: (T, U)) -> Bool where T: Equatable, U: Equatable, Element == (T,U) {
        contains { $0 == tuple }
    }
    func contains<T, U, V>(_ tuple: (T, U, V)) -> Bool where T: Equatable, U: Equatable, V: Equatable, Element == (T,U,V) {
        contains { $0 == tuple }
    }
    func contains<T, U, V, W>(_ tuple: (T, U, V, W)) -> Bool where T: Equatable, U: Equatable, V: Equatable, W: Equatable,Element == (T, U, V, W) {
        contains { $0 == tuple }
    }
    func contains<T, U, V, W, X>(_ tuple: (T, U, V, W, X)) -> Bool where T: Equatable, U: Equatable, V: Equatable, W: Equatable, X: Equatable, Element == (T, U, V, W, X) {
        contains { $0 == tuple }
    }
    func contains<T, U, V, W, X, Y>(_ tuple: (T, U, V, W, X, Y)) -> Bool where T: Equatable, U: Equatable, V: Equatable, W: Equatable, X: Equatable, Y: Equatable, Element == (T, U, V, W, X, Y) {
        contains { $0 == tuple }
    }
}

if tuples.contains(tuple1) {
    print(true)
} else {
    print(false)    // false
}

if tuples.contains(tuple2) {
    print(true)    // true
} else {
    print(false)
}
Compensatory answered 19/4, 2015 at 21:57 Comment(3)
I like functional programming but I treat it with a particle of suspicion as it might worsen performance due to creation of additional collection. So I usually prefer just to iterate.Paletot
This looks elegant. One drawback would be that for (extremely) large arrays it has to go through the whole array to finally find out that that first already matched which would be a waste of time....Bootblack
@JayForeman as long as the objects in the collection are immutable, you don't have performance impact. Swift is smart enough not to create new collections if it sees that the contained objects are immutable.Babs
I
11

While tuples aren’t Equatable, you do not need to go so far as writing your own version of contains, since there is a version of contains that takes a matching predicate:

if contains(a, { $0.0 == 1 && $0.1 == 2 }) {
     // a contained (1,2)
}

While you can’t extend tuples to be equatable, you can write a version of == for tuples, which would make the above code simpler:

func ==<T: Equatable, U: Equatable>(lhs: (T,U), rhs: (T,U)) -> Bool {
    return lhs.0 == rhs.0 && lhs.1 == rhs.1
}

contains(a) { $0 == (1,2) } // returns true

It’d be nice to be able to write a version of contains for tuples, but alas, I don’t think the placeholder syntax supports it:

EDIT: as of Swift 1.2, this does now compile as you can use tuples in placeholder constraints

func contains
  <S: SequenceType, T: Equatable, U: Equatable where S.Generator.Element == (T,U)>
  (seq: S, x: (T,U)) -> Bool {
    return contains(seq) { $0.0 == x.0 && $0.1 == x.1 }
}

let a = [(1,1), (1,2)]

if contains(a, (1,2)) {
    println("Yes")
}
Imbecile answered 19/4, 2015 at 22:18 Comment(2)
@AirspeedVelocity - Can you please post swift 3.0 compatible version of "func contains <S: SequenceType ... ". We are using this function in earlier versions of Swift.Jeffreys
@AirspeedVelocity - Please check if possible the following code is correct conversion in Swift 3.0 ... Start ... func TupleContains <S: Sequence, T: Equatable, U: Equatable> (seq: S, x: (T,U)) -> Bool where S.Iterator.Element == (T,U) { return seq.contains(where: { $0.0 == x.0 && $0.1 == x.1 }) }Jeffreys
B
9

Add the following to your code:

func contains(a:[(Int, Int)], v:(Int,Int)) -> Bool {
  let (c1, c2) = v
  for (v1, v2) in a { if v1 == c1 && v2 == c2 { return true } }
  return false
}

Swift is not that flexible when it comes to tuples. They do not conform to the Equatable protocol. So you must define that or use the above function.

Bootblack answered 19/4, 2015 at 21:49 Comment(2)
It sounds very odd that tuples are not Equatable and one cannot use == to check if two tuples are equal.Paletot
@Jay Tuples as a whole could not be Equatable, since they might contain inequatable types. Same goes for optionals and arrays, which can (and do) also have an == but aren’t Equatable. Tuples have the added complication that there’s no way to write generic code that works for 2-tuples, 3-tuples, n-tuples etc., hence no default == defined for them.Imbecile
G
2

You can't use the contains method for your problem. Also there is no embedded solution in Swift. So you need to solve that by yourself. You can create a simple function to check if a tuple in your array is the same as your tuple to check:

func checkTuple(tupleToCheck:(Int, Int), theTupleArray:[(Int, Int)]) -> Bool{
    //Iterate over your Array of tuples
    for arrayObject in theTupleArray{
        //If a tuple is the same as your tuple to check, it returns true and ends
        if arrayObject.0 == tupleToCheck.1 && arrayObject.1 == tupleToCheck.1 {
            return true
        }
    }

    //If no tuple matches, it returns false
    return false
}
Grecism answered 19/4, 2015 at 21:51 Comment(0)
R
1

Maybe too old for this question hope someone will get help with more option.

You may use switch instead of if condition

    var somePoint = [(0, 1), (1, 0), (0, 0), (-2, 2)]
    for innerSomePoint in somePoint {
        switch innerSomePoint {
        case (0, 0):
            print("\(innerSomePoint) first and second static")
        case (_, 0):
            print("\(innerSomePoint) first dynamic second static")
        case (0, _):
            print("\(innerSomePoint) first static second dynamic")
        case (-2...2, -2...2):
            print("\(innerSomePoint) both in between values")
        default:
            print("\(innerSomePoint) Nothing found")
        }
    }

Also have some more option to do check here from apple doc

    somePoint = [(1, 1), (1, -1), (0, 0), (-2, 2)]
    for innerSomePoint in somePoint {
        switch innerSomePoint {
        case let (x, y) where x == y:
            print("(\(x), \(y)) is on the line x == y")
        case let (x, y) where x == -y:
            print("(\(x), \(y)) is on the line x == -y")
        case let (x, y):
            print("(\(x), \(y)) is just some arbitrary point")
        }
    }
Raceway answered 25/5, 2017 at 14:43 Comment(0)
R
1

Swift 4

Change your code to:

var a = [(1, 1)]

if a.contains(where: { $0 == (1, 2) } ) {
    print("Yes")
}
Resplendent answered 4/3, 2020 at 23:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.