1

For context, this is to control multiple stepper motors simultaneously in a high-accuracy application.

Problem statement
Say I have a loop that will run i iterations. Over the course of those iterations, expression E_x should evaluate to true x times (x <= i is guaranteed).

Requirements
- E_x must evaluate to true exactly x times
- E_x must evaluate to true at more or less evenly spaced intervals*

* "evenly spaced intervals" means that the maximum interval size is minimized

Examples
For: i = 10, x = 7
E_x will be true on iterations marked 1: 1101101101

For: i = 10, x = 3
E_x will be true on iterations marked 1: 0010010010

For: i = 10, x = 2
E_x will be true on iterations marked 1: 0001000100

What is the best (or even "a good") way to have E_x evaluate to true at evenly spaced intervals while guaranteeing that it is true exactly x times?

This question is close to mine, however it assumes that E_x will always evaluate to true in the 1st and last iterations, which does not meet my requirements (see 2nd example above).

Kyle G.
  • 870
  • 2
  • 10
  • 22
  • 2
    https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm – Jasen Jan 06 '19 at 22:46
  • please exaplain the answer you have for i = 10, x = 4, why not `1001001001` – Jasen Jan 06 '19 at 22:47
  • @Jasen that's a good point, I didn't choose the best example. The point was to show that it might not be desirable to have a 1 in the first and/or last position. I'll update my question with better examples. – Kyle G. Jan 06 '19 at 22:58
  • Kyle, am I right that your process is just a single run rather than an infinite loop (a periodical process)? Otherwise your `i = 10`, `x = 2` example looks strange: the gap between the last event in one cycle and the first in the next one is 5 `0`s. – SergGr Jan 06 '19 at 23:08
  • @SergGr you are correct. This will be extended to turn multiple stepper motors simultaneously but at different speeds over a given time frame (in this case i is the time frame and x is the number of steps a specific motor must turn in that time frame). – Kyle G. Jan 06 '19 at 23:10
  • Reminds me of [this question](https://stackoverflow.com/q/34322663/10396). `x` here corresponds to the number of characters there. – AShelly Jan 07 '19 at 05:13

2 Answers2

0

I'll use a bit different naming convention: let's there by T intervals [1..T] and N events to be fired. Also let's solve the problem as a cyclic one. To do the let's add one fake step at the end that we are guaranteed to fire event at (and this will be also the event at time 0 i.e. before the cycle). So my T is your i+1 and my N is your x+1.

If you divide T by N with reminder you'll get T = w*N + r. If r=0 the case is trivial. If r != 0 the best you can achieve is r intervals of size w+1 and (N-r) intervals of size w. The fast and simple but good enough solution would be something like this (pseudocode):

events = []
w = T / N
r = T % N
current = 0
for(i = 1; i<=N; i++) {
   current += w;
   if (i <= r)
      current += 1;
   events[i] = current;
}

You can see that the last value in the array will be T as was promised by our re-statement as a cyclic problem. It will be T because over the cycle we'll add w to current N times and add r times 1, so the sum will be w*N+r which is T.

The main drawback of this solution is that all the "long" intervals will be at the start while all the "short" interval will be at the end.

You can spread intervals more evenly if you are a bit smarter. And the resulting logic will be essentially the same as it is behind Bresenham's line algorithm referenced in comments. Imagine you are drawing a line on a plane, where X-axis represents time and Y-axis represents events, from (0,0) (which is the 0-th event, before your timeframe) to (i+1, x+1) (which is the x+1-th event, just after your timeframe). The moment to raise an event is when you switch to the next Y i.e. draw the first pixel at a given Y.

SergGr
  • 23,570
  • 2
  • 30
  • 51
  • Apologies for the delay, I haven't had a chance to go over your solution yet. I'll be sure to mark an answer as accepted once I've compared them. – Kyle G. Jan 08 '19 at 01:51
  • @KyleG., frankly I don't think that either of the answers is perfect. I don't provide an explicit code for the "better" solution and I believe Matt's code is wrong in the non-cyclic details of your problem (consider the case `x = 2` and `n = 8` where the solution should obviously be `00100100` but I believe that Matt's code produces `01000100`). Also the difference between our answers is that I try to produce an array while Matt's code is something like iterator/generator and it is not clear which is better for your use case. Let me know if you want me to improve my answer with more details. – SergGr Jan 08 '19 at 02:16
  • @KyleG., thanks for accepting my answer. You didn't respond to my last comment but you can still let me know here if you need some further assistance. – SergGr Jan 15 '19 at 20:34
0

If you want to do x increments over n iterations, you can do it like this:

int incCount = 0;
int iterCount = 0;

boolean step() {
    ++iterCount;
    int nextCount = (iterCount*x + n/2) / n; // this is rounding division
    if (nextCount > incCount) {
        ++incCount;
        return true;
    }
    else {
        return false;
    }
}

That's the easy-to-understand way. If you're on an embedded CPU where division is more expensive, you can accomplish exactly the same thing like this:

int accum = n/2;

boolean step() {
    accum+=x;
    if (accum >= n) {
        accum-=n;
        return true;
    }
    else {
        return false;
    }
}

The total amount added to accum here is iterCount*x + n/2 just like the first example, but the division is replaced with an incremental repeated subtraction. This is the way that Bresenham's line drawing algorithm works.

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
  • Apologies for the delay, I haven't had a chance to go over your solution yet. I'll be sure to mark an answer as accepted once I've compared them. – Kyle G. Jan 08 '19 at 01:51