ConteOlaf
ConteOlaf

Reputation: 75

Array map, Swift

I have a custom Rules class.

class Rules: NSCoding {

var x: String?
var y: Double?

override func mapping(map: Map) {
    self.x     <- map["x"]
    self.y     <- map["y"]
}

In my viewModel I need to create an object rules and to pass 1 by 1 the elements of two array. The first array is composed by 3 strings, the second array has some Double (more than 3 !!) This is what I tried so far:

let xValues = ["String1", "String2", "String3"]
let yValues = [1.0, 2.0, 1.5, 2.5, 5.1, 6.0, 8.0]

let rules = zip(xValues, yValues).map {
    Rules(x: "\($0.0)", y: $0.1)
}

The problem with this (I guess) is that my rules object has some duplicated string or sometimes more than what I have in my xValues array. (It's possible I am doing something wrong somewhere else ...)

What I need is to pass exactly three strings, and a number of Double which is different, lets say 6 double.

Upvotes: 4

Views: 356

Answers (2)

Gereon
Gereon

Reputation: 17872

Since zip only returns tuples for those indices where both input arrays have have values, you'll need a method that fills up the smaller array instead, like this:

func zipFill<T, U>(_ arr1: [T], _ arr2: [U]) -> [(T?, U?)] {
    let c1 = arr1.count
    let c2 = arr2.count
    let count = max(c1, c2)

    var result = [(T?, U?)]()

    for i in 0..<count {
        if i < c1 && i < c2 {
            result.append((arr1[i], arr2[i]))
        } else if i >= c1 {
            result.append((nil, arr2[i]))
        } else if i >= c2 {
            result.append((arr1[i], nil))
        }
    }

    return result
}

let xValues = ["String1", "String2", "String3"]
let yValues = [1.0, 2.0, 1.5, 2.5, 5.1, 6.0, 8.0]

let rules = zipFill(xValues, yValues).map {
    Rules(x: $0.0, y: $0.1)
}

print(rules)  
// [ {x "String1", y 1}, {x "String2", y 2}, {x "String3", y 1.5},
//   {x nil, y 2.5}, {x nil, y 5.1}, {x nil, y 6}, {x nil, y 8} ]

Upvotes: 2

Bradley Mackey
Bradley Mackey

Reputation: 7678

Why don't you just remove duplicates before creating your rules?

Define a generic extension to remove duplicates:

extension RangeReplaceableCollection {

    func removingDuplicates<E>(keyPath path: KeyPath<Element, E>) -> Self 
        where E: Hashable 
    {
        var seen = Set<E>()
        seen.reserveCapacity(count)
        var new = self
        new.removeAll { element -> Bool in
            if seen.contains(element[keyPath: path]) {
                return true
            } else {
                seen.insert(element[keyPath: path])
                return false
            }
        }
        return new
    }

}

Then remove duplicates before zipping:

let xValues = ["String1", "String2", "String3"].removingDuplicates(keyPath: \.self)
let yValues = [1.0, 2.0, 1.5, 2.5, 5.1, 6.0, 8.0].removingDuplicates(keyPath: \.self)

let rules = zip(xValues, yValues).map {
    Rules(x: $0.0, y: $0.1)
}

Tidbit: you don't need to use String interpolation for the x argument, because the parameter $0.0 is already a String.

Upvotes: 1

Related Questions