5

Problem: We have x checkboxes and we want to check y of them evenly.

Example 1: select 50 checkboxes of 100 total.

[-]
[x]
[-]
[x]
...

Example 2: select 33 checkboxes of 100 total.

[-]
[-]
[x]
[-]
[-]
[x]
...

Example 3: select 66 checkboxes of 100 total:

[-]
[x]
[x]
[-]
[x]
[x]
...

But we're having trouble to come up with a formula to check them in code, especially once you go 11/111 or something similar. Anyone has an idea?

Shahbaz
  • 46,337
  • 19
  • 116
  • 182
Carra
  • 17,808
  • 7
  • 62
  • 75

8 Answers8

4

Let's first assume y is divisible by x. Then we denote p = y/x and the solution is simple. Go through the list, every p elements, mark 1 of them.

Now, let's say r = y%x is non zero. Still p = y/x where / is integer devision. So, you need to:

  • In the first p-r elements, mark 1 elements
  • In the last r elements, mark 2 elements

Note: This depends on how you define evenly distributed. You might want to spread the r sections withx+1 elements in between p-r sections with x elements, which indeed is again the same problem and could be solved recursively.

Alright so it wasn't actually correct. I think this would do though:

Regardless of divisibility:

  • if y > 2*x, then mark 1 element every p = y/x elements, x times.
  • if y < 2*x, then mark all, and do the previous step unmarking y-x out of y checkboxes (so like in the previous case, but x is replaced by y-x)

Note: This depends on how you define evenly distributed. You might want to change between p and p+1 elements for example to distribute them better.

Shahbaz
  • 46,337
  • 19
  • 116
  • 182
  • If y = 100, x = 66, then: r = 100%66 = 34, and p = 100/66 = 1. This makes p-r negative. – ArjunShankar Nov 28 '11 at 16:21
  • @submachine, yeah, wait, it's something like this, but I'd have to think this better through – Shahbaz Nov 28 '11 at 16:25
  • The thing is, we mentally 'rounded' things off when we saw the 100/66 and made it 3/2. This is an arbitrary decision because we see it's close enough. I don't think this can be approximated on a computer with only integer math. Ofcourse, it would be interesting to see how you adjust to it! – ArjunShankar Nov 28 '11 at 16:37
  • @submachine, see my edit. I wonder though about how to decide when to skin `p-1` and when to skip `p` elements for a better distribution. Somehow I get a feeling this problem is the same as drawing a line in 2D (computer graphics) (You know, Bresenham's algorithm), but the mapping is not so clear in my mind – Shahbaz Nov 28 '11 at 16:45
  • It's interesting how the appeal of your answer was good enough for at least 3 people to upvote it, regardless of the correctness. :) – Daniel Mošmondor Nov 28 '11 at 16:49
  • I was sure I had solved this problem once before but can't seem to find the code :-/ – Shahbaz Nov 28 '11 at 16:51
  • @DanielMošmondor :)) Yeah I was surprised!! – Shahbaz Nov 28 '11 at 16:51
2

Here's a straightforward solution using integer arithmetic:

void check(char boxes[], int total_count, int check_count)
{
    int i;

    for (i = 0; i < total_count; i++)
        boxes[i] = '-';

    for (i = 0; i < check_count; i++)
        boxes[i * total_count / check_count] = 'x';
}

total_count is the total number of boxes, and check_count is the number of boxes to check.

First, it sets every box to unchecked. Then, it checks check_count boxes, scaling the counter to the number of boxes.

Caveat: this is left-biased rather than right-biased like in your examples. That is, it prints x--x-- rather than --x--x. You can turn it around by replacing

        boxes[i * total_count / check_count] = 'x';

with:

        boxes[total_count - (i * total_count / check_count) - 1] = 'x';

Correctness

Assuming 0 <= check_count <= total_count, and that boxes has space for at least total_count items, we can prove that:

  • No check marks will overlap. i * total_count / check_count increments by at least one on every iteration, because total_count >= check_count.

  • This will not overflow the buffer. The subscript i * total_count / check_count

    • Will be >= 0. i, total_count, and check_count will all be >= 0.

    • Will be < total_count. When n > 0 and d > 0:

      (n * d - 1) / d < n
      

      In other words, if we take n * d / d, and nudge the numerator down, the quotient will go down, too.

      Therefore, (check_count - 1) * total_count / check_count will be less than total_count, with the assumptions made above. A division by zero won't happen because if check_count is 0, the loop in question will have zero iterations.

Joey Adams
  • 41,996
  • 18
  • 86
  • 115
1

Say number of checkboxes is C and the number of Xes is N.

You example states that having C=111 and N=11 is your most troublesome case.

Try this: divide C/N. Call it D. Have index in the array as double number I. Have another variable as counter, M.

double D = (double)C / (double)N;
double I = 0.0;
int M = N;
while (M > 0) {
    if (checkboxes[Round(I)].Checked) { //  if we selected it, skip to next
        I += 1.0;
        continue;
    }
    checkboxes[Round(I)].Checked = true;
    M --;
    I += D;
    if (Round(I) >= C) { //  wrap around the end
        I -= C;
    }
}

Please note that Round(x) should return nearest integer value for x.

This one could work for you.

Daniel Mošmondor
  • 19,718
  • 12
  • 58
  • 99
1

I think the key is to keep count of how many boxes you expect to have per check.

Say you want 33 checks in 100 boxes. 100 / 33 = 3.030303..., so you expect to have one check every 3.030303... boxes. That means every 3.030303... boxes, you need to add a check. 66 checks in 100 boxes would mean one check every 1.51515... boxes, 11 checks in 111 boxes would mean one check every 10.090909... boxes, and so on.

double count = 0;
for (int i = 0; i < boxes; i++) {
    count += 1;
    if (count >= boxes/checks) {
        checkboxes[i] = true;
        count -= count.truncate(); // so 1.6 becomes 0.6 - resetting the count but keeping the decimal part to keep track of "partial boxes" so far
    }
}

You might rather use decimal as opposed to double for count, or there's a slight chance the last box will get skipped due to rounding errors.

Toomai
  • 3,974
  • 1
  • 20
  • 22
1

Bresenham-like algorithm is suitable to distribute checkboxes evenly. Output of 'x' corresponds to Y-coordinate change. It is possible to choose initial err as random value in range [0..places) to avoid biasing.

def Distribute(places, stars):
err = places // 2
res = ''
for i in range(0, places):
    err = err - stars
    if err < 0 :
        res = res + 'x'
        err = err + places
    else:
        res = res + '-'
print(res)

Distribute(24,17)
Distribute(24,12)
Distribute(24,5)

output:

x-xxx-xx-xx-xxx-xx-xxx-x

-x-x-x-x-x-x-x-x-x-x-x-x

--x----x----x---x----x--
MBo
  • 77,366
  • 5
  • 53
  • 86
0

Quick html/javascript solution:

<html>
<body>
<div id='container'></div>
<script>
var cbCount = 111;
var cbCheckCount = 11;
var cbRatio = cbCount / cbCheckCount;
var buildCheckCount = 0;

var c = document.getElementById('container');

for (var i=1; i <= cbCount; i++) {
  // make a checkbox
  var cb = document.createElement('input');
  cb.type = 'checkbox';

  test = i / cbRatio - buildCheckCount;
  if (test >= 1) {
    // check the checkbox we just made
    cb.checked = 'checked';
    buildCheckCount++;
  }

  c.appendChild(cb);
  c.appendChild(document.createElement('br'));
}
</script>
</body></html>
James
  • 20,957
  • 5
  • 26
  • 41
0

Adapt code from one question's answer or another answer from earlier this month. Set N = x = number of checkboxes and M = y = number to be checked and apply formula (N*i+N)/M - (N*i)/M for section sizes. (Also see Joey Adams' answer.)

In python, the adapted code is:

  N=100; M=33; p=0;
  for i in range(M):
     k = (N+N*i)/M
     for j in range(p,k-1): print "-",
     print "x",
     p=k

which produces
- - x - - x - - x - - x - - [...] x - - x - - - x
where [...] represents 25 --x repetitions. With M=66 the code gives
x - x x - x x - x x - x x - [...] x x - x x - x - x
where [...] represents mostly xx- repetitions, with one x- in the middle.

Note, in C or java:
Substitute for (i=0; i<M; ++i) in place of for i in range(M):.
Substitute for (j=p; j<k-1; ++j) in place of for j in range(p,k-1):.

Correctness: Note that M = x boxes get checked because print "x", is executed M times.

Community
  • 1
  • 1
James Waldby - jwpat7
  • 8,593
  • 2
  • 22
  • 37
0

What about using Fisher–Yates shuffle ?

Make array, shuffle and pick first n elements. You do not need to shuffle all of them, just first n of array. Shuffling can be find in most language libraries.

Luka Rahne
  • 10,336
  • 3
  • 34
  • 56
  • I considered using a random algorithm but refrained from using it. It would be weird for a customer to see different checkboxes checked each time he opens the screen, the algorithm would have to give the same result each time. – Carra Nov 29 '11 at 14:25