Is there a simple way to traverse a multi-dimensional array and transform each element in Swift? - ios

If I have an multi-dimensional array of latitudes and longitudes like:
let inputLatLongArray = [[[
["-100.7777777775", "99.2222222"],
["-100.777777777", "87.2222222"],
["-100.777777777", "34.2222222"],
["-100.777777777", "99.2222222"],
["-100.777777777", "99.2222222"]
]]]
How can I traverse & transform the elements so each nested lat/long only has 2 decimal places?
Expected output:
let expectedLatLongArray = [[[
["-100.77", "99.22"],
["-100.77", "87.22"],
["-100.77", "34.22"],
["-100.77", "99.22"],
["-100.77", "99.22"]
]]]
Code:
Here's what I have so far below. My thought was to use a flatMap and then map down into the nested arrays but this doesn't seem correct either. Is there a simple way to do this?
func roundLatAndLong(with latLongArray: [[[[String, String]]]]) -> [[[[String, String]]]] {
let coordinates = latLongArray.flatMap { $0 }.map({ $0 }).map({ //Rounded both array positions down to 2 decimal places })
return coordinates
}
func rounded(toDecimalPlaces n: Int) -> String {
return String(format: "%.\(n)f", self)
}

If your data type is Double, there is no such thing as having a number of decimal places. If you try to round a value to a given number of decimal places, you'll get a value close to, but not exactly, that value. That's because binary floating point can't represent most decimal values exactly. If you really want to convert your values to values with an exact number of decimal places, you should use the Decimal type.
You COULD convert your array of lat/long Double values to Strings with a given number of decimal places pretty easily.
If you had an array of structs containing lat/longs, or an array of tuples, it would be a lot cleaner. Or even an array of dictionaries with keys of "lat" and "long"

You could do something like this:
func nestedTransform<InElement, OutElement>(nested: [[[[InElement]]]], transform: (InElement) -> OutElement) -> [[[[OutElement]]]]
{
return nested.map {$0.map { $0.map { $0.map(transform) }}}
}
I think in your attempt you just had your nesting a little off, that is you were chaining instead of nesting the map calls.
You would use this like this:
let roundedResult = nestedTransform(nested: inputLatLongArray) { $0.rounded(toDecimalPlaces: 8) }
Assuming the existence to a suitable extension on InElement. You edited the question to remove the original Double array. So this answer serves to solve the mapping of nested arrays. The implementation of the transform is left up to you!
I also strongly encourage you to read the answer by #DuncanC and the comments by #LeoDaubus about why your original transform is ill-advised.

You can gradually extend Array to allow transformations on multi-dimensional ones:
extension Array {
func map2<T, U>(_ transform: (T) -> U) -> [[U]] where Element == [T] {
map { $0.map(transform) }
}
func map3<T,U>(_ transform: (T) -> U) -> [[[U]]] where Element == [[T]] {
map { $0.map2(transform) }
}
func map4<T,U>(_ transform: (T) -> U) -> [[[[U]]]] where Element == [[[T]]] {
map { $0.map3(transform) }
}
}
You can then use the extension like this:
let inputLatLongArray = [[[
["-98.73264361706984", "38.5447223260328"],
["-98.73255257987707", "38.543630550793544"],
["-98.7302159585956", "38.54506646913993"],
["-98.73200635672036", "38.54556488037536"],
["-98.73264361706984", "38.5447223260328"]
]]]
let transform: (String) -> String = {
return String(format: "%.\(8)f", Double($0) ?? 0.0)
}
let transformedArray = inputLatLongArray.map4(transform)
print(transformedArray)

Related

How to use firstIndex in Switft to find all results

I am trying to split a string into an array of letters, but keep some of the letters together. (I'm trying to break them into sound groups for pronunciation, for example).
So, for example, all the "sh' combinations would be one value in the array instead of two.
It is easy to find an 's' in an array that I know has an "sh" in it, using firstIndex. But how do I get more than just the first, or last, index of the array?
The Swift documentation includes this example:
let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
if let i = students.firstIndex(where: { $0.hasPrefix("A") }) {
print("\(students[i]) starts with 'A'!")
}
// Prints "Abena starts with 'A'!"
How do I get both Abena and Akosua (and others, if there were more?)
Here is my code that accomplishes some of what I want (please excuse the rather lame error catching)
let message = "she sells seashells"
var letterArray = message.map { String($0)}
var error = false
while error == false {
if message.contains("sh") {
guard let locate1 = letterArray.firstIndex(of: "s") else{
error = true
break }
let locate2 = locate1 + 1
//since it keeps finding an s it doesn't know how to move on to rest of string and we get an infinite loop
if letterArray[locate2] == "h"{
letterArray.insert("sh", at: locate1)
letterArray.remove (at: locate1 + 1)
letterArray.remove (at: locate2)}}
else { error = true }}
print (message, letterArray)
Instead of first use filter you will get both Abena and Akosua (and others, if there were more?)
extension Array where Element: Equatable {
func allIndexes(of element: Element) -> [Int] {
return self.enumerated().filter({ element == $0.element }).map({ $0.offset })
}
}
You can then call
letterArray.allIndexes(of: "s") // [0, 4, 8, 10, 13, 18]
You can filter the collection indices:
let students = ["Kofi", "Abena", "Peter", "Kweku", "Akosua"]
let indices = students.indices.filter({students[$0].hasPrefix("A")})
print(indices) // "[1, 4]\n"
You can also create your own indices method that takes a predicate:
extension Collection {
func indices(where predicate: #escaping (Element) throws -> Bool) rethrows -> [Index] {
try indices.filter { try predicate(self[$0]) }
}
}
Usage:
let indices = students.indices { $0.hasPrefix("A") }
print(indices) // "[1, 4]\n"
or indices(of:) where the collection elements are Equatable:
extension Collection where Element: Equatable {
func indices(of element: Element) -> [Index] {
indices.filter { self[$0] == element }
}
}
usage:
let message = "she sells seashells"
let indices = message.indices(of: "s")
print(indices)
Note: If you need to find all ranges of a substring in a string you can check this post.
Have fun!
["Kofi", "Abena", "Peter", "Kweku", "Akosua"].forEach {
if $0.hasPrefix("A") {
print("\($0) starts with 'A'!")
}
}
If you really want to use the firstIndex method, here's a recursive(!) implementation just for fun :D
extension Collection where Element: Equatable {
/// Returns the indices of an element from the specified index to the end of the collection.
func indices(of element: Element, fromIndex: Index? = nil) -> [Index] {
let subsequence = suffix(from: fromIndex ?? startIndex)
if let elementIndex = subsequence.firstIndex(of: element) {
return [elementIndex] + indices(of: element, fromIndex: index(elementIndex, offsetBy: 1))
}
return []
}
}
Recursions
Given n instances of element in the collection, the function will be called n+1 times (including the first call).
Complexity
Looking at complexity, suffix(from:) is O(1), and firstIndex(of:) is O(n). Assuming that firstIndex terminates once it encounters the first match, any recursions simply pick up where we left off. Therefore, indices(of:fromIndex:) is O(n), just as good as using filter. Sadly, this function is not tail recursive... although we can change that by keeping a running total.
Performance
[Maybe I'll do this another time.]
Disclaimer
Recursion is fun and all, but you should probably use Leo Dabus' solution.

Finding closest value in array of custom objects in swift

Hi I do have array of custom objects in swift, like below
Objects of below class
Class Person {
let name: String
let pointsEarned: CGFloat
}
Array is like below
let person1 = Person(“name1”, “5.6”)
let person2 = Person(“name2”, “6.6”)
let person3 = Person(“name3”, “1.6”)
let persons = [person1, person2, person3 ]
I would like find person who’s earned points are close to 7.0
Is there extension on array I can write for this?
Appreciate any help! Thanks.
Sort your objects by their distance from the goal (7), computed asabs(goal - score)`:
people.sort { abs(7 - $0.score) < abs(7 - $1.score) }
Alexander's answer is good, but you only need the min.
public extension Sequence {
func min<Comparable: Swift.Comparable>(
by getComparable: (Element) throws -> Comparable
) rethrows -> Element? {
try self.min {
try getComparable($0) < getComparable($1)
}
}
}
I also think abs as a global function looks archaic. magnitude is the same value.
persons.min { ($0.pointsEarned - 7).magnitude }
You can use the argument label with a trailing closure if you want:
persons.min(by:) { ($0.pointsEarned - 7).magnitude }

Swift Filter and Map over array of structs

I may have this really wrong. Noob. I've recreated what I'm doing in a playground type environment here. Basically the sender is a slider within a UITableView of other sliders. The myData is the underlying data. I want to perform a calculation to all the items of the underlying data EXCEPT the one which corresponds to the sender item. I have no idea if my closure syntax is correct. This is the first time I'm creating one.
// sender comes over as a struct
struct myStruct {
var tag: Int = 0
var value: Float = 0
}
let sender = myStruct(tag: 1, value: 199)
// some vars for the calculation
let globalTotal: Float = 597
let globalAnotherTotal: Float = 0
// an array of data structs
struct myDataStruct {
var name: String = ""
var value: Float = 0
}
var myData: [myDataStruct] = []
myData.append(myDataStruct(name: "Tom", value: 45.0))
myData.append(myDataStruct(name: "Dick", value: 16.4))
myData.append(myDataStruct(name: "Harry", value: 12.3))
// a closure to do the calculation
var calcOtherVals: (Float, Float) -> (Float) = { (startVal, senderStartVal) in
let remainingStartVals = globalTotal - senderStartVal
let remainingNewVal = globalTotal - sender.value - globalAnotherTotal
let endVal = ((startVal * (100 / remainingStartVals)) / 100) * remainingNewVal
return endVal
}
// now need to perform calcOtherVals on all the .value floats in myData EXCEPT the element at position sender.tag hopefully using filter and map
So basically I'm trying to use filter and map and the calcOtherVals closure to edit the array of structs in place. I can do this with conditionals and loops and calcOtherVals as a function no problem. Just hoping to do it more elegantly.
QUESTION: As in the code comment, I need to perform calcOtherVals on all the .value floats in myData EXCEPT the element at position sender.tag. How?
myData.enumerated().flatMap { (index, element) in return index != sender.tag ? calcOtherVals (element.value) : nil }
Few bits of swift magic here. Firstly enumerate() returns an array of tuples containing the element, and the index of said element.
Next flatMap(). This is essentially map but it ignores any transform that resolves to nil. Great for converting from an optional array to a flat array, and also great if you wish to do a map+filter operation such as this.
-- Updated --
If you're comfortable with implicit arguments, you can reduce it further:
myData.enumerated().flatMap { $0.offset != sender.tag ? calcOtherVals ($0.element.value) : nil }
So as I understood you need to filter your array
something like
let filteredData = myData.filter({$0.tag != sender.tag})
then you use reduce to calculate
let sumAll = filterdData.reduce(0, {$0.value + $1.value})
This Question is Already have an answer,
for better understanding you can review this very good tutorial about map, filter and reduce
Link:- https://useyourloaf.com/blog/swift-guide-to-map-filter-reduce/

Swift3 Random Extension Method

I was using this extension method to generate a random number:
func Rand(_ range: Range<UInt32>) -> Int {
return Int(range.lowerBound + arc4random_uniform(range.upperBound - range.lowerBound + 1))
}
I liked it b/c it was no nonsense, you just called it like this:
let test = Rand(1...5) //generates a random number between 1 and 5
I honestly don't know why things need to be so complicated in Swift but I digress..
So i'm receiving an error now in Swift3
No '...' candidates produce the expected contextual result type 'Range<UInt32>'
Would anyone know what this means or how I could get my awesome Rand function working again? I guess x...y no longer creates Ranges or x..y must be explicitly defined as UInt32? Any advice for me to make things a tad easier?
Thanks so much, appreciate your time!
In Swift 3 there are four Range structures:
"x" ..< "y" ⇒ Range<T>
"x" ... "y" ⇒ ClosedRange<T>
1 ..< 5 ⇒ CountableRange<T>
1 ... 5 ⇒ CountableClosedRange<T>
(The operators ..< and ... are overloaded so that if the elements are stridable (random-access iterators e.g. numbers and pointers), a Countable Range will be returned. But these operators can still return plain Ranges to satisfy the type checker.)
Since Range and ClosedRange are different structures, you cannot implicitly convert a them with each other, and thus the error.
If you want Rand to accept a ClosedRange as well as Range, you must overload it:
// accepts Rand(0 ..< 5)
func Rand(_ range: Range<UInt32>) -> Int {
return Int(range.lowerBound + arc4random_uniform(range.upperBound - range.lowerBound))
}
// accepts Rand(1 ... 5)
func Rand(_ range: ClosedRange<UInt32>) -> Int {
return Int(range.lowerBound + arc4random_uniform(range.upperBound + 1 - range.lowerBound))
}
A nice solution is presented in Generic Range Algorithms
(based on How to be DRY on ranges and closed ranges? in the swift-users mailing list).
It uses the fact that both CountableRange and CountableClosedRange
are collections, and in fact a RandomAccessCollection.
So you can define a single (generic) function which accepts both open and closed
integer ranges:
func rand<C: RandomAccessCollection>(_ coll: C) -> C.Iterator.Element {
precondition(coll.count > 0, "Cannot select random element from empty collection")
let offset = arc4random_uniform(numericCast(coll.count))
let idx = coll.index(coll.startIndex, offsetBy: numericCast(offset))
return coll[idx]
}
rand(1...5) // random number between 1 and 5
rand(2..<10) // random number between 2 and 9
but also:
rand(["a", "b", "c", "d"]) // random element from the array
Alternatively as a protocol extension method:
extension RandomAccessCollection {
func rand() -> Iterator.Element {
precondition(count > 0, "Cannot select random element from empty collection")
let offset = arc4random_uniform(numericCast(count))
let idx = index(startIndex, offsetBy: numericCast(offset))
return self[idx]
}
}
(1...5).rand()
(2..<10).rand()
["a", "b", "c", "d"].rand()
You could rewrite Rand() to use Int if that is your primary use case:
func Rand(_ range: Range<Int>) -> Int {
let distance = UInt32(range.upperBound - range.lowerBound)
return range.lowerBound + Int(arc4random_uniform(distance + 1))
}
Or as kennytm points out, use Rand(1..<6)

Counting number of Arrays that contain the same two values

Given a Dictionary<String, Arrary<Int>> find the how many entries have the same two specified values in the first 5 entries in the Array<Int>.
For example:
Given:
let numberSeries = [
"20022016": [07,14,36,47,50,02,05],
"13022016": [16,07,32,36,41,07,09],
"27022016": [14,18,19,31,36,04,05],
]
And the values: 7 and 36, the result should be 2 since the first and second entry have both the values 7 and 36 in the first 5 entries of the entry's array.
I've tried to accomplish this many ways, but I haven't been able to get it to work.
This is my current attempt:
//created a dictionary with (key, values)
let numberSeries = [
"20022016": [07,14,36,47,50,02,05],
"13022016": [16,07,32,36,41,07,09],
"27022016": [14,18,19,31,36,04,05],
]
var a = 07 //number to look for
var b = 36 // number to look for
// SearchForPairAB // search for pair // Doesn't Work.
var ab = [a,b] // pair to look for
var abPairApearedCount = 0
for (kind, numbers) in numberSeries {
for number in numbers[0...4] {
if number == ab { //err: Cannot invoke '==' with argument listof type Int, #Value [Int]
abPairApearedCount++
}
}
}
This gives the error: Cannot invoke '==' with argument listof type Int, #Value [Int] on the line: if number == ab
You can't use == to compare an Int and Array<Int>, that just doesn't make any sense from a comparison perspective. There are lots of different ways you can achieve what you're trying to do though. In this case I'd probably use map/reduce to count your pairs.
The idea is to map the values in your ab array to Bool values determined by whether or not the value is in your numbers array. Then, reduce those mapped Bools to a single value: true if they're all true, or false. If that reduced value is true, then we found the pair so we increment the count.
var ab = [a,b] // pair to look for
var abPairApearedCount = 0
for (kind, numbers) in numberSeries {
let found = ab.map({ number in
// find is a built-in function that returns the index of the value
// in the array, or nil if it's not found
return find(numbers[0...4], number) != nil
}).reduce(true) { (result, value: Bool) in
return result && value
}
if found {
abPairApearedCount++
}
}
That can actually be compacted quite a bit by using some of Swift's more concise syntax:
var ab = [a,b] // pair to look for
var abPairApearedCount = 0
for (kind, numbers) in numberSeries {
let found = ab.map({ find(numbers[0...4], $0) != nil }).reduce(true) { $0 && $1 }
if found {
abPairApearedCount++
}
}
And, just for fun, can be compacted even further by using reduce instead of a for-in loop:
var ab = [a,b] // pair to look for
var abPairApearedCount = reduce(numberSeries, 0) { result, series in
result + (ab.map({ find(series.1[0...4], $0) != nil }).reduce(true) { $0 && $1 } ? 1 : 0)
}
That's getting fairly unreadable though, so I'd probably expand some of that back out.
So here's my FP solution, aimed at decomposing the problem into easily digestible and reusable bite-sized chunks:
First, we define a functor that trims an array to a given length:
func trimLength<T>(length: Int) -> ([T]) -> [T] {
return { return Array($0[0...length]) }
}
Using this we can trim all the elements using map(array, trimLength(5))
Now, we need an predicate to determine if all the elements of one array are in the target array:
func containsAll<T:Equatable>(check:[T]) -> ([T]) -> Bool {
return { target in
return reduce(check, true, { acc, elem in return acc && contains(target, elem) })
}
}
This is the ugliest bit of code here, but essentially it's just iterating over check and insuring that each element is in the target array. Once we've got this we can use filter(array, containsAll([7, 26])) to eliminate all elements of the array that don't contain all of our target values.
At this point, we can glue the whole thing together as:
filter(map(numberSeries.values, trimLength(5)), containsAll([7, 36])).count
But long lines of nested functions are hard to read, let's define a couple of helper functions and a custom operator:
func rmap<S:SequenceType, T>(transform:(S.Generator.Element)->T) -> (S) -> [T] {
return { return map($0, transform) }
}
func rfilter<S:SequenceType>(predicate:(S.Generator.Element)->Bool) -> (S) -> [S.Generator.Element] {
return { sequence in return filter(sequence, predicate) }
}
infix operator <^> { associativity left }
func <^> <S, T>(left:S, right:(S)->T) -> T {
return right(left)
}
And a convenience function to count it's inputs:
func count<T>(array:[T]) -> Int {
return array.count
}
Now we can condense the whole thing as:
numberSeries.values <^> rmap(trimLength(5)) <^> rfilter(containsAll([7, 36])) <^> count

Resources