2
votes

The goal was to convert items in a generic list into a dictionary for the variable uniqueKeys, but I saw the error:

Cannot subscript a value of incorrect or ambiguous type

I knew something needed to conform to the Hashable protocol and eventually landed on the solution, but I don't completely understand why this solution works.

  1. I understand why T needs to be Hashable since its' going into a dictionary key, but why also CustomSet?
  2. If CustomSet uses Hashable why don't I need to write anything in its extension?

initial code

struct CustomSet<T : Comparable > {
    private var list: [T]
    init(_ list: [T]){
        let uniqueKeys = list.reduce(into: [:]){ dict, item in
            dict[item, default: 0] += 1
        }.keys
        self.list = Array(uniqueKeys)
    }
}

extension CustomSet : Equatable {
    static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
        return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
    }
}  

I finally resolved it with:

struct CustomSet<T : Comparable > : Hashable where T : Hashable {
    private var list: [T]
    init(_ list: [T]){
        let uniqueKeys = list.reduce(into: [:]){ dict, item in
            dict[item, default: 0] += 1
        }.keys
        self.list = Array(uniqueKeys)
    }
}

extension CustomSet : Equatable {
    static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
        return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
    }
}  

I also noticed that factoring into an extension: extension CustomSet: Hashable where T : Hashable doesn't seem to be the same as struct CustomSet<T : Comparable > : Hashable where T : Hashable in terms of adding the Hashable protocol because I still see that error

What I tried

If I add this to the generic type T I still saw the same error.

struct CustomSet<T : Comparable, Hashable >

If I add Hashable to CustomSet I see the error

Cannot convert value of type '[T]' to expected argument type 'UnsafeRawBufferPointer'

and

Inheritance from non-protocol type 'Hashable'

extension CustomSet : Equatable, Hashable {
    static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
        return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
    }

    func hash(into hasher: inout Hasher) {
       var hashValue: Int {
           var hasher = Hasher()
           self.hash(into: &hasher)
           return hasher.finalize()
       }
    }
 }
1
Please start with the problem, not with the solution: Which code causes the initial error message “Cannot subscript a value of incorrect or ambiguous type” that you talk about? - Martin R
CustomSet doesn't need to be Hashable, your code compiles just fine without the Hashable conformance. Btw what's the point of reducing a list into a Dictionary, whose keys are the elements of the list if then you're simply assigning the keys of said dictionary to list? You're not actually unique-ing those keys. - Dávid Pásztor
@MartinR just added original code. - Turnipdabeets
@Turnipdabeets: Then I don't understand your question. As Dávid says, CustomSet does not need to be Hashable. struct CustomSet<T : Comparable & Hashable > makes your initial code compile. - Martin R
@Turnipdabeets: For a starter, note that your func hash(into hasher: inout Hasher) does nothing (apart from declaring an unused computed property). - Martin R

1 Answers

2
votes

There's no need to make CustomSet conform to Hashable. Simply adding the Hashable restriction to the generic type parameter solves the error

Cannot subscript a value of incorrect or ambiguous type

,which is expected, since a Dictionary's keys need to conform to Hashable.

Btw the syntax for declaring conformance to several protocols is &, not ,, so there's no need for the where clause.

struct CustomSet<T : Comparable & Hashable> {
    private var list: [T]
    init(_ list: [T]){
        let uniqueKeys = list.reduce(into: [:]){ dict, item in
            dict[item, default: 0] += 1
            }.keys
        self.list = Array(uniqueKeys)
    }
}

extension CustomSet : Equatable {
    static func == (lhs: CustomSet, rhs: CustomSet) -> Bool {
        return lhs.list.count == rhs.list.count && lhs.list.sorted() == rhs.list.sorted()
    }
}

Moreover, you can simply do self.list = Array(Set(list)) in the initializer to store only the unique elements from the input in your list, there's no need for messing around with a Dictionary.