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

I

Ian Kelly

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?

Instead of calling can_buy() 6 times on every iteration of the main
loop, I would suggest maintaining a list of the sequential results.
Just call it once on each number of nuggets in order. If the number
of nuggets is purchasable, and the list is empty or the last item in
the list is the number of nuggets - 1, then append the number of
nuggets to the list. If the last item in the list is not the number
of nuggets - 1, then they're not sequential and you start a new list.
When the length of the list reaches 6, you're done, and the answer is
equal to the first item in the list - 1.

You can also improve the can_buy() function by tightening up the loop
limits. You don't need to go all the way up to n_nuggets on each
loop.
 
M

MRAB

Baba said:
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?
Increase the number of nuggets one by one and keep a count of the number
of consecutive successes.

If you can buy a number of nuggets exactly, increment the count,
otherwise reset the count.

When the count reaches 6 you know that you're at the end of a sequence
of consecutive successes.
 
J

John Posner

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?

[Other responders have covered a lots of the points I make below. I
guess I need to be quicker!]

First, congratulations on getting to a solution! Code can very often be
made shorter, although it's not always worth your while to do so. And
often, shorter code is less understandable code -- which makes a big
difference if you need to revisit the code later on.

Here are some things that can be done with your for-loop:

1. You don't need this "if" test, because it's always true:

if (n_nuggets+5)-n_nuggets==5:

2. Whenever you find yourself inventing a series of variables like
"result1", "result2", "result3", etc., think about using a list instead.

results = []
for i in range(6):
results.append(can_buy(n_nuggets + i))

And to get really fancy, you can use a single "list comprehension"
statement to do it all

results = [can_buy(n_nuggets + i) for i in range(6)]

3. Your "if" statement tests whether all the lists are non-empty. In
Python, expressions (a) and (b) are equivalent:

(a) if result[0] != []
(b) if result[0]

So your "if" test can be expressed as:

if (result[0] and result[1] and result[2] and result[3]
and result[4] and result[5])

(The parentheses are not required by "if", but they *do* enable you
to split the expression across two or more lines.) And you can use the
"all" function to rescue this cumbersome statement;

if all(results)

After all this work, the code is getting pretty short:

for n_nuggets in range(50):
results = [can_buy(n_nuggets + i) for i in range(6)]
if all(results):
print n_nuggets-1
break

4. The variable "results" is defined in one statement, and then is used
just once, in the very next statement. There's no harm in that, and I
think it makes the mode easier to understand, but you can get rid of it:

for n_nuggets in range(50):
if all([can_buy(n_nuggets + i) for i in range(6)]):
print n_nuggets-1
break

But wait, there's more ... :) So far, we've just refined the
*implementation* of your algorithm. But the algorithm itself could use
some work.

* When n_nuggets==0, we compute can_buy(0), can_buy(1), can_buy(2),
can_buy(3), can_buy(4), and can_buy(5).

* When n_nuggets==1, we compute can_buy(1), can_buy(2), can_buy(3),
can_buy(4), can_buy(5), and can_buy(6).

.... and so on. We can use an algorithm in which can_buy(i) is computed
just once for each value of i:

can_buy_count = 0
n_nuggets = 0
while n_nuggets < 50:
if can_buy(n_nuggets):
can_buy_count += 1
else:
can_buy_count = 0

if can_buy_count == 6:
print n_nuggets - 6
break
else:
n_nuggets += 1

And here's how you could shorten *this* code:

### cbc = "can buy count"
cbc = 0
n_nuggets = -1
while n_nuggets < 50:
n_nuggets += 1
cbc = cbc+1 if can_buy(n_nuggets) else 0
if cbc == 6:
print n_nuggets - 6
break

HTH,
John
 
B

Baba

Hi John,

Thanks for your submission! I've improved a lot and everone's help so
far has been thrilling amd is very good for my self-study
motivation :)

ok so i think i'm clear on how to approach this problem and on how to
write basic but clean Python code to solve it.

The next step is to generalise this code so that other pack quantities
could be tested: "generalize this idea to work with any size packages
of McNuggets, not just 6, 9, and 20. For simplicity, however, we will
assume that nuggets are provided in three different sized packages"

I thought this should be relatively straightforward and it does work
if i test it for the values 6,9&20 but if i test for 2,3,4 i would
expect the result to be 1 but it returns nothing

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

def diophantine_nuggets(x,y,z):
cbc=0 #cbc=can_buy counter
packages =[x,y,z]
for n_nuggets in range(50):
result=can_buy(n_nuggets,packages)
if result:
cbc+=1
else:
cbc=0
if cbc==6:
solution=n_nuggets-6
print "Largest number of McNuggets that cannot be bought in
exact quantity: %d" %(solution)

diophantine_nuggets(2,3,4)
 
B

Baba

Hi John,

Thanks for your submission! I've improved a lot and everone's help so
far has been thrilling and is very good for my self-study
motivation :)

ok so i think i'm clear on how to approach this problem and on how to
write basic but clean Python code to solve it.

The next step is to generalise this code so that other pack quantities
could be tested: "generalize this idea to work with any size packages
of McNuggets, not just 6, 9, and 20. For simplicity, however, we will
assume that nuggets are provided in three different sized packages"

I thought this should be relatively straightforward and it does work
if i test it for the values 6,9&20 but if i test for 2,3,4 i would
expect the result to be 1 but it returns nothing

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

def diophantine_nuggets(x,y,z):
cbc=0 #cbc=can_buy counter
packages =[x,y,z]
for n_nuggets in range(50):
result=can_buy(n_nuggets,packages)
if result:
cbc+=1
else:
cbc=0
if cbc==6:
solution=n_nuggets-6
print "Largest number of McNuggets that cannot be bought in
exact quantity: %d" %(solution)

diophantine_nuggets(2,3,4)
 
E

Emile van Sebille

On 8/15/2010 8:44 AM Baba said...
Hi John,

Thanks for your submission! I've improved a lot and everone's help so
far has been thrilling and is very good for my self-study
motivation :)

ok so i think i'm clear on how to approach this problem and on how to
write basic but clean Python code to solve it.

The next step is to generalise this code so that other pack quantities
could be tested: "generalize this idea to work with any size packages
of McNuggets, not just 6, 9, and 20. For simplicity, however, we will
assume that nuggets are provided in three different sized packages"

I thought this should be relatively straightforward and it does work
if i test it for the values 6,9&20 but if i test for 2,3,4 i would
expect the result to be 1 but it returns nothing

Because can_buy still uses denominators 6,9&20 - try packages[0],[1]&[2]

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

def diophantine_nuggets(x,y,z):
cbc=0 #cbc=can_buy counter
packages =[x,y,z]
for n_nuggets in range(50):
result=can_buy(n_nuggets,packages)
if result:
cbc+=1
else:
cbc=0
if cbc==6:
solution=n_nuggets-6
print "Largest number of McNuggets that cannot be bought in
exact quantity: %d" %(solution)

diophantine_nuggets(2,3,4)
 
I

Ian Kelly

On 8/15/2010 8:44 AM Baba said...
Hi John,

Thanks for your submission! I've improved a lot and everone's help so
far has been thrilling and is very good for my self-study
motivation :)

ok so i think i'm clear on how to approach this problem and on how to
write basic but clean Python code to solve it.

The next step is to generalise this code so that other pack quantities
could be tested: "generalize this idea to work with any size packages
of McNuggets, not just 6, 9, and 20. For simplicity, however, we will
assume that nuggets are provided in three different sized packages"

I thought this should be relatively straightforward and it does work
if i test it for the values 6,9&20 but if i test for 2,3,4 i would
expect the result to be 1 but it returns nothing

Because can_buy still uses denominators 6,9&20 - try packages[0],[1]&[2]

Also, note that testing for 6 buyable quantities in a row is correct
for 6, 9, 20 but is not necessarily correct for other denominations.
You should think about how many you need to find in sequence for any
given input of x, y, and z.

Cheers,
Ian
 
J

John Posner

On 8/15/2010 11:38 AM, Baba wrote:

In addition to the points that Emile and Ian made ...
def diophantine_nuggets(x,y,z):
cbc=0 #cbc=can_buy counter
packages =[x,y,z]

You can take advantage of a nifty "syntax convenience feature" here.
Instead of loading all of the function's arguments into a list
"manually", you can make it happen automatically:

def diophantine_nuggets(x,y,z):
cbc = 0
packages =[x,y,z]

... becomes ...

def diophantine_nuggets(*packages):
cbc = 0

The asterisk (*) in the function's signature does the trick.

for n_nuggets in range(50):

Careful -- in the general case, you might need to search beyond 50 for
your answer!


Best,
John
 
B

Baba

Hi All,

@Emile tnx for spotting the mistake. Should have seen it myself.

@John & Ian i had a look around but couldn't find a general version of
below theorem
If it is possible to buy x, x+1,…, x+5 sets of McNuggets, for some x,
then it is possible to buy any number of McNuggets >= x, given that
McNuggets come in 6, 9 and 20 packs.
I must admit that i'm not keen on spending too much time on purely
mathematical questions. It seems that this has to do with Frobenius
Number? re range the exercise seems to suggest to limit it to 200

so feel free to enlighten me on this last part. In the meantime the
almost complete code is:

def can_buy(n_nuggets,packages):
for a in range (0,n_nuggets/packages[0]+1):
for b in range (0,n_nuggets/packages[1]+1):
for c in range (0,n_nuggets/packages[2]+1):
#print "trying for %d: %d %d %d" % (n_nuggets,a,b,c)
if packages[0]*a+packages[1]*b
+packages[2]*c==n_nuggets:
return True
return False

def diophantine_nuggets(*packages):
cbc=0 #cbc=can_buy counter
#packages =[x,y,z]
for n_nuggets in range(200):
result=can_buy(n_nuggets,packages)
if result:
cbc+=1
else:
cbc=0
if cbc==6:
solution=n_nuggets-6
print "Largest number of McNuggets that cannot be bought in
exact quantity: %d" %(solution)
break
diophantine_nuggets(2,3,4)
 
M

Mel

Baba said:
Hi All,

@Emile tnx for spotting the mistake. Should have seen it myself.

@John & Ian i had a look around but couldn't find a general version of
below theorem
If it is possible to buy x, x+1,…, x+5 sets of McNuggets, for some x,
then it is possible to buy any number of McNuggets >= x, given that
McNuggets come in 6, 9 and 20 packs.

Not that hard. When you can buy nuggets `n` to a pack (6 for
example) then if you have schemes for buying
k,k+1,k+2,...,k+n-1 nuggets, you can create a scheme for
buying any number of nuggets >= k . At worst just buy packs
of `n` until the number left to buy is between k and k+n-1
inclusive, then follow the scheme for that number.

Mel.
 
B

Baba

Hi Mel,

indeed i thought of generalising the theorem as follows:
If it is possible to buy n, n+1,…, n+(x-1) sets of McNuggets, for some
x, then it is possible to buy any number of McNuggets >= x, given that
McNuggets come in x, y and z packs.

so with diophantine_nuggets(7,10,21) i would need 7 passes
result:53

but with (10,20,30) and 10 passes i get no result

so i'm not sure i'm on the right path...
 
I

Ian Kelly

Hi Mel,

indeed i thought of generalising the theorem as follows:
If it is possible to buy n, n+1,…, n+(x-1) sets of McNuggets, for some
x, then it is possible to buy any number of McNuggets >= x, given that
McNuggets come in x, y and z packs.

so with diophantine_nuggets(7,10,21) i would need 7 passes
result:53

but with (10,20,30) and 10 passes i get no result

You're on the right track. In the case of (10,20,30) there is no
largest exactly purchasable quantity. Any quantity that does not end
with a 0 will not be exactly purchasable.

I suspect that there exists a largest unpurchasable quantity iff at
least two of the pack quantities are relatively prime, but I have made
no attempt to prove this.

Cheers,
Ian
 
R

Roald de Vries

You're on the right track. In the case of (10,20,30) there is no
largest exactly purchasable quantity. Any quantity that does not end
with a 0 will not be exactly purchasable.

I suspect that there exists a largest unpurchasable quantity iff at
least two of the pack quantities are relatively prime, but I have made
no attempt to prove this.

That for sure is not correct; packs of 2, 4 and 7 do have a largest
unpurchasable quantity.

I'm pretty sure that if there's no common divisor for all three (or
more) packages (except one), there is a largest unpurchasable
quantity. That is: ∀ i>1: ¬(i|a) ∨ ¬(i|b) ∨ ¬(i|c), where ¬(x|
y) means "x is no divider of y"

Cheers, Roald
 
I

Ian Kelly

That for sure is not correct; packs of 2, 4 and 7 do have a largest
unpurchasable quantity.

2 and 7 are relatively prime, so this example fits my hypothesis.
I'm pretty sure that if there's no common divisor for all three (or more)
packages (except one), there is a largest unpurchasable quantity. That is: ∀
i>1: ¬(i|a) ∨ ¬(i|b) ∨ ¬(i|c), where ¬(x|y) means "x is no divider of y"

No. If you take the (2,4,7) example and add another pack size of 14,
it does not cause quantities that were previously purchasable to
become unpurchasable.

Ian
 
I

Ian Kelly

2 and 7 are relatively prime, so this example fits my hypothesis.

Although now that I think about it some more, there are
counter-examples. For example, the pack sizes (6, 10, 15) have a
largest unpurchasable quantity of 29, but no two of those are
relatively prime.

I'm going to revise my hypothesis to state that a largest
unpurchasable quantity exists iff some there exists some relatively
prime subset of the pack sizes of cardinality 2 or greater.

Cheers,
Ian
 
R

Roald de Vries

2 and 7 are relatively prime, so this example fits my hypothesis.

I now notice I misread your post (as 'iff the least two pack
quantities are relatively prime')
No. If you take the (2,4,7) example and add another pack size of 14,
it does not cause quantities that were previously purchasable to
become unpurchasable.

Then what is the common divisor of 2, 4, 7 and 14? Not 2 because ¬(2|
7), not anything higher than 2 because that's no divisor of 2.

Cheers, Roald
 
C

cbrown

That for sure is not correct; packs of 2, 4 and 7 do have a largest  
unpurchasable quantity.

But 2 is coprime to 7. I think a better counterexample is 6, 10, 15;
no two are coprime, but there is a largest unpurchasable quantity.
I'm pretty sure that if there's no common divisor for all three (or  
more) packages (except one), there is a largest unpurchasable  
quantity. That is: ∀ i>1: ¬(i|a) ∨ ¬(i|b) ∨ ¬(i|c), where ¬(x|
y) means "x is no divider of y"

It's easy to prove that there is a largest unpurchasble quantity iff
gcd(x,y,z) = 1.

First, suppose d = gcd(x, y, z); then for some x', y', z' we have that
x = d*x', y = d*y', z = d*z'; and so for any a, b, c:

a*x + b*y + c*z = a*d*x' + b*d*y' + c*d*z'
= d*(a*x' + b*y' + c*z')

which means that d must always divide the total number of nuggets
purchasable; thus if d >1, there is no largest unpurchasable quantity
(you cannot purchase n*d + 1 nuggets for any n).

To go the other way, if d = 1, then there exists integers (not
neccessarily positive) such that

a*x + b*y + c*z = 1

so therefore

(a*x)*x + (b*x)*y + (c*x)*z = x

Choose A, B, and C to be positive integers with

A + a*x >= 0
B + b*x >= 0
C + c*x >= 0

Then we get a sequence of x consecutive numbers, all purchasable, via

(A + i*a)*x + (B + i*b)*x + (C + i*c)*z
= (Ax + By + Cz) + i*(ax + by cz)
= (Ax + By + Cz) + i

with i in range(x).

The next consecutive number is then achieved via

(Ax + By + Cz) + x = (A+1)x + By + Cz

and so on.

Cheers - Chas
 
I

Ian Kelly

Then what is the common divisor of 2, 4, 7 and 14? Not 2 because ¬(2|7), not
anything higher than 2 because that's no divisor of 2.

Ah, I misread what you meant as well.
 
B

Baba

Hi Chas, Roald,

These are all complicated formula that i believe are not expected at
this level. If you look at the source (see my first submission) you
will see that this exercise is only the second in a series called
"Introduction to Programming". Therefore i am convinced that there is
a much simpler solution.

Now, i believe that the number of consecutive passes required to make
this work is equal to the smallest number of pack sizes. So if we have
packs of (9,12,21) the number of passes needed would be 9 and the
theorem would read

"If it is possible to buy n,n+1,n+2,...n+8 nuggets it is possible to
buy any number of nuggets >= 9 given that they come in packs of
9,12,21"

However i turn in circles because i don't seem to get any results for
some random pack combinations like (9,12,21) or (10,20,30).

The must always be a solution i'm thinking, be it the smallest pack -
1

Thoughts?

tnx
Raoul
 
M

Mel

Baba wrote:
[ ... ]
Now, i believe that the number of consecutive passes required to make
this work is equal to the smallest number of pack sizes. So if we have
packs of (9,12,21) the number of passes needed would be 9 and the
theorem would read

"If it is possible to buy n,n+1,n+2,...n+8 nuggets it is possible to
buy any number of nuggets >= 9 given that they come in packs of
9,12,21"

That's the proper statement.
However i turn in circles because i don't seem to get any results for
some random pack combinations like (9,12,21) or (10,20,30).

That "if it is possible" is the big if. If every package you can buy has a
multiple of 3, then every possible purchase will be a multiple of 3.
Nothing that leaves a remainder of 1 or 2 will ever show up.
The must always be a solution i'm thinking, be it the smallest pack -
1

The set (9/3, 12/3, 21/3) settles down pretty quickly:
0 set([])
3 set([0])
4 set([0])
6 set([3])
7 set([0, 3, 4])
8 set([4])
9 set([6])
10 set([3, 6, 7])
11 set([8, 4, 7])
12 set([8, 9])
13 set([9, 10, 6])
14 set([10, 11, 7])
15 set([8, 11, 12])
16 set([9, 12, 13])
17 set([10, 13, 14])
18 set([11, 14, 15])
19 set([16, 12, 15])
The printout here is the 'genaeology' of each purchase -- the set of smaller
purchases from which you can reach each one. After ...,6,7,8,... every
larger number can be reached.

Mel.
 

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
473,774
Messages
2,569,596
Members
45,142
Latest member
DewittMill
Top