Reputation: 407
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
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
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 flatMap
s 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
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
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