Histogram of Array in Swift - ios

I'm trying to write a generic histogram function that operates on an Array, but I'm running into difficulties as Type 'Element' does not conform to protocol 'Hashable'.
extension Array {
func histogram() -> [Array.Element: Int] {
return self.reduce([Array.Element: Int]()) { (acc, key) in
let value = (acc[key] == nil) ? 1 : (acc[key]! + 1)
return acc.dictionaryByUpdatingKey(key: key, value: value)
}
}
}
where dictionaryByUpdatingKey(...) mutates an existing dictionary as follows:
extension Dictionary {
func dictionaryByUpdatingKey(key: Dictionary.Key, value: Dictionary.Value) -> Dictionary {
var mutableSelf = self
let _ = mutableSelf.updateValue(value, forKey: key)
return mutableSelf
}
}
I have tried replacing Array.Element with AnyHashable and then forcing the key as! AnyHashable, but this seems messy and the return type should preferably be of the same type as the Array.Element and not of AnyHashable.
I wish to use the Array extension as follows:
let names = ["Alex", "Alex", "James"]
print(names.histogram()) // ["James": 1, "Alex": 2]
or
let numbers = [2.0, 2.0, 3.0]
print(numbers.histogram()) // [3.0: 1, 2.0: 2]

Add the generic where clause: where Element: Hashable to your extension:
extension Sequence where Element: Hashable {
func histogram() -> [Element: Int] {
return self.reduce([Element: Int]()) { (acc, key) in
let value = acc[key, default: 0] + 1
return acc.dictionaryByUpdatingKey(key: key, value: value)
}
}
}
I also incorporated #MartinR's suggestion of using the new default value for dictionary look ups.
Using reduce(into:_:) you can do this much more simply and efficiently:
extension Sequence where Element: Hashable {
func histogram() -> [Element: Int] {
return self.reduce(into: [:]) { counts, elem in counts[elem, default: 0] += 1 }
}
}

First, you could limit the Element type to only be Hashable.
extension Array where Array.Element:Hashable {
After this, you might get another error because the swift compiler is a little "overstrained". Try to give him a hint:
typealias RT = [Array.Element: Int]
and use it everywhere. So:
extension Array where Array.Element:Hashable {
typealias RT = [Array.Element: Int]
func histogram() -> RT {
return self.reduce(RT()) { (acc, key) in
let value = (acc[key] == nil) ? 1 : (acc[key]! + 1)
return acc.dictionaryByUpdatingKey(key: key, value: value)
}
}
}
finally should work.

Related

GroupBy Extension Usage on Array on Swift

I have an array consists of Dictionary.
I need to group them by Key in a dictionary.
I tried the line, but do not know what to write in handler. I am trying
globalArray.groupBy(handler: {$0["Name"]})
it gives error;
Cannot convert value of type "String?" to closure result type "_"
my group by extension is as follows;
extension Sequence {
// Using a `typealias` because it's shorter to write `E`
// Think of it as a shortcut
typealias E = Iterator.Element
// Declaring a `K` generic that we'll use as the type of the key
// for the resulting dictionary. The only restriction is having
// it conforming to the `Hashable` protocol
func groupBy<K: Hashable>(handler: (E) -> K) -> [K: [E]] {
// Creating the resulting dictionary
var grouped = [K: [E]]()
// Iterating over our elements
self.forEach { item in
// Retrieving the key based on the current item
let key = handler(item)
if grouped[key] == nil {
grouped[key] = []
}
grouped[key]?.append(item)
}
return grouped
}
}
Could you please show me the right usage?
BR,
Erdem
I am using this extension to group array , and it is working superbly
extension Array {
func grouped<T>(by criteria: (Element) -> T) -> [T: [Element]] {
var groups = [T: [Element]]()
for element in self {
let key = criteria(element)
if groups.keys.contains(key) == false {
groups[key] = [Element]()
}
groups[key]?.append(element)
}
return groups
}
}
How I use
array.grouped { (object:MyObjectClass) -> String in
return object.location?.name ?? "EmptyKey"
//Here you need to return your key
}
Hope it is helpful to you

Conforming an array of enums to Hashable for use as dictionary key

I'd like to use an array of custom enums as a dictionary key, but I'm having difficulty figuring out how to make the array conform to Hashable. The compiler tells me that [Symbol] doesn't conform to Hashable. What do I have to do to get this to compile?
I messed around with an extension to Array where Element:Symbol, but I couldn't figure out how to add a protocol that way.
enum Symbol:Hashable, Equatable {
case Dot
case Dash
var value:Int {
get {
switch self {
case .Dot: return 0
case .Dash: return 1
}
}
var hashValue:Int {
return self.value
}
}
func ==(left: Symbol, right: Symbol) -> Bool {
return left.value == right.value
}
struct MorseDictionary {
static let symbolToStringDictionary:[[Symbol]:String] = [
[.Dot, .Dash]:"A"
]
}
In Swift 2.2 you cannot create an extension restricted to an Array of a specific type
So you cannot conform an Array of Symbol to Hashable or Equatable.
This means you cannot use an Array of Symbol as key in a Dictionary.
Of course you can create an extension making Array (every array!!) Equatable and Hashable but it's a crazy approach since obviously you will never be able to provide a valid implementation.
However you can follow another approach
First of all your enum can be rewritten this way
enum Symbol: Int {
case Dot = 0, Dash
}
Next you'll need a wrapper for an array of Symbol
struct Symbols: Hashable, Equatable {
let value: [Symbol]
// you can use a better logic here
var hashValue: Int { return value.first?.hashValue ?? 0 }
}
func ==(left: Symbols, right:Symbols) -> Bool {
return !zip(left.value, right.value).contains { $0.0 != $0.1 }
}
Now you can create your dictionary
let dict: [Symbols:String] = [Symbols(value: [.Dot, .Dash]) : "A"]
I have same idea with #appzYourLife. So do not accept my answer. The worth thing here, I have implemented the hash function for Symbols.
func ==(left: Symbols, right: Symbols) -> Bool {
return left.value == right.value
}
enum Symbol: Int {
case Dot = 0
case Dash = 1
case Count = 2
}
struct Symbols: Hashable {
let symbols: [Symbol]
init(symbols: [Symbol]) {
self.symbols = symbols
}
var value: Int {
var sum = 0
var i = 1
symbols.forEach({ (s) in
sum += s.rawValue * i
i = i * Symbol.Count.rawValue
})
return sum
}
var hashValue: Int {
return value % Int(pow(Double(2), Double(Symbol.Count.rawValue)))
}
}
struct MorseDictionary {
static let symbolToStringDictionary: [Symbols: String] = [
Symbols(symbols: [.Dot, .Dash]): "A",
Symbols(symbols: [.Dash, .Dot]): "B",
Symbols(symbols: [.Dash, .Dash]): "C",
Symbols(symbols: [.Dot, .Dot]): "D",
]
}
Clients's code:
MorseDictionary.symbolToStringDictionary[Symbols(symbols: [.Dot, .Dash])]
MorseDictionary.symbolToStringDictionary[Symbols(symbols: [.Dash, .Dot])]
MorseDictionary.symbolToStringDictionary[Symbols(symbols: [.Dash, .Dash])]
MorseDictionary.symbolToStringDictionary[Symbols(symbols: [.Dot, .Dot])]
Results in:
"A"
"B"
"C"
"D"

Dictionary extension to return a combined dictionary

I'm trying to extend Swift's dictionary class in the following manner:
extension Dictionary {
func merge<K, V>(dict: [K:V]) -> Dictionary<K, V> {
var combinedDict: [K:V] = [:]
for (k, v) in self {
combinedDict[k] = v
}
for (k, v) in dict {
combinedDict[k] = v
}
return combinedDict
}
}
The first for loop gives me the error: "Cannot subscript a value of type '[K:V]' with an index of type 'Key'" but the second for loop is fine. I even commented out the first one to check and the second still works. Anyone know what the problem is? Thanks!
A dictionary's generic placeholder types are called Key and Value and you have to keep those names; you cannot arbitrarily rename them K and V.
Here's the implementation I use:
extension Dictionary {
mutating func addEntriesFromDictionary(d:[Key:Value]) { // generic types
for (k,v) in d {
self[k] = v
}
}
}
The dictionary type already defines Key and Value as generic variables, so K and V are not required (and cause the problem).
extension Dictionary {
func merge(dict: [Key : Value]) -> [Key : Value] {
var combinedDict = self
for (k, v) in dict {
combinedDict[k] = v
}
return combinedDict
}
}
How about this code.
extension Dictionary {
func merge(other: [Key: Value]) -> [Key: Value] {
var ret: [Key: Value] = self
for (key, value) in other {
ret[key] = value
}
return ret
}
}

How to determine if one array contains all elements of another array in Swift?

I have 2 arrays:
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
I want to determine if list Array contains all findList elements.
By the way, elements might be String as well or other type.
How to do that?
I know that Swift provides contains method that works with one item.
Instead of iterating through arrays and doing filtering yourself, you can use NSSet to do all the work for you.
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
let listSet = NSSet(array: list)
let findListSet = NSSet(array: findList)
let allElemtsEqual = findListSet.isSubsetOfSet(otherSet: listSet)
NSSet is a lot faster than arrays at checking if it contains any object. In fact it's what it's designed for.
Edit: Using Swift's built-in Set.
let list = [1,2,3,4,5]
let findList = [1,3,5]
let listSet = Set(list)
let findListSet = Set(findList)
//**Swift 4.2 and Above**
let allElemsContained = findListSet.isSubset(of: listSet)
//below versions
//let allElemsContained = findListSet.isSubsetOf(listSet)
allSatisfy seems to be what you want, assuming you can't conform your elements to Hashable and use the set intersection approach others have mentioned:
let containsAll = subArray.allSatisfy(largerArray.contains)
Since Swift 4.2 you can write:
extension Array where Element: Equatable {
func satisfy(array: [Element]) -> Bool {
return self.allSatisfy(array.contains)
}
}
Otherwise for Swift 3, Swift 4 you can write this:
extension Array where Element: Equatable {
func contains(array: [Element]) -> Bool {
for item in array {
if !self.contains(item) { return false }
}
return true
}
}
You can see the:
contains method here
allSatisfy method here
This is just a simple extension that check if the array that you give is in the current array (self)
You can use the filter method to return all elements of findList which are not in list:
let notFoundList = findList.filter( { contains(list, $0) == false } )
then check if the length of the returned array is zero:
let contained = notFoundList.count == 0
Note that his solution traverses the entire findList array, so it doesn't stop as soon as a non contained element is found. It should be used if you also want to know which elements are not contained.
If you just need a boolean stating whether all elements are contained or not, then the solution provided by Maxim Shoustin is more efficient.
Consider following generic method:
func arrayContainsArray<S : SequenceType where S.Generator.Element : Equatable>
(src:S, lookFor:S) -> Bool{
for v:S.Generator.Element in lookFor{
if contains(src, v) == false{
return false
}
}
return true
}
The advantage - method stops after 1st fail and do not continue over findList
Tests
var listAsInt:Array<Int> = [1,2,3,4,5]
var findListAsInt:Array<Int> = [1,3,5]
var result = arrayContainsArray(listAsInt, findListAsInt) // true
listAsInt:Array<Int> = [1,2,3,4,5]
findListAsInt:Array<Int> = [1,3,5,7,8,9]
result = arrayContainsArray(listAsInt, findListAsInt) // false
var listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
var findListOfStr:Array<String> = ["bbb","ccc","eee"]
result = arrayContainsArray(listOfStr, findListOfStr) // true
listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
findListOfStr:Array<String> = ["bbb","ccc","eee","sss","fff","ggg"]
result = arrayContainsArray(listOfStr, findListOfStr) // false
(tested on Beta7)
As a complement to Sequence.contains(element) handling multiple elements, add this extension:
public extension Sequence where Element : Hashable {
func contains(_ elements: [Element]) -> Bool {
return Set(elements).isSubset(of:Set(self))
}
}
Used:
list.contains(findList)
Since this uses Set/Hashable it performs much better than Equatable alternatives.
Right now, I'd probably use something like:
let result = list.reduce(true, { $0 ? contains(findList, $1) : $0 })
...but then I did just read this article, which might be biasing me towards this kind of solution. You could probably make this more efficient without making it completely unreadable, but it's early and I've not had my coffee.
Extend the Array with the following methods:
extension Array {
func contains<T where T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
func isEqualTo< T : Equatable> (comparingArray : [T]) -> Bool {
if self.count != comparingArray.count {
return false
}
for e in comparingArray {
if !self.contains(e){
return false
}
}
return true
}
}
An example of how you can use it like this:
if selectedDates.isEqualTo(originalDates) {
//Arrays the same hide save button
} else {
//Arrays not the same, show Save & Discard Changes Button (if not shown)
}
Shout out to #David Berry for the contain method.
None of the previous answers seem to be right.
consider:
let a = [2,2]
let b = [1,2,3]
we wouldn't say that b actually "contains" a, but if your algorithm is based on for-loop & swift's built-in contains(element:) or a set, the above case would pass.
I use this set of extended methods myself. I hope this code snippet helps:
// Array + CommonElements.swift
import Foundation
public extension Array where Element: Hashable {
func set() -> Set<Array.Element> {
return Set(self)
}
func isSubset(of array: Array) -> Bool {
self.set().isSubset(of: array.set())
}
func isSuperset(of array: Array) -> Bool {
self.set().isSuperset(of: array.set())
}
func commonElements(between array: Array) -> Array {
let intersection = self.set().intersection(array.set())
return intersection.map({ $0 })
}
func hasCommonElements(with array: Array) -> Bool {
return self.commonElements(between: array).count >= 1 ? true : false
}
}
This is Maxim Shoustin's answer updated for Swift 3:
func arrayContainsArray<S : Sequence>
(src:S, lookFor:S) -> Bool where S.Iterator.Element : Equatable{
for v:S.Iterator.Element in lookFor{
if src.contains(v) == false{
return false
}
}
return true
}
If you need to determine, that one array is subArray of another.
public extension Array where Element: Equatable {
func isSuperArray(of array: Array<Element>) -> Bool {
guard
count >= array.count,
let indexes = array.first.flatMap(indexes(of:)),
!indexes.isEmpty else {
return false
}
let arraysForComparison = indexes
.compactMap { index -> [Element]? in
guard index + (array.count - 1) <= count else { return nil }
return Array(self[index..<(index + array.count)])
}
return arraysForComparison.contains(array)
}
func isSubArray(of array: Array<Element>) -> Bool {
array.isSuperArray(of: self)
}
private func indexes(of element: Element) -> [Index] {
enumerated()
.filter { element == $0.1 }
.map { index, _ in index }
}
}
Example of usage:
let array1 = [1, 2, 3, 4]
let array2 = [2, 3]
print(array1.isSuperArray(of: array2)) // true
print(array2.isSubArray(of: array1)) // true
print(array2.isSuperArray(of: array1)) // false
print(array1.isSubArray(of: array2)) // false

How to unwrap the elements of an Array in Swift? (ie. Array<Int?> as Array<Int>)

Lets say I have an Array of String and I want to map it to an Array of Int
I can use the map function:
var arrayOfStrings: Array = ["0", "a"]
let numbersOptional = arrayOfStrings.map { $0.toInt() }
// numbersOptional = "[Optional(0), nil]"
Numbers is now an Array of Int?, but I want an Array of Int.
I know I can do this:
let numbers = arrayOfStrings.map { $0.toInt() }.filter { $0 != nil }.map { $0! }
// numbers = [0]
But that doesn't seem very swift. Converting from the Array of Int? to Array of Int requires calling both filter and map with pretty much boilerplate stuff. Is there a more swift way to do this?
Update: As of Swift 1.2, there's a built-in flatMap method for arrays, but it doesn't accept Optionals, so the helper below is still useful.
I like using a helper flatMap function for that sort of things, just like Scala's flatMap method on collections (which can consider an Scala Option as a collection of either 0 or 1 element, roughly spoken):
func flatMap<C : CollectionType, T>(source: C, transform: (C.Generator.Element) -> T?) -> [T] {
var buffer = [T]()
for elem in source {
if let mappedElem = transform(elem) {
buffer.append(mappedElem)
}
}
return buffer
}
let a = ["0", "a", "42"]
let b0 = map(a, { $0.toInt() }) // [Int?] - [{Some 0}, nil, {Some 42}]
let b1 = flatMap(a, { $0.toInt() }) // [Int] - [0, 42]
This definition of flatMap is rather a special case for Optional of what a more general flatMap should do:
func flatMap<C : CollectionType, T : CollectionType>(source: C, transform: (C.Generator.Element) -> T) -> [T.Generator.Element] {
var buffer = [T.Generator.Element]()
for elem in source {
buffer.extend(transform(elem))
}
return buffer
}
where we'd get
let b2 = flatMap(a, { [$0, $0, $0] }) // [String] - ["0", "0", "0", "a", "a", "a", "42", "42", "42"]
Using reduce to build the new array might be more idiomatic
func filterInt(a: Array<String>) -> Array<Int> {
return a.reduce(Array<Int>()) {
var a = $0
if let x = $1.toInt() {
a.append(x)
}
return a
}
}
Example
filterInt(["0", "a", "42"]) // [0, 42]
What you would really want is a collect (map + filter) method. Given the specific filter you need to apply, in this case even a flatMap would work (see Jean-Philippe's answer). Too bad both methods are not provided by the swift standard library.
update: Xcode 7.2 • Swift 2.1.1
let arrayOfStrings = ["0", "a", "1"]
let numbersOnly = arrayOfStrings.flatMap { Int($0) }
print(numbersOnly) // [0,1]
There’s no good builtin Swift standard library way to do this. However, Haskell has a function, mapMaybe, that does what you’re looking for (assuming you just want to ditch nil values). Here’s an equivalent in Swift:
func mapSome<S: SequenceType, D: ExtensibleCollectionType>
(source: S, transform: (S.Generator.Element)->D.Generator.Element?)
-> D {
var result = D()
for x in source {
if let y = transform(x) {
result.append(y)
}
}
return result
}
// version that defaults to returning an array if unspecified
func mapSome<S: SequenceType, T>
(source: S, transform: (S.Generator.Element)->T?) -> [T] {
return mapSome(source, transform)
}
let s = ["1","2","elephant"]
mapSome(s) { $0.toInt() } // [1,2]
You can consider using reduce, it's more flexible:
var arrayOfStrings: Array = ["0", "a"]
let numbersOptional = arrayOfStrings.reduce([Int]()) { acc, str in
if let i = str.toInt() {
return acc + [i]
}
return acc
}

Resources