5

I've been working on some quick and dirty scripts for doing some of my chemistry homework, and one of them iterates through lists of a constant length where all the elements sum to a given constant. For each, I check if they meet some additional criteria and tack them on to another list.

I figured out a way to meet the sum criteria, but it looks horrendous, and I'm sure there's some type of teachable moment here:

# iterate through all 11-element lists where the elements sum to 8.
for a in range(8+1):
 for b in range(8-a+1):
  for c in range(8-a-b+1):
   for d in range(8-a-b-c+1):
    for e in range(8-a-b-c-d+1):
     for f in range(8-a-b-c-d-e+1):
      for g in range(8-a-b-c-d-e-f+1):
       for h in range(8-a-b-c-d-e-f-g+1):
        for i in range(8-a-b-c-d-e-f-g-h+1):
         for j in range(8-a-b-c-d-e-f-g-h-i+1):
            k = 8-(a+b+c+d+e+f+g+h+i+j)
            x = [a,b,c,d,e,f,g,h,i,j,k]
            # see if x works for what I want
3
  • [vals for vals in itertools.product(range(8), repeat=11) if sum(vals) == 8] is more beautiful, but much slower than your solution. Commented Mar 7, 2012 at 8:18
  • +1 - Props for using a computer to automate your repetitive chemistry homework. Commented Mar 7, 2012 at 9:21
  • My insight is this: for a list of 11 integers all summing to 8, a LOT of the numbers are going to be zero. A fast way of doing this would be to find all the ways of summing integers to 8 - for example 8, 1+7, 2+6, 3+5, 4+4, 1+1+6, 1+2+5... and then just permute those with the appropriate number of zeroes. Commented Mar 7, 2012 at 9:36

2 Answers 2

1

Here's a recursive generator that yields the lists in lexicographic order. Leaving exact as True gives the requested result where every sum==limit; setting exact to False gives all lists with 0 <= sum <= limit. The recursion takes advantage of this option to produce the intermediate results.

def lists_with_sum(length, limit, exact=True):
    if length:
        for l in lists_with_sum(length-1, limit, False):
            gap = limit-sum(l)
            for i in range(gap if exact else 0, gap+1):
                yield l + [i]
    else:
        yield []
Sign up to request clarification or add additional context in comments.

Comments

1

Generic, recursive solution:

def get_lists_with_sum(length, my_sum):
    if my_sum == 0:
        return [[0 for _ in range(length)]]

    if not length:
        return [[]]
    elif length == 1:
        return [[my_sum]]
    else:
        lists = []
        for i in range(my_sum+1):
            rest = my_sum - i
            sublists = get_lists_with_sum(length-1, rest)
            for sl in sublists:
                sl.insert(0, i)
                lists.append(sl)

    return lists

print get_lists_with_sum(11, 8)

5 Comments

Just tried running that. Oh my, that's slow... Maybe convert to something that uses a stack instead of full-blown recursion?
It's targeted at low values, of course. For the given numbers, i think it's acceptable. Building a cache into it will probably help, because the arguments will repeat quite often. Still the number of possible combinations will explode for higher valued arguments.
With arguments 8,11 it redlined my CPU for a good few minutes before I killed it. OP's answer is actually very fast (just ugly.)
And yes, memoizing it would probably help immensely.
Hurp. I must have done something funny (I tried the lazy man's way of running it from an interactive Python shell.) I'll try it again when I get to work tomorrow.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.