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]
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]
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]
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.
Element to be Hashable//: 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)
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