Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift Cannot invoke 'find' with an argument list of type '([Score], Score)' where Score is a struct

While find(["a", "b"], "c") works with no problems, I get an error when trying to find the index of a structure inside an array of structures:

struct Score
{
    //...
}

var scores: [Score] = //...
var score: Score = //...

find(self.scores, score) // Error: Cannot invoke 'find' with an argument list of type '([Score], Score)'

I though it could be a problem with structures that can't be compared to each other by default. But changing Scores definition to class gives me the same error.

like image 453
Rivera Avatar asked Apr 18 '15 13:04

Rivera


Video Answer


3 Answers

EDIT: as of Swift 2.0, there is now a built-in version of find that takes a closure so you don’t have to write your own – but also, find has been renamed indexOf, and is now a protocol extension of CollectionType, so you call it like a method:

// if you make `Score` conform to `Equatable:
if let idx = self.scores.indexOf(score) {

}

// or if you don't make it Equatable, you can just use a closure:
// (see original answer below for why you might prefer to do this)
if let idx = scores.indexOf({$0.scoreval == 3}) {

}

Original pre-2.0 answer below


While the answers suggesting making your class Equatable may work nicely, I'd recommend a bit of caution before choosing to do this. The reason being that, as the docs state, equatability implies substitutability, and your == operator must be reflexive, symmetric and transitive. If you don't adhere to this, you might get some very weird behavior when using algorithms like equals, sort etc. Be especially cautious if implementing Equatable on non-final classes. If you're sure you can meet requirements, go for it, and find will work.

If not, an alternative you could consider is writing a function that ought to be in the standard library but isn't, which is a find that takes a closure:

func find<C: CollectionType>(source: C, match: C.Generator.Element -> Bool) -> C.Index {
    for idx in indices(source) {
        if match(source[idx]) { return idx }
    }
    return nil
}

Once you have this, you can supply any matching criteria you prefer. For example, if your objects are classes you could use reference equality:

let idx = find(scores) { $0 === $1 }
like image 91
Airspeed Velocity Avatar answered Nov 25 '22 17:11

Airspeed Velocity


The interface for the function find is/was:

func find<C : CollectionType where C.Generator.Element : Equatable>(domain: C,
    value: C.Generator.Element) -> C.Index?

This says that the CollectionType of C must have elements that are Equatable and, furthermore, that the value must also be Equatable.

[Note Swift 3.0: As of Swift 3.0, you'll need to use the index function which comes in two variations. In the first, you'll supply your own predicate:

func index(where: (Self.Generator.Element) -> Bool) -> Self.Index?

In the second, your elements need to be equatable:

// Where Generator.Element : Equatable
func index(of: Self.Generator.Element) -> Self.Index?

If you decide to go the equatable route, then the following applies. Note End]

Your Score struct is not Equatable and hence the error. You'll need to figure out what it means for scores to be equal to one another. Maybe it is some numerical 'score'; maybe it is a 'score' and a 'user id'. It depends on your Score abstraction. Once you know, you implement == using:

func == (lhs:Score, rhs:Score) -> Bool {
 return // condition for what it means to be equal
}

Note: if you use class and thus scores have 'identity' then you can implement this as:

func == (lhs:Score, rhs:Score) -> Bool { return lhs === rhs }

Your example with strings works because String is Equatable. If you look in the Swift library code you'll see:

extension String : Equatable {}
func ==(lhs: String, rhs: String) -> Bool
like image 39
GoZoner Avatar answered Nov 25 '22 17:11

GoZoner


As the others have said, the objects you search for must conform to the Equatable protocol.

So you need to add an extension to your Score struct that tells the compiler that it conforms to that protocol:

extension Score: Equatable {}

And then you need to implement the == function for that class:

public func ==(lhs: Score, rhs: Score) -> Bool
{
  return lhs.whatever == rhs.whatever //replace with code for your struct.
}
like image 31
Duncan C Avatar answered Nov 25 '22 19:11

Duncan C