Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to determine the sum of a group of integers without using recursion

This is my first post on Stack Overflow, and I'm hoping that it'll be a good one.

This is a problem that I thought up myself, and now I'm a bit embarrassed to say, but it's beating the living daylights out of me. Please note that this is not a homework exercise, scout's honor.

Basically, the program takes (as input) a string made up of integers from 0 to 9.

strInput = '2415043'

Then you need to break up that string of numbers into smaller groups of numbers, until eventually, the sum of those groups give you a pre-defined total. In the case of the above string, the target is 289.

iTarget = 289

For this example, there are two correct answers (but most likely only one will be displayed, since the program stops once the target has been reached):

Answer 1 = 241, 5, 043    (241 + 5 + 043    = 289)  

Answer 2 = 241, 5, 0, 43 (241 + 5 + 0 + 43 = 289)

Note that the integers do not change position. They are still in the same order that they were in the original string.

Now, I know how to solve this problem using recursion. But the frustrating part is that I'm NOT ALLOWED to use recursion.

This needs to be solved using only 'while' and 'for' loops. And obviously lists and functions are okay as well.

Below is some of the code that I have so far:

My Code:

                                         #Pre-defined input values, for the sake of simplicity
lstInput = ['2','4','1','5','0','4','3'] #This is the kind of list the user will input
sJoinedList = "".join(lstInput)          #sJoinedList = '2415043'
lstWorkingList = []                      #All further calculuations are performed on lstWorkingList
lstWorkingList.append(sJoinedList)       #lstWorkingList = ['2415043']
iTarget = 289                            #Target is pre-defined

-

def SumAll(_lst):          #Adds up all the elements in a list
   iAnswer = 0             #E.g. lstEg = [2,41,82]
     for r in _lst:        #     SumAll(lstEg) = 125
       iAnswer += int(r)
   return(iAnswer) 

-

def AddComma(_lst):
                                  #Adds 1 more comma to a list and resets all commas to start of list
                                  #E.g. lstEg = [5,1001,300]  (Note only 3 groups / 2 commas)
                                  #     AddComma(lstEg)
                                  #     [5,1,0,001300] (Now 4 groups / 3 commas)
    iNoOfCommas = len(_lst) - 1   #Current number of commas in list
    sResetString = "".join(_lst)  #Make a string with all the elements in the list
    lstTemporaryList = []
    sTemp = ""
    i = 0
    while i < iNoOfCommas +1:
        sTemp += sResetString[i]+','    #Add a comma after every element
        i += 1
    sTemp += sResetString[i:]       
    lstTemporaryList = sTemp.split(',') #Split sTemp into a list, using ',' as a separator
                                        #Returns list in format ['2', '415043'] or ['2', '4', '15043']
    return(lstTemporaryList)
    return(iAnswer)

So basically, the Pseudo-code will look something like this:

Pseudo-Code:

while SumAll(lstWorkingList) != iTarget:      #While Sum != 289
    if(len(lstWorkingList[0]) == iMaxLength): #If max possible length of first element is reached
        AddComma(lstWorkingList)              #then add a new comma / group and
        Reset(lstWorkingList)                 #reset all the commas to the beginning of the list to start again
    else:
        ShiftGroups()                         #Keep shifting the comma's until all possible combinations
                                              #for this number of comma's have been tried
                                              #Otherwise, Add another comma and repeat the whole process

Phew! That was quite a mouthfull .

I have worked through the process that the program will follow on a piece of paper, so below is the expected output:

OUTPUT:

[2415043]  #Element 0 has reached maximum size, so add another group 
#AddComma()
#Reset()
[2, 415043] #ShiftGroups()
[24, 15043] #ShiftGroups()
[241, 5043] #ShiftGroups()
#...etc...etc...
[241504, 3] #Element 0 has reached maximum size, so add another group
#AddComma()
#Reset()
[2, 4, 15043] #ShiftGroups()
[2, 41, 5043] #ShiftGroups()
#etc...etc...

[2, 41504, 3] #Tricky part

Now here is the tricky part. In the next step, the first element must become 24, and the other two must reset.

#Increase Element 0
#All other elements Reset() 
[24, 1, 5043] #ShiftGroups()
[24, 15, 043] #ShiftGroups()
#...etc...etc

[24, 1504, 3]
#Increase Element 0
#All other elements Reset()
[241, 5, 043] #BINGO!!!!

Okay. That is the basic flow of the program logic. Now the only thing I need to figure out, is how to get it to work without recursion.

For those of you that have been reading up to this point, I sincerely thank you and hope that you still have the energy left to help me solve this problem. If anything is unclear, please ask and I'll clarify (probably in excruciating detail X-D).

Thanks again!

Edit: 1 Sept 2011

Thank you everyone for responding and for your answers. They are all very good, and definitely more elegant than the route I was following. However, my students have never worked with 'import' or any data-structures more advanced than lists. They do, however, know quite a few list functions. I should also point out that the students are quite gifted mathematically, many of them have competed and placed in international math olympiads. So this assignment is not beyond the scope of their intelligence, perhaps only beyond the scope of their python knowledge.

Last night I had a Eureka! moment. I have not implemented it yet, but will do so over the course of the weekend and then post my results here. It may be somewhat crude, but I think it will get the job done.

Sorry it took me this long to respond, my internet cap was reached and I had to wait until the 1st for it to reset. Which reminds me, happy Spring everyone (for those of you in the Southern Hempisphere).

Thanks again for your contributions. I will choose the top answer after the weekend. Regards!

like image 239
Victor Grobler Avatar asked Aug 31 '11 00:08

Victor Grobler


3 Answers

A program that finds all solutions can be expressed elegantly in functional style.

Partitions

First, write a function that partitions your string in every possible way. (The following implementation is based on http://code.activestate.com/recipes/576795/.) Example:

def partitions(iterable):
    'Returns a list of all partitions of the parameter.'
    from itertools import chain, combinations
    s = iterable if hasattr(iterable, '__getslice__') else tuple(iterable)
    n = len(s)
    first, middle, last = [0], range(1, n), [n]
    return [map(s.__getslice__, chain(first, div), chain(div, last))
            for i in range(n) for div in combinations(middle, i)]

Predicate

Now, you'll need to filter the list to find those partitions that add to the desired value. So write a little function to test whether a partition satisfies this criterion:

def pred(target):
   'Returns a function that returns True iff the numbers in the partition sum to iTarget.'
   return lambda partition: target == sum(map(int, partition))

Main program

Finally, write your main program:

strInput = '2415043'
iTarget = 289

# Run through the list of partitions and find partitions that satisfy pred
print filter(pred(iTarget), partitions(strInput))

Note that the result is calculated in a single line of code.

Result: [['241', '5', '043'], ['241', '5', '0', '43']]

like image 198
Mechanical snail Avatar answered Nov 08 '22 04:11

Mechanical snail


Recursion isn't the best tool for the job anyways. itertools.product is.

Here's how I search it:

Imagine the search space as all the binary strings of length l, where l is the length of your string minus one.

Take one of these binary strings

Write the numbers in the binary string in between the numbers of your search string.

2 4 1 5 0 4 3
 1 0 1 0 1 0

Turn the 1's into commas and the 0's into nothing.

2,4 1,5 0,4 3

Add it all up.

2,4 1,5 0,4 3 = 136

Is it 289? Nope. Try again with a different binary string.

2 4 1 5 0 4 3
 1 0 1 0 1 1

You get the idea.

Onto the code!

import itertools

strInput = '2415043'
intInput = map(int,strInput)
correctOutput = 289

# Somewhat inelegant, but what the heck
JOIN = 0
COMMA = 1

for combo in itertools.product((JOIN, COMMA), repeat = len(strInput) - 1):
    solution = []
    # The first element is ALWAYS a new one.
    for command, character in zip((COMMA,) + combo, intInput):
        if command == JOIN:
            # Append the new digit to the end of the most recent entry
            newValue = (solution[-1] * 10) + character
            solution[-1] = newValue
        elif command == COMMA:
            # Create a new entry
            solution.append(character)
        else:
            # Should never happen
            raise Exception("Invalid command code: " + command)
    if sum(solution) == correctOutput:
        print solution

EDIT: agf posted another version of the code. It concatenates the string instead of my somewhat hacky multiply by 10 and add approach. Also, it uses true and false instead of my JOIN and COMMA constants. I'd say the two approaches are equally good, but of course I am biased. :)

import itertools
strInput = '2415043'
correctOutput = 289
for combo in itertools.product((True, False), repeat = len(strInput) - 1):
    solution = []
    for command, character in zip((False,) + combo, strInput):
        if command:
            solution[-1] += character
        else:
            solution.append(character)
            solution = [int(x) for x in solution]
        if sum(solution) == correctOutput:
            print solution
like image 32
Nick ODell Avatar answered Nov 08 '22 06:11

Nick ODell


To expand on pst's hint, instead of just using the call stack as recursion does, you can create an explicit stack and use it to implement a recursive algorithm without actually calling anything recursively. The details are left as an exercise for the student ;)

like image 31
Tom Zych Avatar answered Nov 08 '22 04:11

Tom Zych