user3800924
user3800924

Reputation: 407

count numbers in array and order them by count in swift

Is there a easy way to sort an array by the count of numbers? And if a number have the same count put the highest number first.

[2,8,2,6,1,8,2,6,6] 
to
[6,6,6,2,2,2,8,8,1]

Upvotes: 1

Views: 1424

Answers (4)

Damian
Damian

Reputation: 4763

Using grouping in Dictionary:

var entries = [1,2,3,3,1,3,5,6,3,4,1,5,5,5,5]

extension Sequence where Element : Hashable {
    func byFrequency() -> [Element] {
        Dictionary(grouping: self, by: {$0}).sorted{ (a, b) in
            a.value.count > b.value.count
        }.map { $0.key}
    }
}


print(entries.byFrequency().first)

Prints 5

Upvotes: 0

Aaron Rasmussen
Aaron Rasmussen

Reputation: 13316

I'm not sure if this is the most efficient way to do it, but I think it is fairly elegant:

extension Array where Element: Equatable {

    func subArrays() -> [[Element]] {
        if self.isEmpty {
            return [[]]
        } else {
            let slice = self.filter { $0 == self[0] }
            let rest = self.filter { $0 != self[0] }
            return rest.isEmpty 
                ? [slice]
                : [slice] + rest.subArrays()
        }
    }

    func sortByFrequency(secondarySort: ((Element, Element) -> Bool)? = nil) -> [Element] {
        return self.subArrays()
            .sort { secondarySort?($0[0], $1[0]) ?? false }
            .sort { $0.count > $1.count }
            .flatMap { $0 }
    }
}

let nums = [2,8,2,6,1,8,2,6,6]

print(nums.sortByFrequency(>))     // [6, 6, 6, 2, 2, 2, 8, 8, 1]

The function subArrays just breaks the array down into an array of sub-arrays for each value in the original array - i.e., you'd get [[2,2,2],[8,8],[6,6,6],[1]] for the input that you provided.

sortByFrequency sorts the output of subArrays and then flatMaps to get the answer.

EDIT: I modified sortByFrequency to add the optional secondarySearch parameter. That allows you to control how you want items that occur at the same frequency to be sorted. Or, just accept the default nil and they won't be sorted by anything other than frequency.

Also, I modified the extension to indicate that Element only needs to conform to Equatable, not Comparable.

Upvotes: 3

R Menke
R Menke

Reputation: 8391

What you are looking for is a way to get the frequencies of values. As long as the values are Hashable this function will work:

It extends all sequence types where the Element is Hashable, so an array of Int will work.

extension SequenceType where Generator.Element : Hashable {
    func frequencies() -> [Generator.Element:Int] {
        var results : [Generator.Element:Int] = [:]
        for element in self {
            results[element] = (results[element] ?? 0) + 1
        }
        return results
    }
}

Then you can do this:

let alpha = [2,8,2,6,1,8,2,6,6]

let sorted = alpha.frequencies().sort {
    if $0.1 > $1.1 { // if the frequency is higher, return true
        return true
    } else if $0.1 == $1.1 { // if the frequency is equal
        return $0.0 > $1.0 // return value is higher
    } else {
        return false // else return false
    }
}

Even better, you can now create another extension to sequence types. Now they need to conform to Comparable as well as Hashable

extension SequenceType where Generator.Element : protocol<Hashable,Comparable> {
    func sortByFrequency() -> [Generator.Element] {
        // the same sort function as before
        let sorted = self.frequencies().sort {
            if $0.1 > $1.1 {
                return true
            } else if $0.1 == $1.1 {
                return $0.0 > $1.0
            } else {
                return false
            }
        }
        // this is to convert back from the dictionary to an array
        var sortedValues : [Generator.Element] = []

        sorted.forEach { // for each time the value was found
            for _ in 0..<$0.1 {
                sortedValues.append($0.0) // append
            }
        }
        return sortedValues
    }
}

Your final usage of all this will look like this :

let sorted = alpha.sortByFrequency() // [6, 6, 6, 2, 2, 2, 8, 8, 1]

Super clean :)


If you prefer a function closer to sort itself you can also use this :

extension SequenceType where Generator.Element : Hashable {
    func sortedFrequency(@noescape isOrderedBefore: ((Self.Generator.Element,Int), (Self.Generator.Element,Int)) -> Bool) -> [Generator.Element] {

        let sorted = self.frequencies().sort {
            return isOrderedBefore($0,$1) // this uses the closure to sort
        }

        var sortedValues : [Generator.Element] = []

        sorted.forEach {
            for _ in 0..<$0.1 {
                sortedValues.append($0.0)
            }
        }
        return sortedValues
    }
}

The extension above converts the array to a frequency dictionary internally and just asks you to input a closure that returns a Bool. Then you can apply different sorting depending on your needs.

Because you pass the closure with the sorting logic to this function the Elements of the SequenceType no longer need to be comparable.

Cheat sheet for all the shorthand:

$0 // first element
$1 // second element
$0.0 // value of first element
$0.1 // frequency of first element

Sorting :

let sortedB = alpha.sortedFrequency {
    if $0.1 > $1.1 {
        return true
    } else if $0.1 == $1.1 {
        return $0.0 > $1.0
    } else {
        return false
    }
} // [6, 6, 6, 2, 2, 2, 8, 8, 1]

Upvotes: 5

Florian Blum
Florian Blum

Reputation: 658

//: Playground - noun: a place where people can play

import UIKit

var arr1 = [2,8,2,6,1,8,2,6,6]
var arr2 = [6,6,6,2,2,2,8,8,1]

var counting = [Int: Int]()

// fill counting dictionary
for num in arr1 {
    if counting[num] != nil {
        counting[num]!++
    } else {
        counting[num] = 1
    }
}

// [6: 3, 2: 3, 8: 2, 1: 1]
print(counting)

func order(i1: Int, i2: Int) -> Bool {
    let count1 = counting[i1]
    let count2 = counting[i2]

    // if counting is the same: compare which number is greater
    if count1 == count2 {
        return i1 > i2
    } else {
        return count1 > count2
    }
}

// [6, 6, 6, 2, 2, 2, 8, 8, 1]
print(arr1.sort(order))
print(arr2)

Upvotes: 1

Related Questions