Search code examples
swiftblockreducecounting

Feel the "need for speed" Using reduce(into:_:) to count elements in enclosing array of sets of triplet elements


given this example array of triplets, real arrays would be very large data sets, hence the "need for speed":

let usedIndicies2 = [
    [0, 1, 2]
    [2, 1, 3],
    [1, 4, 3],
    [3, 4, 5],
    [6, 7, 8],
    [8, 7, 9],
    [7, 10, 9],
    [9, 10, 11],
    [12, 2, 13],
    [13, 2, 14],
    [2, 3, 14],
    [14, 3, 15],
    [3, 5, 15],
    [15, 5, 16],
    [5, 17, 16],
    [16, 17, 18],
    [19, 8, 20],
    [20, 8, 21],
    [8, 9, 21],
    [21, 9, 22],
    [9, 11, 22],
    [22, 11, 23],
    [11, 24, 23],
    [23, 24, 25],
    [13, 14, 26],
    [26, 14, 27],
    [14, 15, 27],
    [27, 15, 28],
    [15, 16, 28],
    [28, 16, 29],
    [16, 18, 29],
    [29, 18, 30],
    [18, 20, 30],
    [30, 20, 31],
    [20, 21, 31],
    [31, 21, 32],
    [21, 22, 32],
    [32, 22, 33],
    [22, 23, 33],
    [33, 23, 34],
    [26, 27, 35],
    [35, 27, 36],
    [27, 28, 36],
    [36, 28, 37],
    [28, 29, 37],
    [37, 29, 38],
    [29, 30, 38],
    [38, 30, 39],
    [30, 31, 39],
    [39, 31, 40],
    [31, 32, 40],
    [40, 32, 41],
    [32, 33, 41],
    [41, 33, 42],
    [33, 34, 42],
    [42, 34, 43],
    [44, 35, 45],
    [45, 35, 46],
    [35, 36, 46],
    [46, 36, 47],
    [36, 37, 47],
    [47, 37, 48],
    [37, 38, 48],
    [48, 38, 49],
    [38, 39, 49],
    [49, 39, 50],
    [39, 40, 50],
    [50, 40, 51],
    [40, 41, 51],
    [51, 41, 52],
    [41, 42, 52],
    [52, 42, 53],
    [42, 43, 53],
    [53, 43, 54],
    [43, 55, 54],
    [54, 55, 56],
]

Using swift, find the counts of each element of this type of array, while preserving the triplets, so the output would look like this:

let keyValuePairs =[
  [[0: 1], [1: 3], [2: 5]],
  [[2: 5], [1: 3], [3: 6]],
  [[1: 3], [4: 2], [3: 6]],
  [[3: 6], [4: 2], [5: 4]],
  [[6: 1], [7: 3], [8: 5]],
  [[8: 5], [7: 3], [9: 6]],
  [[7: 3], [10: 2], [9: 6]],
  [[9: 6], [10: 2], [11: 4]],
  // etc. ...
]

I am only suggesting "reduce(into" as it feels like it is possible, but it maybe something else entirely that is better/faster/stronger.

(does not have to preserve the order of the triplet sets, but must preserver the order of elements inside the triplet: for instance the first and second one needs to stay [[0:x], [1:x], [2:x]], [[2:x], [1:x], [3:x]], or as an example the order could [[2:x], [1:x], [3:x]], [[0:x], [1:x], [2:x]] instead.

So far I've been able to produce the correct organization using this code below, it feels like this could be solved with reduce(into:_:) but I couldn't correctly "count" the elements.. i've left the code in that doesn't count correctly, obviously because it is intialized each time, but wanted to show what I had so far:

let keyValuePairs = usedIndicies2.reduce(into: [[[UInt32:Int]]]()) { (array, entry) in

    var dict0:[UInt32:Int] = [UInt32:Int]()
    var dict1:[UInt32:Int] = [UInt32:Int]()
    var dict2:[UInt32:Int] = [UInt32:Int]()

    dict0[entry[0], default: 0] += 1
    dict1[entry[1], default: 0] += 1
    dict2[entry[2], default: 0] += 1

    var array012:[[UInt32:Int]] = [[UInt32:Int]]()

    array012.append(dict0)
    array012.append(dict1)
    array012.append(dict2)

    array.append(array012)
}

print("keyValuePairs:",keyValuePairs)

the output of this partially working code looks like this:

let keyValuePairs** = [
    [[0: 1], [1: 1], [2: 1]],
    [[2: 1], [1: 1], [3: 1]],
    [[1: 1], [4: 1], [3: 1]],
    [[3: 1], [4: 1], [5: 1]],
    [[6: 1], [7: 1], [8: 1]],
    [[8: 1], [7: 1], [9: 1]],
    [[7: 1], [10: 1], [9: 1]],
    [[9: 1], [10: 1], [11: 1]],
    // etc...
]

would greatly appreciate your insights


Solution

  • Two steps:

    1. Flatten the array and just make an ordinary histogram (count) for all values (that's a one-liner).

    2. Now go back to the original array and map it into the array of pairs.

    To demonstrate, I'll just operate on the opening piece of your data just to prove it works. I've given my own interpretation of the notion of a key value pair (I've used a tuple with labels), but feel free to patch that up as desired.

    let original = [[0, 1, 2], [2, 1, 3], [1, 4, 3], [3, 4, 5]]
    // step 1
    var histo = [Int:Int]()
    original.flatMap {$0}.forEach {histo[$0, default:0] += 1}
    // step 2
    let output = original.map {array in array.map {i in (item:i, count:histo[i]!)}}
    

    Result:

    [[(item: 0, count: 1), (item: 1, count: 3), (item: 2, count: 2)], 
     [(item: 2, count: 2), (item: 1, count: 3), (item: 3, count: 3)], 
     [(item: 1, count: 3), (item: 4, count: 2), (item: 3, count: 3)], 
     [(item: 3, count: 3), (item: 4, count: 2), (item: 5, count: 1)]]
    

    And that is correct.