Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get distinct values from an Array of Classes?

Tags:

xcode

swift

I've found many examples on how to get the distinct values from an array such as this:

[1, 2, 2, 3, 4, 5, 6]

And then I can do Array(Set(myArray)), however, this doesn't work when the array value is a class.

However, what I need is to get a list of distinct values from a property of a class that is in an array such as this class:

class Alert: Decodable {
    var id: Int?

    var category: String?
    var category_id: Int?

    var summary: String?
    var title: String?
    var publish_date: String?

}

I'd like to get a list of "category" from my array.

I know I can probably just loop through the array, and append a new array if the value doesn't exist. Not sure if this is the most effective way, or if there is a more swifty way of doing things.

I found I can do:

let categories = self.items!.map { $0.category }

Which will get a list of the categories, but not sure how to make them distinct. I tried Array(Set(, but that doesn't work. I get the following error:

Generic parameter 'Element' could not be inferred

I'm relatively new to swift, so I know what I want to do, just can't figure it out syntactically.

Any guidance will be appreciated.

like image 633
CodeLikeBeaker Avatar asked Jan 02 '23 14:01

CodeLikeBeaker


1 Answers

Set requires your elements to be Hashable. That means they have to be Equatable and provide a hashing value.

If your aim is to create list of unique categories, then you should start by creating an array of categories:

let categories = (self.items ?? []).compactMap { $0.category }

Then you can simply get unique categories using Set:

let uniqueCategories = Array(Set(categories))

However, note that Set does not keep items ordered. If you also need to keep the categories in order then I would recommend a simple Array extension:

extension Array where Element: Hashable {
    func distinct() -> Array<Element> {
        var set = Set<Element>()
        return filter {
            guard !set.contains($0) else { return false }
            set.insert($0)
            return true
        }
    }
}

or simplified using Set.insert return value (Leo Dabus's idea):

extension Array where Element: Hashable {
    func distinct() -> Array<Element> {
        var set = Set<Element>()
        return filter { set.insert($0).inserted }
    }
}

and then

let uniqueCategories = (self.items ?? [])
   .flatMap { $0.category }
   .distinct()
like image 168
Sulthan Avatar answered Jan 05 '23 16:01

Sulthan