looping through possible combinations of McNuggets packs of 6,9 and20

J

John Posner

Dept of overkill, iterators/generators division ...

-John

#------------------
from itertools import imap, product, ifilter
from operator import mul

box_sizes = (6, 9, 20)

def sum_product(s1, s2):
"""
return "scalar product" of two sequences
"""
return sum(imap(mul, s1, s2))

def reachables(target):
"""
return generator of numbers that are <= target
and are valid linear combos of McNuggets
"""
candidate_box_counts = product(
xrange(target/box_sizes[0] + 1),
xrange(target/box_sizes[1] + 1),
xrange(target/box_sizes[2] + 1),
)

gen = (sum_product(box_sizes, tup)
for tup in candidate_box_counts)

return (ifilter(lambda val, tgt=target: val < tgt,
gen))

if __name__ == "__main__":
tgt = 120 # thanks, Dave Angel
unreachables = set(xrange(tgt)) - set(reachables(tgt))
print "Max unreachable:", max(unreachables)
 
N

News123

Dept of overkill, iterators/generators division ...

-John

#------------------
from itertools import imap, product, ifilter
from operator import mul

box_sizes = (6, 9, 20)

def sum_product(s1, s2):
"""
return "scalar product" of two sequences
"""
return sum(imap(mul, s1, s2))

def reachables(target):
"""
return generator of numbers that are <= target
and are valid linear combos of McNuggets
"""
candidate_box_counts = product(
xrange(target/box_sizes[0] + 1),
xrange(target/box_sizes[1] + 1),
xrange(target/box_sizes[2] + 1),
)

Couldn't this be rewritten as:
candidate_box_counts = product(
* [ xrange(target/sizes + 1) for size in box_sizes ]
)
 
J

John Posner

candidate_box_counts = product(
xrange(target/box_sizes[0] + 1),
xrange(target/box_sizes[1] + 1),
xrange(target/box_sizes[2] + 1),
)

Couldn't this be rewritten as:
candidate_box_counts = product(
* [ xrange(target/sizes + 1) for size in box_sizes ]
)

Sure (except for the typo: "sizes" should be "size").

You could even use a generator expression instead of a list comprehension:

candidate_box_counts = product(
* ( xrange(target/size + 1) for size in box_sizes )
)

-John
 
M

Martin P. Hellwig

SPOILER ALTER: THIS POST CONTAINS A POSSIBLE SOLUTION

you are allowed to mix.
15 is neither divisable by 6 nor by nine, but 9 + 6 = 15

I was aware of that, thats whhy I wrote:
"or any combination of 6, 9, 20"
I guess, trying to find the result with divisions and remainders is
overly complicated.

Python 2.6.4 (r264:75706, Jul 1 2010, 12:52:41)
[GCC 4.2.1 20070719 [FreeBSD]] on freebsd8
Type "help", "copyright", "credits" or "license" for more information.
>>> MODULO_COMBINATIONS = [[20], [9], [6],
.... [20, 9], [20, 6], [9, 20],
.... [9, 6], [6, 20], [6, 9],
.... [20, 9, 6], [20, 6, 9], [9, 20, 6],
.... [9, 6, 20], [6, 20, 9], [6, 9, 20]].... tmp = list()
.... for combination in MODULO_COMBINATIONS:
.... remainder = number
.... for modulo_value in combination:
.... if remainder == 0:
.... remainder = None
.... break
....
.... result = remainder % modulo_value
....
.... if result == remainder :
.... remainder = None
.... break
....
.... remainder = result
....
.... if remainder == 0:
.... tmp.append(str(combination))
.... return(tmp)
....
>>> print(apply_combinations_on(15)) ['[9, 6]']
>>>

What is so over complicated about it?
 
P

Peter Otten

Martin said:
SPOILER ALTER: THIS POST CONTAINS A POSSIBLE SOLUTION

you are allowed to mix.
15 is neither divisable by 6 nor by nine, but 9 + 6 = 15

I was aware of that, thats whhy I wrote:
"or any combination of 6, 9, 20"
I guess, trying to find the result with divisions and remainders is
overly complicated.

Python 2.6.4 (r264:75706, Jul 1 2010, 12:52:41)
[GCC 4.2.1 20070719 [FreeBSD]] on freebsd8
Type "help", "copyright", "credits" or "license" for more information.
MODULO_COMBINATIONS = [[20], [9], [6],
... [20, 9], [20, 6], [9, 20],
... [9, 6], [6, 20], [6, 9],
... [20, 9, 6], [20, 6, 9], [9, 20, 6],
... [9, 6, 20], [6, 20, 9], [6, 9, 20]]... tmp = list()
... for combination in MODULO_COMBINATIONS:
... remainder = number
... for modulo_value in combination:
... if remainder == 0:
... remainder = None
... break
...
... result = remainder % modulo_value
...
... if result == remainder :
... remainder = None
... break
...
... remainder = result
...
... if remainder == 0:
... tmp.append(str(combination))
... return(tmp)
...
print(apply_combinations_on(15)) ['[9, 6]']

What is so over complicated about it?

Well, it was hard enough for me to run the code rather than just read it.
I get
[]

which should be 1*9 + 2*6

What am I missing?

Peter
 
R

Roald de Vries

tgt = 120 # thanks, Dave Angel

Anytime, but I'm not Dave Angel.

My previous algorithm was more efficient, but for those who like one-
liners:

[x for x in range(120) if any(20*a+9*b+6*c == x for a in range(x/20)
for b in range(x/9) for c in range(x/6))][-1]

Cheers, Roald
 
R

Roald de Vries

My previous algorithm was more efficient, but for those who like one-
liners:

[x for x in range(120) if any(20*a+9*b+6*c == x for a in range(x/20)
for b in range(x/9) for c in range(x/6))][-1]

OK, I did some real testing now, and there's some small error in the
above. All solutions for all x's are given by:

[(x, a, b, c) for x in range(120) for a in range(x/20+1) for b in
range(x/9+1) for c in range(x/6+1) if x == a*20+b*9+c*6]

.... and all non-solutions by:

[x for x in range(120) if not any(x == a*20+b*9+c*6 for a in
range(x/20+1) for b in range(x/9+1) for c in range(x/6+1))]

Cheers, Roald
 
M

Martin P. Hellwig

No it wasn't :)
which should be 1*9 + 2*6

What am I missing?

Aah interesting, 21 % 9 returns 3 instead of 12, which makes sense of
course. I guess the algorithm has to be adapted in a way that if the
value is bigger or equal twice the size of the modulo value you need to
iterate over it, something like:
for minus_multiplier in range(1, int(number, modulo_value)+2):
number = number - (modulo_value * minus_multiplier)
do the rest of the loop

Probably another ten lines or so to make it working as it should
 
M

MRAB

Martin said:
No it wasn't :)


Aah interesting, 21 % 9 returns 3 instead of 12, which makes sense of
course. I guess the algorithm has to be adapted in a way that if the
value is bigger or equal twice the size of the modulo value you need to
iterate over it, something like:
for minus_multiplier in range(1, int(number, modulo_value)+2):
number = number - (modulo_value * minus_multiplier)
do the rest of the loop

Probably another ten lines or so to make it working as it should
I don't understand what you're trying to do. My solution would be:

def can_buy(nuggets):
for packs_20 in range(nuggets // 20, -1, -1):
remaining_20 = nuggets - 20 * packs_20
for packs_9 in range(remaining_20 // 9, -1, -1):
remaining_9 = remaining_20 - 9 * packs_9
if remaining_9 % 6 == 0:
packs_6 = remaining_9 // 6
return [packs_6, packs_9, packs_20]
return []
 
B

Baba

Hi News 123,

Ok i'm getting closer. I am able to write code that will output values
that can be bought in exact quantity (truelist) and values that cannot
be bought in exact quantities.

For a range up to 29 i get this:
true [6, 9, 12, 15, 18, 20, 21, 24, 26, 27, 29]
false [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 22, 23, 25,
28]

the sixth value that passes the test of having an exact solution is 20
so that would mean that the last number i got that cannot be bought in
exact quantity is 19

that doesn't seem quite right, does it?


def can_buy(n_nuggets):
for a in range (0,n_nuggets):
for b in range (0,n_nuggets):
for c in range (0,n_nuggets):
#print "trying for %d: %d %d %d" % (n_nuggets,a,b,c)
if 6*a+9*b+20*c==n_nuggets:
return True
return False

truelist=[]
falselist=[]
for n_nuggets in range(30):
result = can_buy(n_nuggets)
if result==True:
truelist=truelist+[n_nuggets,]
else:
falselist=falselist+[n_nuggets,]

print 'true',truelist
print 'false',falselist


tnx
Baba
 
B

Baba

Hi News 123,

Ok i'm getting closer. I am able to write code that will output values
that can be bought in exact quantity (truelist) and values that cannot
be bought in exact quantities.

For a range up to 29 i get this:
true [6, 9, 12, 15, 18, 20, 21, 24, 26, 27, 29]
false [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 22, 23, 25,
28]

the sixth value that passes the test of having an exact solution is 20
so that would mean that the last number i got that cannot be bought in
exact quantity is 19

that doesn't seem quite right, does it?


def can_buy(n_nuggets):
for a in range (0,n_nuggets):
for b in range (0,n_nuggets):
for c in range (0,n_nuggets):
#print "trying for %d: %d %d %d" % (n_nuggets,a,b,c)
if 6*a+9*b+20*c==n_nuggets:
return True
return False

truelist=[]
falselist=[]
for n_nuggets in range(30):
result = can_buy(n_nuggets)
if result==True:
truelist=truelist+[n_nuggets,]
else:
falselist=falselist+[n_nuggets,]

print 'true',truelist
print 'false',falselist


tnx
Baba
 
N

News123

Roald,


What would your solution be if you weren't allowed to 'know'
that 120 is an upper limit.

Assume you were only allowed to 'know', that you won't find
any other amount, which can't be bought A AOON A
you found six solutions in a row?

I have a rather straightforward solution trying from 0 nuggets on
until I found six 'hits' in a row, but would be interested about other
idioms.


My previous algorithm was more efficient, but for those who like
one-liners:

[x for x in range(120) if any(20*a+9*b+6*c == x for a in range(x/20)
for b in range(x/9) for c in range(x/6))][-1]

OK, I did some real testing now, and there's some small error in the
above. All solutions for all x's are given by:

[(x, a, b, c) for x in range(120) for a in range(x/20+1) for b in
range(x/9+1) for c in range(x/6+1) if x == a*20+b*9+c*6]

... and all non-solutions by:

[x for x in range(120) if not any(x == a*20+b*9+c*6 for a in
range(x/20+1) for b in range(x/9+1) for c in range(x/6+1))]

Cheers, Roald
 
N

News123

SPOILER ALTER: THIS POST CONTAINS A POSSIBLE SOLUTION

you are allowed to mix.
15 is neither divisable by 6 nor by nine, but 9 + 6 = 15

I was aware of that, thats whhy I wrote:
"or any combination of 6, 9, 20"
I guess, trying to find the result with divisions and remainders is
overly complicated.

Python 2.6.4 (r264:75706, Jul 1 2010, 12:52:41)
[GCC 4.2.1 20070719 [FreeBSD]] on freebsd8
Type "help", "copyright", "credits" or "license" for more information.
MODULO_COMBINATIONS = [[20], [9], [6],
... [20, 9], [20, 6], [9, 20],
... [9, 6], [6, 20], [6, 9],
... [20, 9, 6], [20, 6, 9], [9, 20, 6],
... [9, 6, 20], [6, 20, 9], [6, 9, 20]] ... tmp = list()
... for combination in MODULO_COMBINATIONS:
... remainder = number
... for modulo_value in combination:
... if remainder == 0:
... remainder = None
... break
...
... result = remainder % modulo_value
...
... if result == remainder :
... remainder = None
... break
...
... remainder = result
...
... if remainder == 0:
... tmp.append(str(combination))
... return(tmp)
...
print(apply_combinations_on(15)) ['[9, 6]']

What is so over complicated about it?


What I meant with complicated is your mathematical assumption about
modulos, which are probably not obvious to everybody on the first glance.


Saying, that you can find out, whether for integers a,b.c,n
the equation
n = a*6 + b*9 + c*20 is true
by verifying


whether
n mod 6 == 0
or
n mod 9 == 0

or
(n mod 6) mod 9 == 0
or
(n mod 9) mod 6 == 0



Trial error is not so smart, but much easier to explain to beginners.
One can even return a,b,c for verification.


Being a little (and incrementally) smart, the search space can then be
reduced by some degrees
with rather easy to explain and incremental assumptions,

For given example the run times are so short, that it's not really an issue.


Another advantage of brute force is, that you can return a,b,c
and not only say, whether a,b,c exist.

This allows simple verification or assertions during debugging and learning.



# plain brute force.
#------------------------------------
def can_buy(n_nuggets):
for a in range (n_nuggets):
for b in range (n_nuggets):
for c in range (n_nuggets):
print "trying for %2d: %2d %2d %2d" % (n,a,b,c)
if 6*a + 9*b + 20*c == n_nuggets:
return (a,b,c)
return ()

# brute force but reduce the upper limit for each loop by saying,
# that one can stop if one a*6 > n or b*9 > n or c*20 >n
#------------------------------------------
def can_buy(n_nuggets):
for a in range (n_nuggets / 6 + 1):
for b in range (n_nuggets / 9 + 1):
for c in range (n_nuggets / 20 + 1):
print "trying for %2d: %2d %2d %2d" % (n,a,b,c)
if 6*a + 9*b + 20*c == n_nuggets:
return (a,b,c)
return ()


# as above code, but try even less in inner loops by
# removing what has been taken in outer loop
#--------------------------------------------
def can_buy(n_nuggets):
for a in range (1, n_nuggets / 6 + 1):
left_6 = n)nuggets - a * 6
for b in range (1, left_6 / 9 + 1):
left_9 = left_6 - b * 9
for c in range (1, left_9/ 20 + 1):
print "trying for %2d: %2d %2d %2d" % (n,a,b,c)
if 6*a+9*b+20*c==n_nuggets:
return (a,b,c)
return ()

# as above code, but do less multiplications
# in inner loop
#-------------------------------------------
def can_buy(n_nuggets):
for a in range (1, n_nuggets / 6 + 1):
left_6 = n)nuggets - a * 6
for b in range (1, left_6 / 9 + 1):
left_9 = left_6 - b * 9
for c in range (1, left_9/ 20 + 1):
print "trying for %2d: %2d %2d %2d" % (n,a,b,c)
if 20*c == left_9:
return (a,b,c)
return ()

# as above code, but use modulo in inner loop
# ------------------------------------------
def can_buy(n_nuggets):
for a in range (1, n_nuggets / 6 + 1):
left_6 = n)nuggets - a * 6
for b in range (1, left_6 / 9 + 1):
left_9 = left_6 - b * 9
print "trying for %2d: %2d %2d c" % (n,a,b)
if left_9 % 20 == 0:
return (a,b,left_9/20)
return ()
 
N

News123

Hi BAba,


Hi News 123,

Ok i'm getting closer. I am able to write code that will output values
that can be bought in exact quantity (truelist) and values that cannot
be bought in exact quantities.

For a range up to 29 i get this:
true [6, 9, 12, 15, 18, 20, 21, 24, 26, 27, 29]
false [0, 1, 2, 3, 4, 5, 7, 8, 10, 11, 13, 14, 16, 17, 19, 22, 23, 25,
28]

the sixth value that passes the test of having an exact solution is 20
so that would mean that the last number i got that cannot be bought in
exact quantity is 19

that doesn't seem quite right, does it?
As Thomas says:

It's not. You're not just trying to find the sixth value that can be
bought in exact quantity, but a sequence of six values that can all be
bought in exact quantity. The integers [6, 9, 12, 15, 18, 20] are not
sequential.


Six True values in a row without a False value n between tells you, that
you can stop searching.


So you that's what you have to write
A piece of code, which

fetches true fals values for 0 to e.g. 200 nuggets
and stops if you had 6 True values in a row.


Think how you do it manually:
you can try this even without the
can_buy function and plug in th can_buy() function only if you ahve
your detection of 6 True values in a row working.


test_sequence = "0010011011101110111101111111011111"


# below I use the enumerate function.
# rather useful for going through a list AND having a counter value.
# when plugging in your function you can switch back to
# for n_nuggets in xramge(200):

# perhaps here some initialisation for your searching
for i, a_char in enumerat(test_suequence):
print "entry %2d (%s) is %s" % (i,a_char,result)
result = a_char == '1' # result is now true for a '1'
# and false for a '0'
# here some code to determine the length of the sequence
if sequence_length == 6:
print "I found a sequence of 6 and can stop searching"
print "my solution is "
 
G

gslindstrom

Is that a homework problem?  Hint: first convince yourself that a
largest number actually exists.

If I recall, this was a "puzzler" on the NPR radio show "Car Talk".
Still might be homework, though.
 
B

Baba

It's not.  You're not just trying to find the sixth value that can be
bought in exact quantity, but a sequence of six values that can all be
bought in exact quantity.  The integers [6, 9, 12, 15, 18, 20] are not
sequential.

Hi Ian,

Thanks for stating the obvious. I obviously hadn't understood a
fundamental part of the theorem which states that 6 SEQUENTIAL passes
must be found! That's a good lesson learned and will help me in future
exercises to make sure i understand the theory first. Thanks again!

Ok so with your and News123's help (no offence to all others but i
need to keep it simple at this stage)i was able to find the solution:
43

my code is probably not elegant but a huge step forward from where i
started:

def can_buy(n_nuggets):
for a in range (0,n_nuggets):
for b in range (0,n_nuggets):
for c in range (0,n_nuggets):
#print "trying for %d: %d %d %d" % (n_nuggets,a,b,c)
if 6*a+9*b+20*c==n_nuggets:
return [a,b,c]
return []

for n_nuggets in range(50):
result1 = can_buy(n_nuggets)
result2 = can_buy(n_nuggets+1)
result3 = can_buy(n_nuggets+2)
result4 = can_buy(n_nuggets+3)
result5 = can_buy(n_nuggets+4)
result6 = can_buy(n_nuggets+5)
if result1!=[] and result2!=[] and result3!=[] and result4!=[] and
result5!=[] and result6!=[]:
if (n_nuggets+5)-n_nuggets==5:
print n_nuggets-1
break

i suppose this can be tweaked to make it shorter? For instance i
wonder if i can do the same with less variable to be defined?

tnx
Baba
 
M

Mel

Baba said:
def can_buy(n_nuggets):
for a in range (0,n_nuggets):
for b in range (0,n_nuggets):
for c in range (0,n_nuggets):
#print "trying for %d: %d %d %d" % (n_nuggets,a,b,c)
if 6*a+9*b+20*c==n_nuggets:
return [a,b,c]
return []

for n_nuggets in range(50):
result1 = can_buy(n_nuggets)
result2 = can_buy(n_nuggets+1)
result3 = can_buy(n_nuggets+2)
result4 = can_buy(n_nuggets+3)
result5 = can_buy(n_nuggets+4)
result6 = can_buy(n_nuggets+5)
if result1!=[] and result2!=[] and result3!=[] and result4!=[] and
result5!=[] and result6!=[]:
if (n_nuggets+5)-n_nuggets==5:
print n_nuggets-1
break

i suppose this can be tweaked to make it shorter? For instance i
wonder if i can do the same with less variable to be defined?

That can_buy function is a computational heavyweight -- very
repetitive when called inside a loop. It could be cheaper to compute a
list of quantities that can be purchased, then check to see what's in
the list -- or the set, if you optimize a bit more.

Mel.
 
M

member thudfoo

It's not. You're not just trying to find the sixth value that can be
bought in exact quantity, but a sequence of six values that can all be
bought in exact quantity. The integers [6, 9, 12, 15, 18, 20] are not
sequential.

Hi Ian,

Thanks for stating the obvious. I obviously hadn't understood a
fundamental part of the theorem which states that 6 SEQUENTIAL passes
must be found! That's a good lesson learned and will help me in future
exercises to make sure i understand the theory first. Thanks again!

Ok so with your and News123's help (no offence to all others but i
need to keep it simple at this stage)i was able to find the solution:
43

my code is probably not elegant but a huge step forward from where i
started:

def can_buy(n_nuggets):
for a in range (0,n_nuggets):
for b in range (0,n_nuggets):
for c in range (0,n_nuggets):
#print "trying for %d: %d %d %d" % (n_nuggets,a,b,c)
if 6*a+9*b+20*c==n_nuggets:
return [a,b,c]
return []

for n_nuggets in range(50):
result1 = can_buy(n_nuggets)
result2 = can_buy(n_nuggets+1)
result3 = can_buy(n_nuggets+2)
result4 = can_buy(n_nuggets+3)
result5 = can_buy(n_nuggets+4)
result6 = can_buy(n_nuggets+5)
if result1!=[] and result2!=[] and result3!=[] and result4!=[] and
result5!=[] and result6!=[]:
if (n_nuggets+5)-n_nuggets==5:
print n_nuggets-1
break

i suppose this can be tweaked to make it shorter? For instance i
wonder if i can do the same with less variable to be defined?

tnx
Baba

One tweak:

def can_buy(n_nuggets):
for a in range (0,n_nuggets):
for b in range (0,n_nuggets):
for c in range (0,n_nuggets):
#print "trying for %d: %d %d %d" % (n_nuggets,a,b,c)
if 6*a+9*b+20*c==n_nuggets:
return [a,b,c]
return []

for n_nuggets in range(50):
if (can_buy(n_nuggets)
and can_buy(n_nuggets+1)
and can_buy(n_nuggets+2)
and can_buy(n_nuggets+3)
and can_buy(n_nuggets+4)
and can_buy(n_nuggets+5)):
print n_nuggets - 1
break
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,170
Messages
2,570,927
Members
47,469
Latest member
benny001

Latest Threads

Top