Gold Challenge...is using the AnyCollection type correct?

I ran into an error after trying a few things, which I comment in my code:

func findAllCollection<T: Equatable>(_ stuff: [T],_ searchTerm: T) -> [AnyCollection<Any>] {
    var items = [AnyCollection<Any>]()
    for i in 0..<stuff.count {
        if stuff[i] == searchTerm {
            items.append(i) // error Cannot convert value of type 'Int' to expected argument type 'AnyCollection<Any>'
            // items.append(AnyCollection<Any>(i)) // I also tried this instead...error here too: No exact matches in call to initializer
        }
    }
    return items
}

Is AnyCollection the appropriate type to use?

1 Like

I used Collection instead of AnyCollection; I’m not sure if one is better than the other for this. I will say you’re using it wrong, though. The idea here is that stuff should be a collection instead of an array, so that the function will work with different kinds of collections. And while the function no longer returns an array of Integers, it does not return an array of Collections either.

Edit: I take that back, I’m not sure you want to be using AnyCollection here. I tried modifying my solution to use AnyCollection & while I was able to convert an array to an AnyCollection to pass into the function, I got some rather strange results back.

OK, the strange results are because Swift (or at least the playground) apparently doesn’t have a way to print the results in a meaningful way when the collection is not an Array. I tried my original solution with a Set instead of an Array and had the same problem. I was able to use the returned value in the code to index into the collection, so it does appear that my solutions work (both the Collection version and the AnyCollection version), you just can’t directly print the results the way you could with the Silver challenge.

Was this your declaration to start?

func findAllCollection<T: Equatable>(_ stuff: Collection,_ searchTerm: T) -> Collection {

I’m getting an error from this:

Protocol 'Collection' can only be used as a generic constraint because it has Self or associated type requirements

It doesn’t like it when I add a either.

When I wrote a version using AnyCollection, it looked very much like that. You just need to specify that the collection is a collection of items of type T:

func findAllGold2<T: Equatable>(_ input: AnyCollection<T>, _ searchTerm: T) -> // (you still need to work on the return type)

But with Collection, the declaration looks completely different. Since Collection is a protocol like Equatable is, I ended up having to put that inside the angle brackets as a type constraint on the first argument:

func findAllGold<TC: Collection>(_ input: TC,

but then that changed the way I declared searchTerm, and also changed how I set up the Equatable constraint on the items in the collection.

I tried Set as my return type and got this to build, but I don’t know if it’s correct…

func findAllCollection<T: Equatable>(_ stuff: AnyCollection<T>,_ searchTerm: T) -> Set<T> {
    var items = Set<T>()
    for i in 0..<stuff.count {
        if stuff as! T == searchTerm {
            items.insert(i as! T) // error Cannot convert value of type 'Int' to expected argument typ 'AnyCollection<Any>'
            // items.append(AnyCollection<Any>(i)) // I also tried this instead...error here too: No exact matches in call to initializer
        }
    }
    return items
}

But ran into build errors when I tried it out…

let membersTom2 = findAllCollection(cast, "Tom") // Cannot convert value of type '[String]' to expected argument type 'AnyCollection<String>'

The return type on your function is still wrong. I guess using a Set is OK but it can’t be a Set of things of type T, it needs to be a set of things that can be used to index into whatever container was passed in as stuff. And it can’t be a Set of Integers either - you can’t use integers as indexes into a set, for example. Different containers have different ways of indexing into them and the return type needs to be able to accommodate any of them. (Well, any collections that can store Equatable elements - I don’t think you need to worry about Dictionaries, for instance.) The hint the assignment gives you is suggesting a solution to this problem.

You also have a problem with your if statement. You can’t say “stuff == searchTerm”, you need to say “stuff[i] == searchTerm” but again, that isn’t going to work for all collections when i is an integer (which is what the for loop sets it up to be). You need to find a different way to loop through the container.

Also, if you haven’t been reading the documentation on the Collection protocol, you really need to.

What exactly is “an array of an associated type of the Collection Protocol” if it’s neither an AnyCollection nor a Set?

A Slice? But after trying to understand the Collection protocol documentation, I don’t think that meets the needs for this exercise.

Finally, when I revert back to this:

var items = AnyCollection<T>() 

I get an error stating “Missing argument for parameter #1 in call” and Xcode recommends “Insert ‘<#AnyCollection#>’” as a Fix. So I did, and it gives me

var items = AnyCollection<T>(<#AnyCollection<T>#>) // `Ambiguous use of 'init(_:)'`

…which really doesn’t look correct.

I’ve spent almost a week trying to get this right. And I’m still struggling to break through to the correct solution.

Wow, no, that suggestion doesn’t look correct to me either. But you need to get the function signature correct before you worry about the code inside it.

The Collection protocol defines several associated types, which are types related to the collection. The one that will be most useful here is Index, which will be a type that can be used to index the collection. If your function has an input of AnyCollection<T>, a value of type AnyCollection<T>.Index can be used to index into that collection. Your function should be returning an array of those values.

This is part of the code I wrote to test my implementation:

let numbers = Set<String>(["One", "Two", "Three", "three", "Four"])
let numberCollection = AnyCollection(numbers)
let indexes = findAllGold2(numberCollection, "Three")
for index in indexes { 
    print(numberCollection[index])    // prints "Three"
}
1 Like

I tried applying index, but I can’t find an equivalent method to append to add it to the array I want to return:

func findAllCollection<T: Equatable>(_ stuff: AnyCollection<T>,_ searchTerm: T) -> [AnyCollection<T>.Index]{
    var items = [AnyCollection<T>.Index]()
    for thing in stuff {
        if thing == searchTerm {
            items.append(thing) // No exact matches in call to instance method 'append'
        }
    }
    return items

I’m still flailing around here. What I would really find useful is a full-proof sequence to approach any programming exercise besides

Retry > Fail > Fail > Fail > Fail > Fail

I’ve tried StackOverflow and all I see in results are either inapplicable to what I’m trying to solve or too difficult to understand.

I’ve stated my gripes with Apple Developer Documentation in the past.

Finally, the impression I’ve carried for a very long time is: one has to know everything about programming all at once before learning how to program.

That’s the most frustrating part about all of this.

You’re getting closer.

You have items, which is an array of type AnyCollection<T>.Index, and you have thing which is of type T. You can’t put thing into that array, you need to put the index of thing into the array.

AnyCollection has a property indices which is a collection of all the valid indices into the collection. So if you iterate through stuff.indices you can find out which ones point to a value matching searchTerm and put those indices into items.

That’s what programming is, especially when you’re learning. That’s what any learning experience is like - you fail a lot in the beginning. As you get more experience you fail less but you still fail. It took me about 40 minutes to figure out my first solution to this exercise and I made several wrong attempts before getting it right.

You don’t need to know everything about programming. You do need to get used to the idea that at any time you could run into something you don’t know how to do and you suddenly have to switch back into learning mode to figure it out, and sometimes that can involve a lot of trial & error.

After over an hour later, I was able to produce your output with this:

func findAllCollection<T: Equatable>(_ stuff: AnyCollection<T>,_ searchTerm: T) -> [AnyCollection<T>.Index]{
    var items = [AnyCollection<T>.Index]()
    for thing in stuff {
        if thing == searchTerm {
            items.append(stuff.firstIndex(of: searchTerm)!)
        }
    }
    return items
}

But it won’t work if I add more entries of “Three” to numbers. In that case, isn’t it supposed to print “Three” more than once?

Why did Apple deprecate the index(of: ) function?

If you’re talking about the test code I posted earlier, you can’t add more entries of “Three” to the set numbers. Sets are collection of unique values, so any given string can only occur once in the set. That’s why my set had “Three” followed by “three”. If you try to initialize a Set with an array that has a duplicate entry, it will silently throw away the duplicate:

let numbers = Set<String>(["One", "Two", "Three", "Three", "Four"])

results in the set {“Two”, “Four”, “One”, “Three”} (although the order changes every time I run my playground, which is to be expected with an unordered collection).

And anyway firstIndex doesn’t do what you want. Suppose you passed in an array like [1, 2, 3, 4, 1, 5, 6]. If you tell your function to search for the number 1, you should get back two indexes - one for the first value and one for the fifth value, but your function would return two indexes to the first value.

I think the reason they deprecated index(of:) is that the name was misleading. If you read the description of the function it was returning the first index, so it was really identical to firstIndex(of:) but the name might have made people think it was not returning the first index.

Like I said in my previous post, you need to iterate through the container indices, not the container values.

Finally…

func findAllCollection<T: Equatable>(_ stuff: AnyCollection<T>,_ searchTerm: T) -> [AnyCollection<T>.Index]{
    var items = [AnyCollection<T>.Index]()
    var i = stuff.startIndex
    while i != stuff.endIndex {
        if stuff[i] == searchTerm {
            items.append(i)
        }
        i = stuff.index(after: i)
    }
    return items
}

let numbers = AnyCollection<String>(["One", "Two", "Three", "three", "Four", "Three"])
let numberCollection = AnyCollection(numbers)
let indexes = findAllCollection(numberCollection, "Three")
for index in indexes {
    print(numberCollection[index])    // prints "Three" twice

Looks good.

Here’s my original solution using the Collection protocol instead of AnyCollection:

// Gold challenge
func findAllCollection<MyCollection: Collection>(_ container: MyCollection, 
                                                 _ searchTerm: MyCollection.Element) -> [MyCollection.Index] where MyCollection.Element: Equatable {
    var output: [MyCollection.Index] = []

    for i in container.indices where (container[i] == searchTerm) {
        output.append(i)
    }
    return output
}

var names = ["Jon", "Jim", "Joe", "Jay", "Jim"]
for index in findAllCollection(names, "Jim") { names[index] = names[index].uppercased() }
print(names)  // prints "["Jon", "JIM", "Joe", "Jay", "JIM"]"

var nameSet = Set<String>(names)
for index in findAllCollection(nameSet, "JIM") {
    let newName = nameSet[index].lowercased()
    nameSet.remove(at: index)
    nameSet.insert(newName)
}
print(nameSet)  // prints "["Joe", "Jon", "Jay", "jim"]
1 Like