Get next value on a map? - swift
I'm trying to compare element to next element in a collection.
For example :
let array: [(Double, String)]= [(2.3, "ok"),
(1.4, "ok"),
(5.1, "notOk")]
I need a returned array who will summary element where the string is the same. So my result will be :
new array = [(3.7, "ok"), (5.1, "notOk")]
I need to do it functional if possible. i tried to get next element in a map but can't found how.
Something like this (this is just for logic, this code isn't working.
let newArray = array.map {(element, nextElement) in
if element.1 == nextElement.1 {
return element.0 + nextElement.0
}
}
In a more functional way:
let array: [(Double, String)]= [(2.3, "ok"),
(1.4, "ok"),
(5.1, "notOk")]
let keys = Set(array.map{$0.1}) // find unique keys
let result = keys.map { key -> (Double, String) in
let sum = array.filter {$0.1 == key} // find all entries with the current key
.map {$0.0} // map them to their values
.reduce(0, +) // sum the values
return (sum, key)
}
print(result)
Output:
[(5.0999999999999996, "notOk"), (3.6999999999999997, "ok")]
Alternatively (suggested by #dfri):
let keys = Set(array.map{$0.1}) // find unique keys
let result = keys.map { key -> (Double, String) in
let sum = array.reduce(0) { $0 + ($1.1 == key ? $1.0 : 0) }
return (sum, key)
}
I like alexburtnik's answer. It's basically word for word how I wrote my first pass of this. It's straightforward, clear, and efficient. It is excellent Swift.
But functional programming can help us think more deeply about problems and create better, reusable tools. So let's think functionally.
dfri's solution appears beautiful, but is O(m*n) (in the worst case, O(n^2)). It loops through the entire array for every unique key. This gets back the old adage by Alan Perlis: "A Lisp programmer knows the value of everything and the cost of nothing." But functional programming doesn't have to be inefficient.
The point of functional programming is to break down complex problems into simpler problems, make those simpler problems generic, and then recombine them. It's not about filters and flatMaps.
So let's break down this problem. We want to group by key, and then sum the values for each key. Grouping by key is going to be a lot easier if we sort by key first:
let result = array
.sorted(by: { $0.1 < $1.1 })
Now, we wish we could group them with something like this:
let result = array
.sorted(by: { $0.1 < $1.1 })
.grouped(by: { $0.1 == $1.1 })
I wish I had that grouped(by:). Wish fulfillment is the heart of functional programming, so let's write it. Well, a group is a sequence of elements that are all "equal" for some value of "equal." We could build that this way:
extension Array {
func grouped(by equal: (Element, Element) -> Bool) -> [[Element]] {
guard let firstElement = first else { return [] }
guard let splitIndex = index(where: { !equal($0, firstElement) } ) else { return [self] }
return [Array(prefix(upTo: splitIndex))] + Array(suffix(from: splitIndex)).grouped(by: equal)
}
That said, I don't really like this code. It's not very Swifty. That [Array(prefix(...)] + is a good indication of how much Swift hates us doing it this way. And it can be very expensive due to copying (probably getting us back to O(n^2). The Swiftier solution would be an Sequence:
struct GroupedSequence<Element>: Sequence, IteratorProtocol {
var elements: [Element]
let equal: (Element, Element) -> Bool
private var nextIndex = 0
init(of elements: [Element], by equal: #escaping (Element, Element) -> Bool) {
self.elements = elements
self.equal = equal
}
mutating func next() -> ArraySlice<Element>? {
guard nextIndex < elements.endIndex else { return nil }
let first = elements[nextIndex]
let splitIndex = elements[nextIndex..<elements.endIndex].index(where: { !equal($0, first) } ) ?? elements.endIndex
defer { nextIndex = splitIndex }
return elements[nextIndex..<splitIndex]
}
}
extension Array {
func grouped(by equal: #escaping (Element, Element) -> Bool) -> GroupedSequence<Element> {
return GroupedSequence(elements: self, equal: equal)
}
}
Yes, it mutates and it's a little more code, but it's also lazy (which is a key tool from functional programming), it's better Swift, and very reusable. I like it. But you can use the recursive, pure version if you like.
OK, so now we have an array of arrays that are equivalent. We want to map over those and reduce each element to its sum. So we'll have a reduce inside a map. But this is not O(n^2) because each reduce is only over a single slice. We're going to walk every element just one time. To take care of one impossible corner case (an empty group, which grouped(by:) will never actually create), we'll use flatMap, but it's really just a map. You might be tempted to jump to this, but don't do it:
let result: [(Double, String)] = array
.sorted(by: { $0.1 < $1.1 })
.grouped(by: { $0.1 == $1.1 })
.flatMap { group in
guard let key = group.first?.1 else { return nil }
return (group.reduce(0, { $0 + $1.0 }), // Sum of our values
key)
}
Why? That's horribly unreadable. This is what gives functional programming a bad name. What the heck is that last piece doing? No, we want functional composition, not just functional tools. So we extract a function:
func sumEach(pairGroup: ArraySlice<(Double, String)>) -> (Double, String)? {
guard let key = pairGroup.first?.1 else { return nil }
return (pairGroup.reduce(0, { $0 + $1.0 }), // Sum of our values
key)
}
Now, we can have our nice functional approach without sacrificing comprehension:
let result = array
.sorted(by: { $0.1 < $1.1 })
.grouped(by: { $0.1 == $1.1 })
.flatMap(sumEach(pairGroup:))
And in the process we've created a new tool, grouping, that we can use to compose other solutions. I think that's pretty nice.
But I'd still probably do it alexburtnik's way.
You can iterate over every tupple in your input array and save a sum in a dictionary like this:
let array: [(Double, String)] = [(1.0,"notok"),(2.0,"ok"),(3.0,"ok"),(4.0,"ok"),(5.0,"ok"),(6.0,"ok"), (7.0,"notok")]
var dict = [String: Double]()
for (value, key) in array {
dict[key] = (dict[key] ?? 0) + value
}
print ("dict: \(dict)")
Output:
dict: ["notok": 8.0, "ok": 20.0]
If you really need to get an array of tuples, use this:
let result = dict.map { (key, value) in (value, key) }
print ("result: \(result)")
Output:
result: [(8.0, "notok"), (20.0, "ok")]
I guess that a solution that makes a good use of Swift's features would be to combine filter and reduce:
let array: [(String, Double)] = [("ok", 2.4),
("ok", 1.3),
("not ok", 4.4),
("very not ok", 99.0)]
let key = "ok"
let result = array.filter({$0.0 != key}) + [array.filter({ $0.0 == key }).reduce((key, 0.0), { (key, $0.1 + $1.1) })]
print(result)
And then the result would be
[("not ok", 4.4000000000000004), ("very not ok", 99.0), ("ok", 3.7000000000000002)]
Which I assume is what you wanted to achieve.
EDIT:
To reduce all tuples you could simply wrap the solution inside of a function:
func reduceAllTuples(tupleArray: [(String, Double)]) -> [(String, Double)]{
var array = tupleArray
for (key, _) in tupleArray {
array = array.filter({$0.0 != key}) + [array.filter({ $0.0 == key }).reduce((key, 0.0), { (key, $0.1 + $1.1) })]
}
return array
}
Related
In swift 5, how to retrieve the key of the second low value in a dictionary of [String: Float]?
I have a dictionary: var mydict: [String: Float] = ["aze": 22.4, "lkh": 42.04, etc ... ] How to retrieve the key of the second low value in mydict?
You can use sorted(by:) to sort the Dictionary into ascending order based on the values, then simply get the 2nd element from the sorted array of key-value pairs and access its key property. var mydict: [String: Float] = ["aze": 22.4, "lkh": 42.04, "abc": 25.12 ] let ascendingDict = mydict.sorted(by: { $0.value < $1.value }) let secondLowest = ascendingDict[1] secondLowest.key // "abc"
Here is another solution that also handles dictionaries that contain 1 element or less by returning nil in that case. The solution is using a tuple of optional tuples (key/value) and the second lowest key is then found by using reduce(into:) on the dictionary let secondLowest = mydict.reduce(((String, Float)?, (String,Float)?)(nil, nil)) { guard let first = $0.0 else { return ($1, nil) } guard let second = $0.1 else { if first.1 < $1.value { return (first, $1) } return ($1, first) } return $1.value < first.1 ? ($1, first) : ($1.value < second.1 ? (first, $1) : $0) }.1?.0 this could also be expressed in an extension to Dictionary as a computed property extension Dictionary where Value: Comparable { var keyForSecondLowest: Key? { self.reduce(((Key, Value)?, (Key,Value)?)(nil, nil)) { guard let first = $0.0 else { return ($1, nil) } guard let second = $0.1 else { if first.1 < $1.value { return (first, $1) } return ($1, first) } return $1.value < first.1 ? ($1, first) : ($1.value < second.1 ? (first, $1) : $0) }.1?.0 } } Example if let secondLowest = mydict.keyForSecondLowest { print(secondLowest) }
Another approach is via pattern matching within reduce(): let minims = mydict.reduce(into: ((String, Float)?.none, (String, Float)?.none)) { switch ($0.0?.1 ?? .infinity, $0.1?.1 ?? .infinity, $1.1) { case let (min1, _, val) where val < min1: $0 = ($1, $0.0) case let (_, min2, val) where val < min2: $0.1 = $1 default: break } } print(minims.1?.0) The algorithm is the same as the Joakim's one, and it works by computing a tuple of optional tuples, the first optional tuple corresponding to the minimal entry in the dictionary, while the second will correspond to the next minimal entry. Note that if the dictionary has less than two elements then some of the result tuples will be nil. Performance is linear, as it requires only one iteration of the dictionary.
What is the purpose of .enumerated() and .map() in this code?
I'm working a tutorial from https://www.raywenderlich.com/921-cocoa-bindings-on-macos. I'm wondering what the .enumerated() and .map() functions are operating on in this section: #IBAction func searchClicked(_ sender: Any) { guard let resultsNumber = Int(numberResultsComboBox.stringValue) else { return } iTunesRequestManager.getSearchResults(searchTextField.stringValue, results: resultsNumber, langString: "en_us") { (results, error) in let itunesResults = results.map { return Result(dictionary: $0) } .enumerated() .map({ (index, element) -> Result in element.rank = index + 1 return element }) DispatchQueue.main.async { self.searchResultsController.content = itunesResults print(self.searchResultsController.content!) } } } I can usually figure out most things eventually in Swift but I'm stumped here and the explanatory text isn't clear to me either. I hope someone can help me understand this part of the tutorial. Thanks!
Map is used for modifications. At this point you are basically initialising an object of Result by giving results array as a param to it: results.map { return Result(dictionary: $0) } $0 means the first input. In a following case, $0 is equal to param(we just gave it a name): results.map { param in return Result(dictionary: param) } .enumerated() returns each element of an array with its index number. Without it you would have only the element like this: .map({ (element) -> Result in // you don't have `index` value inside the closure anymore // element.rank = index + 1 return element }) Note that the element in the closure above is the same Result(dictionary: $0) object that you created in a previous map function. At the end, you are making and modification by assigning elements index number increased by 1 to the element's rank property, and returning it: .map({ (index, element) -> Result in // element.rank = index + 1 return element }) Note that the value we get after the 3rd step, including all modification is assigned to let itunesResults.
Swift 4.2 closures
Hi I'm learning swift I have an exercise on closures used to filter collections I have two simple closures that are used to filter and map a dictionary let myDict: [String: Int] = ["Dan":38, "Kira":2, "Olga":33, "Jess":10, "Bobo":4] let filteredMyDict = myDict.filter { return $0.value < 5 } print(filteredMyDict) let filteredNames = filteredMyDict.map { return $0.key } print(filteredNames) Is it possible to chain the filter and map statement , if so how. That
You can chain filter and map let filteredNames = myDict.filter { $0.value < 5 } .map { $0.key } or use compactMap to get the result with a single traversal of the dictionary: Returns an array containing the non-nil results of calling the given transformation with each element of this sequence. In your case: let filteredNames = myDict.compactMap { $0.value < 5 ? $0.key : nil }
Swift: second occurrence with indexOf
let numbers = [1,3,4,5,5,9,0,1] To find the first 5, use: numbers.indexOf(5) How do I find the second occurence?
List item You can perform another search for the index of element at the remaining array slice as follow: edit/update: Swift 5.2 or later extension Collection where Element: Equatable { /// Returns the second index where the specified value appears in the collection. func secondIndex(of element: Element) -> Index? { guard let index = firstIndex(of: element) else { return nil } return self[self.index(after: index)...].firstIndex(of: element) } } extension Collection { /// Returns the second index in which an element of the collection satisfies the given predicate. func secondIndex(where predicate: (Element) throws -> Bool) rethrows -> Index? { guard let index = try firstIndex(where: predicate) else { return nil } return try self[self.index(after: index)...].firstIndex(where: predicate) } } Testing: let numbers = [1,3,4,5,5,9,0,1] if let index = numbers.secondIndex(of: 5) { print(index) // "4\n" } else { print("not found") } if let index = numbers.secondIndex(where: { $0.isMultiple(of: 3) }) { print(index) // "5\n" } else { print("not found") }
Once you've found the first occurrence, you can use indexOf on the remaining slice of the array to locate the second occurrence: let numbers = [1,3,4,5,5,9,0,1] if let firstFive = numbers.indexOf(5) { // 3 let secondFive = numbers[firstFive+1..<numbers.count].indexOf(5) // 4 }
I don't think you can do it with indexOf. Instead you'll have to use a for-loop. A shorthand version: let numbers = [1,3,4,5,5,9,0,1] var indexes = [Int]() numbers.enumerate().forEach { if $0.element == 5 { indexes += [$0.index] } } print(indexes) // [3, 4]
Here's a general use extension of Array that will work for finding the nth element of a kind in any array: extension Array where Element: Equatable { // returns nil if there is no nth occurence // or the index of the nth occurence if there is func findNthIndexOf(n: Int, thing: Element) -> Int? { guard n > 0 else { return nil } var count = 0 for (index, item) in enumerate() where item == thing { count += 1 if count == n { return index } } return nil } } let numbers = [1,3,4,5,5,9,0] numbers.findNthIndexOf(2, thing: 5) // returns 4
EDIT: as per #davecom's comment, I've included a similar but slightly more complex solution at the bottom of the answer. I see a couple of good solutions here, especially considering the limitations the relatively new language of Swift. There is a really concise way to do it too, but beware...it is rather quick-and-dirty. May not be the perfect solution, but it is pretty quick. Also very versatile (not to brag). extension Array where Element: Equatable { func indexes(search: Element) -> [Int] { return enumerate().reduce([Int]()) { $1.1 == search ? $0 + [$1.0] : $0 } } } Using this extension, you could access the second index as follows: let numbers = [1, 3, 4, 5, 5, 9, 0, 1] let indexesOf5 = numbers.indexes(5) // [3, 4] indexesOf5[1] // 4 And you're done! Basically, the method works like this: enumerate() maps the array to tuples including the index of each element with the element itself. In this case, [1, 3, 4, 5, 5, 9, 0, 1].enumerate() returns a collection of the type EnumerateSequence<Array<Int>> which, translated to an Integer array, returns [(0,1), (1,3), (2,4), (3,5), (4,5), (5,9), (6,0), (7,1)]. The rest of the work is done using reduce (called 'inject' in some languages), which is an extremely powerful tool that many coders are not familiar with. If the reader is among those coders, I'd recommend checking out this article regarding use of the function in JS (keep in mind the placement of the non-block argument passed in is inputted after the block in JS, rather than before as seen here). Thanks for reading. P.S. not to be too long-winded on this relatively simple solution, but if the syntax for the indexes method shown above is a bit too quick-and-dirty, you could try something like this in the method body, where the closure's parameters are expanded for a bit more clarity: return enumerate().reduce([Int]()) { memo, element in element.1 == search ? memo + [element.0] : memo } EDIT: Here's another option that allows the implementer to scan for a specific "index at index" (e.g. the second occurrence of 5) for a more efficient solution. extension Array where Element: Equatable { func nIndex(search: Element, n: Int) -> Int? { let info = enumerate().reduce((count: 0, index: 0), combine: { memo, element in memo.count < n && element.1 == search ? (count: memo.count + 1, index: element.0) : memo }) return info.count == n ? info.index : nil } } [1, 3, 4, 5, 5, 9, 0, 1].nIndex(5, n: 2) // 4 [1, 3, 4, 5, 5, 9, 0, 1].nIndex(5, n: 3) // nil The new method still iterates over the entire array, but is much more efficient due to the lack of "array-building" in the previous method. That performance hit would be negligible with the 8-object array used for the majority. But consider a list of 10,000 random numbers from 0 to 99: let randomNumbers = (1...10000).map{_ in Int(rand() % 100)} let indexes = randomNumbers.indexes(93) // count -> 100 (in my first run) let index1 = indexes[1] // 238 // executed in 29.6603130102158 sec let index2 = randomNumbers.nIndex(93, n: 2) // 238 // executed in 3.82625496387482 sec As can be seen, this new method is considerably faster with the (very) large dataset; it is a bit more cumbersome and confusing though, so depending on your application, you may prefer the simpler solution, or a different one entirely. (Again) thanks for reading.
extension Collection where Element: Equatable { func nth(occurance: Int, of element: Element) -> Index? { var level : Int = occurance var position = self.startIndex while let index = self[position...].index(of: element) { level -= 1 guard level >= 0 else { return nil } guard level != 0 else { return index } position = self.index(after: index) } return nil } }
Array transform having failable initialiser
I am using Swift 1.2 in Xcode 6.3.1 Following is my Person struct struct Person { let age: Int init?(age: Int) { //Failable init if age > 100 { return nil } self.age = age } } I am having a list of ages against which I have to make Person Objects. I have made playground file. let arr = Array(1...150) //Sample set of ages against which Person is created var personList: [Person]! and personList = arr.map({ (val: Int) -> Person? in return Person(age: val) //Makes object of type Person? }).filter { $0 != nil }.map { return $0! } Here I have uses map - filter - map because the first map invokes failable intializer, (hence it returns Person?) and personList is of type [Person]. Hence second function filters all the non nil objects and third map forcefully opens to optional therby making Person? to Person. Is there a more easy/readable way out ? Chaining map-filter-map definitely seems to be an overkill for this
You can use flatMap to get rid of any nils in the array, this tutorial discusses the method in length, but the following will work best: let personList = arr.flatMap { Person(age: $0) }
Note: This answer was given for Swift 1.2, the current version at the time the question was posted. Since Swift 2 there is a better solution, see #Jeremie's answer. I don't know of a built-in function that combines filter() and map(). You can write the code slightly more compact using the shorthand argument $0 in all closures: let personList = arr.map { Person(age: $0) } .filter { $0 != nil } .map { $0! } Of course you can define your own extension method which maps the array elements and keeps only the non-nil results: extension Array { func optmap<U>(transform: T -> U?) -> [U] { var result : [U] = [] for elem in self { if let mapped = transform(elem) { result.append(mapped) } } return result } } and then use it as let personList = arr.optmap { Person(age: $0) }
You can use compactMap which is better that flatMap in this case to remove any nils in the array: let personList = arr.compactMap { Person(age: $0) } The Swift document declared: Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.