2

Given an array of numbers like this:

[0, 99, 299, 498, 901]

What algorithm could I use to resize that array into an array with approximately equal distances between them. Or said another way, resample with approximate greatest common multiples. So with the example above the Greatest Common Divisor is approximately 100, thus the result would be:

[0, 99, 200, 299, 400, 498, 600, 700, 800, 901]

Using the original values would be nice, and error bars can be set (above solution has error set to 2), but would also be happy with this result:

[0, 100, 200, 300, 400, 500, 600, 700, 800, 900]

Update 12 Jan 2017

Based on Redu's answer, here is the Swift version of his code:

var arr: [Int] = [0, 99, 299, 498, 901]

var diffs = [Int]()
var minGap: Int = 0
for x in 0..<arr.count-1 {
    let gap = arr[x+1] - arr[x]
    diffs.append(gap)
    if minGap == 0 || minGap > gap {
        minGap = gap
    }
}

var resamples = [Int]()
for item in arr {
    if let lastSample = resamples.last {
        let n = Int((Float(item - lastSample) / Float(minGap)).rounded())
        let g = (item - lastSample) / n
        var inserts = [Int]()
        for x in 0..<n-1 {
            let newSample = lastSample + ((x+1) * g)
            inserts.append(newSample)
        }
        resamples.append(item)
        resamples.append(contentsOf: inserts)
    } else {
        resamples.append(item)
    }
}
2
  • 1
    Presumably you want the result to be rounded. i.e. 100, 200, 300 as opposed to 99, 198, 297. Commented Jan 11, 2017 at 17:07
  • Hmm, I suppose I could round to the preferred error level. So if I round to nearest 10, the smallest GCD value would be ten. Commented Jan 11, 2017 at 17:14

2 Answers 2

2

Essentially you want to use a least squares regression against an arithmetic progression.

An arithmetic progression can be parameterised with 3 terms: first term, last term, and common difference. These 3 terms would form the parameters of your objective function, which you will seek to minimise.

In each optimisation step, you'd need to pick which terms in the trial arithmetic progression need to be regressed against your original set. That will be quite challenging, but luckily both series will be sorted so this ought to be an O(N) traversal.

A constraint around the 3 terms would be a set that is typographically pleasing. For example, would 100, 200, 300 be preferred over 99, 198, 297 even if the source series is 99, 297?

A full answer would I feel be too broad - and is probably at least a week's work. But this is how I would embark on the project.

Sign up to request clarification or add additional context in comments.

Comments

1

The following would be my solution in JS. I first find the minimum gap and then try to find how many of that would fit in-between each item and process accordingly without altering the original values.

Obviously for this algorithm to work the input array must be sorted in ascending order.

var arr = [0, 99, 299, 498, 901],
    gap = Math.min(...Array(arr.length-1).fill().map((_,i) => arr[i+1]-arr[i])),  // Find the minimum gap
    res = arr.reduce((p,c,i) => { var n = Math.round((c-p[p.length-1])/gap);      // Find howmany gaps are inbetween according to the minimum gap
                                      g = Math.round((c-p[p.length-1])/n);        // Calculate the average gap top apply
                                  return i ? p.concat(Array(Math.round(n-1)).fill().map((_,i) => p[p.length-1] + (i+1)*g),c)
                                           : p.concat(c);
                                },[]);
console.log(res);

Explanation:

gap = Math.min(...Array(arr.length-1).fill().map((_,i) => arr[i+1]-arr[i])),

First we set up a new array in size one less than the input array. (Array(arr.length-1)) first we initialize (.fill()) it with undefined elements and then .map() every element with arr[i+1]-arr[i]. So now we have the gaps array. Then we spread it into a Math.min() function as arguments. It's the Math.min(...Array( part. So now we have the minimum gap as 99 in the above given case.

res = arr.reduce((p,c,i) => { var n = Math.round((c-p[p.length-1])/gap);
                                  g = Math.round((c-p[p.length-1])/n);
                              return i ? p.concat(Array(Math.round(n-1)).fill().map((_,i) => p[p.length-1] + (i+1)*g),c)
                                       : p.concat(c);
                            },[]);

.reduce() part is slightly tough looking but it's easy. Our .reduce() operation takes a function as it's argument (mostly known as a callback function) and runs it with every iteration over the array items. This callback function is the part which starts with (p,c,i) => {... }. This is an arrow function. Which is essentially same with normal functions. x => x means function(x) { return x;} or x => {return x;}. In our case since we use braces to define the body of our function (due to multiple statements) we will have to use a return instruction.

Our .reduce() uses an initial value which is an empty array. It's the ,[]); part at the very end. The callback function, which reduce will invoke per array item, will be passed three arguments (p,c,i) The initial empty array gets assigned to the p (previous) argument, the current item gets assigned to the c argument and the current index gets assigned to the i argument per call.

In the body of our callback we define 2 variables. n and g.

n = Math.round((c-p[p.length-1])/gap);

p[p.length-1] returns the last element of the p array. So in the first turn; when i = 0, p[0] is undefined and Math.round((c-p[p.length-1])/gap); is a NaN (Not a Number) but we don't care because;

return i ? p.concat(Array(Math.round(n-1)).fill().map((_,i) => p[p.length-1] + (i+1)*g),c)
         : p.concat(c);

The ternary conditional means that;

result = condition ? if true do this
                   : if false do this

So as you see depending on the condition it does either one of the instructions and returns the result. In our case the result is returned as the value of p.

So in our case if i == 0 (false value in JS) then only do p.concat(c) and return the new p value and continue with the next iteration (invoke callback with the new p, c and i values.

If i is not false (any value other than 0) then do like

p.concat(Array(Math.round(n-1)).fill().map((_,i) => p[p.length-1] + (i+1)*g),c)

Which means create an array in the size to take the gap many interim elements, initialize the array with undefineds and map each element with p[p.length-1] + (i+1)*g and concatenate this array to the p array and append c to the very end and then return the p array.

One thing to remind: p.concat(whatever...) instruction would return a new array consisting of the elements of p and the "items" of the arrays included as argument or the items itself included ar argument. I mean;

[1,2,3].concat([4,5,6],[7,8],9) would result [1,2,3,4,5,6,7,8,9]

So this should explain it.

5 Comments

That's a nifty solution. I think this would be much more efficient than finding the GCD that my original idea alluded to.
@elprl Thanks.. I would like to criticize myself here... Reading my answer once again, the last sentence doesn't make much sense to me :) Math.abs(c-p[p.length-1]) should suffice for this to work with unsorted arrays as well.
I have to say that JS is rather tricky to follow. I'm not a JS expert but I am a Swift expert and I'm having trouble following the "return i" line and converting that code. Can you explain what's going on there?
@elpri It's early morning here. Later today i will include a detailed explanation of the code under my answer. In short gap is the minimum distance and res is just a JS functional reduce instruction which takes a function and applies it to each element of an array. In this case it works on the arr array. Give me a few hours and i will give the details.
@elpri OK added explanation i hope it helps. :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.