Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Functional clustering of array values in Swift

Given an array, like:

[0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0] 

I want to cluster together values into subarrays based on their sequential differences (e.g., where abs(x-y) < n, and n = 0.2), e.g.:

[[0], [0.5, 0.51], [1.0], [1.5], [1.99, 2.0, 2.1], [2.5], [3.0]]. 

I'd like to do it declaratively — just to get a better grasp on how more complex sequence operations might work in a functional context (it seems like most "functional Swift" demos/tutorials are pretty basic).

Thanks in advance.


Update:

Here's a one-liner that's kinda close:

let times = [0, 0.5, 0.99, 1, 1.01, 1.5, 2, 2.5, 2.51, 3, 3.49, 3.5]

let result = times.map { t1 in
    return times.filter { fabs($0 - t1) < 0.2 }
}

// [[0.0], [0.5], [0.99, 1.0, 1.01], [0.99, 1.0, 1.01], [0.99, 1.0, 1.01], [1.5], [2.0], [2.5, 2.51], [2.5, 2.51], [3.0], [3.49, 3.5], [3.49, 3.5]]

Just need to get rid of duplicates.

like image 562
jbm Avatar asked Dec 19 '22 23:12

jbm


2 Answers

A simple fold with an accumulating parameter works. Btw not sure if that's exactly what you want as I don't understand whether the elements in your array need to be subsequent. In the description you say so, but then your 'sample answer' doesn't take into account if they are subsequent. You should improve the question description.

let a : [Double] = [0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0];
let diff : Double = 0.2;
let eps = 0.0000001

let b = a.sort().reduce(([],[])) { (ps : ([Double],[[Double]]), c : Double) -> ([Double],[[Double]]) in
  if ps.0.count == 0 || abs(ps.0.first! - c) - diff <= eps { return (ps.0 + [c], ps.1) } else { return ([c], ps.1 + [ps.0]) }
}
let result = b.1 + [b.0];
print(result)

Returns

[[0.0], [0.5, 0.51], [1.0], [1.5], [1.99, 2.0, 2.1], [2.5], [3.0]]
like image 170
rafalio Avatar answered Jan 24 '23 12:01

rafalio


I don't know of any native Swift methods that do what you want. You can accomplish this with a simple extension though:

extension Array {
    func split(condition : (Element, Element) -> Bool) -> [[Element]] {
        var returnArray = [[Element]]()

        var currentSubArray = [Element]()

        for (index, element) in self.enumerate() {
            currentSubArray.append(element)

            if index == self.count - 1 || condition(element, self[index+1]) {
                returnArray.append(currentSubArray)
                currentSubArray = []
            }
        }

        return returnArray
    }
}

Example usage:

let source = [0, 0.5, 0.51, 1.0, 1.5, 1.99, 2.0, 2.1, 2.5, 3.0]
let n = 0.2
let target = source.split { abs($0 - $1) > n }

Output:

[[0.0], [0.5, 0.51], [1.0], [1.5], [1.99, 2.0, 2.1], [2.5], [3.0]]
like image 26
Aaron Brager Avatar answered Jan 24 '23 13:01

Aaron Brager