Lottery Ball Problem

6

A woman works at a lottery ball factory. She's instructed to open up a lottery ball creation kit composed of 1 red rubber ball and n strips of digits from 0 through 9.

She's instructed to do the following:

  1. Starting with 1, write the current number on the ball using the digits available on the n strips.
  2. Put unused digits into a bowl where they can be used later for when she runs out of available digits in step 1.
  3. Open up another lottery ball creation kit and continue to step 1 for the next number.

The question is, at what number will she arrive at before she runs out of digits to write the number? Edited: Given n where n is the number of digit strips from 0 through 9 provided in each lottery ball creation kit, write a program that can solve this (not trivially) in the least amount of time.

Edit: Additional notes - 6 and 9 are different numbers. Assume there are no limits to the number of digits that can be pasted on the ball.

Hint: If she counted in base 2 rather than base 10, how would that change the result and would that be easier to calculate? What could you derive from solutions in base 2?

Neil

Posted 2011-06-29T16:18:50.977

Reputation: 459

2Are 6 and 9 considered to be different ? – Paul R – 2011-06-29T16:25:45.587

1@Paul R, irrelevant. It's easy to prove that the first digit which runs out is 1. – Peter Taylor – 2011-06-29T17:01:59.687

1I think we need some more detail. The way I'm reading the question, she'll run out of digits on the strips at ball #111 (since there are only two 1's) and then need to go into the bowl. Are we talking about running out of digits in the bowl? That too should have a relatively simple solution, and I'm not sure it's interesting to see which language can be compiled most efficiently since there's not much room for algorithmic differences in a simple increment+check problem. – Matthew Read – 2011-06-29T17:05:04.770

@Matthew, she can use the 1s from the bowl for 111. The solution will be somewhere just short of 10^10, so increment and check won't be the fastest approach. – Peter Taylor – 2011-06-29T17:10:19.980

How many digits can she fit on one ball ? – Paul R – 2011-06-29T17:25:48.787

I think the shortest answer for this question will be something like print X. – Alexandru – 2011-06-29T17:27:59.830

@Alexandru: I think that's already covered by the "(not trivially)" clause. – Paul R – 2011-06-29T17:31:19.843

2@Peter I guess I'm confused about how you can do anything intelligent beyond increment and check without making it trivial. It seems kind of silly to take advantage of various properties of the problem while ignoring the obvious one, that it has a direct and fixed solution. I don't think this is a good question. – Matthew Read – 2011-06-29T17:46:39.993

@matthew you can iterate through it through the magnitude; the digits you need for 1 through 1e(X+1) is equal to 10 times the digits you need for 1 through 1eX. plus 1e(X-1) for the ending digits (with some caveats though) – ratchet freak – 2011-06-29T20:45:31.157

2@Matthew, it should be possible to do something binary-searchy. (If anyone can be bothered to check which Project Euler problem is very similar to this question I might check my solution). But I agree that fixed mathematical problems are bad questions. Perhaps the problem should be fixed by making the number of strips in each kit an input variable? – Peter Taylor – 2011-06-29T20:53:18.237

2All good points. Making relevant corrections to the question. – Neil – 2011-06-30T12:03:26.410

@Matthew: She places 1 on the ball and places the rest of that strip (0 and 2 through 9) in a bowl along with any additional strips (0 through 9) every number. For example for n = 2, when she finishes with number 2, she's got 3 ones, 3 twos, and 4 of numbers three through nine at her disposal (she's gone through 4 digit strips total only to use a 1 digit and a 2 digit). – Neil – 2011-06-30T12:35:08.060

1I'm assuming the result will be of the order of 10^21, i.e. a 21 digit integer, give or take a digit ? In which case 64 bit ints aren't going to be much use ? – Paul R – 2011-06-30T20:24:31.873

@Paul, seems like a pretty good assumption with hindsight for n=2. Or 10^31 for n=3... – asoundmove – 2011-07-01T04:29:57.487

I think static value types are out of the question, in fact. – Neil – 2011-07-01T10:10:29.713

1

Found it: Project Euler problem 156 is related (but different).

– Peter Taylor – 2011-07-01T12:26:23.763

@Peter Taylor: how can it be proven that the first digit to run out is 1? – Lowjacker – 2011-07-01T16:12:46.297

@Lowjacker: I don't know about a formal proof, but it's fairly self-evident that the digits 1-9 all get consumed at the same aggregate rate over any given order of magnitude, but the 1s will always get used first, e.g. consider the ranges 1..9, 10..99, 100..999, etc. – Paul R – 2011-07-01T16:22:30.983

1@Lowjacker, let f(d, n) be the number of digits d used up by the first n balls. Then for d in [2,9], f(d-1, n) >= f(d, n). Proof: consider the set of (number, digit position) pairs which contribute towards f(d, n). Replacing the digit d at that position in that number you get a smaller number, so if the first number is <= n, the one with the digit replaced also is, and that contributes to f(d-1, n). QED. 1 is a special case because leading 0s are ignored. – Peter Taylor – 2011-07-01T18:58:39.683

@Peter Taylor I thought about your argument and don't come around one point. I think you'll have to add another argument. If I understand correctly, for f(2,21) the numbers 2, 12, 20 and 21 contribute. Replacing digit 2 by 1 we get numbers 1, 11, 10 and 11. Note that 11 is actually a duplicate but must not be counted twice for f(1,21)! – Howard – 2011-07-02T15:26:47.163

@Howard: incorrect. 11 must be counted twice for f(1, 21) because it contains the digit 1 twice. – Peter Taylor – 2011-07-02T19:20:39.323

@Peter Taylor. yes, it counts 2 ones. But that's not what I meant. You would take it a third time if you think of f(2,22) because then also 22 would be mapped to 11. – Howard – 2011-07-02T19:48:54.093

@Howard, no: 22 would be mapped to 12 and 21. I was quite careful to set up my replacements to only replace one digit at a time. – Peter Taylor – 2011-07-03T19:17:14.447

@Peter now I understand your point. thank you for the clarification. – Howard – 2011-07-03T20:15:48.023

@Neil: you say "Answer for n=1 is 199991 since technically 199990 is the last number she could make" yet the question says "what number will she arrive at before she runs out of digits". My understanding is that if she runs out of digits writing 199991, then the answer is the number before, hence 199990. – AtnNn – 2011-07-05T00:57:25.933

@atnnn You're right of course, though if you gave me 199991, I'd consider it the right answer anyway. – Neil – 2011-07-05T07:28:31.327

Answers

2

edit 2: fixed

General solution for n > 1:

-- lottery2.hs

import System
import Control.Monad
import Data.Maybe

main = mapM_ print . map (solve . read) =<< getArgs

rdigits 0 = []
rdigits n = (n `mod` 10) : rdigits (n `div` 10)

digits = reverse . rdigits

undigits = foldl ((+) . (10*)) 0

c1 = c . digits

c [] = 0
c [0] = 0
c [_] = 1
c (0:xs) = c xs
c (1:xs) = d xs + c xs + undigits xs
c (x:xs) = c xs + x * (d xs - 1) + 10 ^ length xs

d xs = ds !! length xs

ds = map d' [0..]
  where d' n = c1 (10 ^ n - 1) + 1

maxVal n =
  fst . head . filter (uncurry $ (<) . (*n))
  . map (liftM2 (,) undigits c . (1:) . flip replicate 9) $ [0..]

search f p q a b = s f (uncurry p) q (a, f a) (b, f b)
  where 
    s f p q a b
      | not (p a) = Nothing
      | fst a + 1 == fst b = guard (not $ p b) >> return (fst a)
      | a >= b = Nothing
      | q a b = maybe (s f p q k b) Just (s f p q a k)
      | otherwise = Nothing
        where k = ((fst a + fst b) `div` 2, f (fst k))

solve n = fromJust $ search c1 ((>=) . (*n)) q 1 m
  where 
    m = maxVal n
    q (a, fa) (b, fb) = (a * n) - fa <= fb - fa

There seems to be a pattern here:

$ ghc -O --make lottery2.hs
Linking lottery2 ...
$ time ./lottery 1 2 3 4 5 6
199990
1999919999999980
19999199999999919999999970
199991999999999199999999919999999960
1999919999999991999999999199999999919999999950
19999199999999919999999991999999999199999999919999999940

real    0m3.580s
user    0m3.508s
sys     0m0.020s
$ time ./lottery 7 8 9 10 11 12
199991999999999199999999919999999991999999999199999999919999999930
1999919999999991999999999199999999919999999991999999999199999999919999999920
19999199999999919999999991999999999199999999919999999991999999999199999999919999999918
199991999999999199999999919999999991999999999199999999919999999991999999999199999999919999999917
1999919999999991999999999199999999919999999991999999999199999999919999999991999999999199999999919999999916
19999199999999919999999991999999999199999999919999999991999999999199999999919999999991999999999199999999919999999915

real    1m10.855s
user    1m3.868s
sys     0m0.324s

AtnNn

Posted 2011-06-29T16:18:50.977

Reputation: 136

What answer did you get and how long did it take to get there ? – Paul R – 2011-06-30T22:24:15.030

199990

real    0m0.742s
user    0m0.532s
sys     0m0.020s
 – AtnNn  – 2011-07-01T02:14:33.340

That looks like the answer for only 1 strip of numbers - for 2 strips it should be of the order of 10^20. – Paul R – 2011-07-01T06:59:30.833

Answer for n=1 is 199991 since technically 199990 is the last number she could make. @Paul: For n=2, actually it's a bit less. Mind you, still not a small number.. – Neil – 2011-07-01T10:21:08.170

@Neil Why do you think it is less? I thin you're right but do you have any "proof"? I did some tests with other bases than 10. Thus you can brute-force the solutions within reasonable times. It always scaled like (b^b)^(n-1) for base b and n strips and this I would assume something around 199990*10^10. The first few numbers even look like there is a quite simple formula behind, but it breaks for larger bases or strip numbers. – Howard – 2011-07-01T17:03:03.637

@atnnn I don't know if you can guarantee a correct result from your bisection algorithm. It is only defined if you know that there exists exactly one point where there is the cross over (i.e. c1 x <= n x and c1 (x+1) > n (x+1) ). But I suppose that there may be more than one in the function you try to solve. – Howard – 2011-07-02T11:32:11.683

@Howard: There is at least one 1 in each number in the range I bisect over, so there is only one cross over point – AtnNn – 2011-07-02T11:40:39.650

Sorry, that is a wrong assumption on my part. I will try to formalize my reasoning and adapt the code if necessary. – AtnNn – 2011-07-02T11:47:34.573

1It's commendable that you found what seems to be the most efficient solution to solve the problem, though unfortunately the answers aren't correct. If you get it right, you'll see a pattern begin to form. – Neil – 2011-07-04T10:40:17.117

@Neil: How did you get the answers? – AtnNn – 2011-07-04T16:46:38.893

I'm guessing that the answer for n = 2 should be 19999999999999999991 - is that right ? – Paul R – 2011-07-04T21:45:12.663

@Paul R: No. writing all the numbers in [1..19999999999999999991] takes 47999999999999999992 ones, which is a lot more than 2 * 19999999999999999991 – AtnNn – 2011-07-04T22:03:02.707

@Howard: I replaced my bisect function with a search function that tries all possible ranges – AtnNn – 2011-07-04T22:05:17.107

OK - maybe I got one too many 9s - how about 1999999999999999991 then ? – Paul R – 2011-07-04T22:05:19.213

@Paul R: see my updated answer. I give my results for n in [1..12] – AtnNn – 2011-07-04T22:09:25.703

@atnnn: oh - sorry - I hadn't notice that you'd fixed it - congratulations. Not quite the pattern I was expecting, but neat all the same... – Paul R – 2011-07-04T22:16:04.953

Congrats. Nicely done. :) – Neil – 2011-07-05T07:29:53.713

I'll give it to you, though it should be composed of only 1s and 9s (and 0s if it's the last number she could make). n=1:199991, n=2:1999919999999990, n=3:19999199999999919999999990. Strange pattern isn't it? – Neil – 2011-07-05T07:36:57.013

@Neil: It is a strange pattern. How did you get those answers? I get c1 1999919999999981 = 3999839999999963, which is more than 2 * 1999919999999981. – AtnNn – 2011-07-05T13:59:27.220

@atnnn: I read it in a math enthusiast magazine and thinking about how I would implement a program to search for these values led me to make this code golf question. – Neil – 2011-07-06T15:00:49.693

@Neil: I figured it out. To get the answers from your magazine, you need to start with n-1 strips. It is like starting from 0 instead of 1, and finding the first number that uses up the last 1. If you start from 0, it is possible to write 1999919999999992 even though, after writing 1999919999999991, there are no 1s left in the bowl. But if you start from 1 and find the last number that can be written, then my answers are correct. – AtnNn – 2011-07-06T17:16:43.007

@atnn: But you wrote 1999919999999980, not 1999919999999991 (or even 1999919999999990). – Neil – 2011-07-11T14:07:15.817

2

awk (109)

brute force solution

yes|awk -F "" '{c("0123456789",1);c(NR,-1)}function c(C,m,i){for($0=C;NF-i++;)if((t[$i]+=m)<0){print NR-1;exit}}'

The awk does not count in the character count if I understand the rules correctly.
The trick with yes| + NR is a shorter way to define a loop counter than a BEGIN + while or for clause.
Replace ,1 with ,[value of n]. With 1 it returns 199990, with 2 it is still trying to find the answer.

Could write it as:

yes|awk -F "" -v n=1 '{c("0123456789",n);c(NR,-1)}function c(C,m,i){for($0=C;NF-i++;)if((t[$i]+=m)<0){print NR-1;exit}}'

And replace n=1 with n=[whatever value] (now a parameter of the program, for an extra 6 characters plus the number of characters for the value of n. But like I said since it is still trying to compute for n=2, there may be no need to parameterise this version.

I'm sure there must be a much more clever algorithm than plain increment and count. Like a bit of algebra. No time for this... I'll leave that to someone else.

asoundmove

Posted 2011-06-29T16:18:50.977

Reputation: 358

This is code-challenge rather than code-golf, so it doesn't matter whether awk counts or not. – Peter Taylor – 2011-07-01T07:30:50.130

We're dealing with enormous numbers, so large in fact that I think it makes choice of language irrelevant in the ultimate performance of the program. As for your answer, it's technically 199991, since 199990 is the last number she could make. – Neil – 2011-07-01T10:13:08.717

@Neil: you said "what number she would arrive at before she runs out", so yes, I printed the last number before she ran out. You do not have enough digits to print 199991. – asoundmove – 2011-07-01T15:45:10.097

... I hate it when I'm wrong. – Neil – 2011-07-04T10:37:22.423

0

C

Here's a non-golf brute force solution in C which seems to give the right answer for 1 strip, but I haven't had the patience to let it run long enough to see if it gets to a solution for 2 strips (in fact if I extrapolate the progress output I'm not sure it will finish within my life-time !).

#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    const int MAX_DIGITS = 24;
    char buff[MAX_DIGITS + 1];
    int num_strips = 1;
    int64_t digits[10] = { 0 };
    int64_t i;

    if (argc > 1)
    {
        num_strips = atoi(argv[1]);
        if (num_strips < 1 || num_strips > 2)
        {
            fprintf(stderr, "num_strips must be 1 or 2\n");
            exit(1);
        }
    }

    memset(buff, ' ', MAX_DIGITS - 1);
    buff[MAX_DIGITS - 1] = '1';
    buff[MAX_DIGITS] = '\0';

    for (i = 1 ; ; ++i)
    {
        int d;

        // add 2 to each remaining digit count

        for (d = 0; d < 10; ++d)
        {
            digits[d] += num_strips;
        }

        // update counts of remaining digits based on current i

        for (d = MAX_DIGITS - 1; d >= 0; --d)
        {
            char ch = buff[d];

            if (ch != ' ')
            {
                int digit = ch - '0';
                digits[digit]--;
            }
            else
            {
                break;
            }
        }

        // check to see whether we found solution

        for (d = 0; d < 10; ++d)
        {
            if (digits[d] < 0)
            {
                printf("Found solution at i = %s, digits = { %24lld %24lld %24lld ... %24lld }\n", buff, digits[0], digits[1], digits[2], digits[9]);
                goto done;
            }
        }

        // log progress when i == 2^n

        if ((i & (i - 1)) == 0)
        {
            printf("i = %s, digits = { %24lld %24lld %24lld ... %24lld }\n", buff, digits[0], digits[1], digits[2], digits[9]);
        }

        // increment "i"

        for (d = MAX_DIGITS - 1; d >= 0; --d)
        {
            char ch = buff[d];

            switch (ch)
            {
                case ' ':
                    buff[d] = '1';
                    break;
                case '9':
                    buff[d] = '0';
                    continue;
                    break;
                default:
                    buff[d] = ch + 1;
                    break;
            }
            break;
        }
    }
done:
    return 0;
}

Paul R

Posted 2011-06-29T16:18:50.977

Reputation: 2 893

Nice try, though exceeding n=2 for brute force is a mess. – Neil – 2011-07-05T07:30:55.850