Как определить сумму группы целых чисел без использования рекурсии

Это мой первый пост на Stack Overflow, и я надеюсь, что он будет хорошим.

Это проблема, которую я придумал сам, и сейчас мне немного неловко об этом говорить, но она выбивает из меня живые лучи солнца. Обратите внимание, что это не домашнее задание, честь скаута.

По сути, программа принимает (в качестве входных данных) строку, состоящую из целых чисел от 0 до 9.

strInput = '2415043'

Затем вам нужно разбить эту строку чисел на более мелкие группы чисел, пока в конечном итоге сумма этих групп не даст вам заранее определенную сумму. В случае приведенной выше строки целью является 289.

iTarget = 289

Для этого примера есть два правильных ответа (но, скорее всего, будет показан только один, так как программа останавливается после достижения цели):

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

Ответ 2 = 241, 5, 0, 43 (241 + 5 + 0 + 43 = 289)

Обратите внимание, что целые числа не меняют положение. Они все еще в том же порядке, что и в исходной строке.

Теперь я знаю, как решить эту проблему с помощью рекурсии. Но разочаровывает то, что мне НЕ РАЗРЕШЕНО использовать рекурсию.

Эту проблему нужно решить, используя только циклы while и for. И, очевидно, списки и функции тоже подойдут.

Ниже приведен некоторый код, который у меня есть до сих пор:

Мой код:

                                         #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)

В общем, псевдокод будет выглядеть примерно так:

Псевдокод:

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

Фу! Это было довольно много.

Я проработал процесс, которому программа будет следовать, на листе бумаги, поэтому ниже приведен ожидаемый результат:

ВЫВОД:

[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

Теперь вот сложная часть. На следующем шаге первый элемент должен стать 24, а два других должны сбросить.

#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!!!!

Хорошо. Это основной поток логики программы. Теперь единственное, что мне нужно выяснить, это как заставить его работать без рекурсии.

Тех из вас, кто дочитал до этого места, я искренне благодарю и надеюсь, что у вас еще остались силы, чтобы помочь мне решить эту проблему. Если что-то непонятно, спрашивайте, и я поясню (вероятно, в мучительных подробностях X-D).

Спасибо еще раз!

Изменение: 1 сентября 2011 г.

Спасибо всем за отклик и за ваши ответы. Все они очень хороши и определенно более элегантны, чем маршрут, которым я следовал. Однако мои студенты никогда не работали с «импортом» или какими-либо более сложными структурами данных, чем списки. Однако они знают довольно много функций списка. Я также должен отметить, что студенты достаточно одарены математически, многие из них участвовали в международных математических олимпиадах и занимали призовые места. Так что это задание не выходит за рамки их интеллекта, возможно, только за пределы их знаний о питоне.

Прошлой ночью у меня была Эврика! момент. Я еще не реализовал это, но сделаю это в течение выходных, а затем опубликую свои результаты здесь. Это может быть несколько грубо, но я думаю, что это сделает работу.

Извините, что мне потребовалось так много времени, чтобы ответить, мой лимит интернета был достигнут, и мне пришлось ждать до 1-го числа, чтобы его сбросить. Что напоминает мне, всем счастливой весны (для тех из вас, кто живет в Южном полушарии).

Еще раз спасибо за ваш вклад. Я выберу лучший ответ после выходных. С Уважением!


person Victor Grobler    schedule 31.08.2011    source источник
comment
Рекурсия создает неявный стек — как это можно сделать явно?   -  person    schedule 31.08.2011
comment
Почему нельзя использовать рекурсию, если ты сам ее придумал? Вы взяли на себя обязательство использовать его для классного проекта или что-то в этом роде?   -  person Tom Zych    schedule 31.08.2011
comment
Домашнее задание? Конечно, похоже, если вам не разрешено использовать рекурсию.   -  person Ken White    schedule 31.08.2011
comment
Псевдовенгерская нотация? lstWorkingList? Фу!   -  person Johnsyweb    schedule 31.08.2011
comment
Я предполагаю, что вы имели в виду 043 в десятичном, а не в восьмеричном формате?   -  person Mechanical snail    schedule 31.08.2011
comment
@Tom Zych: Да, я решил использовать это для класса. Обучаю детей младшего возраста основам программирования. Я дал им это задание для практики, не думая, что это будет настолько сложно. Излишне говорить, что тот факт, что я борюсь с этим, дает мне мало надежды на то, что один из моих студентов решит его. Учитель не может решить свою проблему. Насколько это грустно?   -  person Victor Grobler    schedule 31.08.2011
comment
@Ken White: Причина, по которой мне не разрешено использовать рекурсию, заключается в том, что я еще не научил своих учеников, как ее использовать. Было бы несправедливо с моей стороны использовать технику, которую они никогда раньше не видели.   -  person Victor Grobler    schedule 31.08.2011
comment
@Механическая улитка: Да, я имею в виду 043 в десятичной системе. 043 = 43.   -  person Victor Grobler    schedule 31.08.2011
comment
Вы назначили это, прежде чем решить это самостоятельно? Что ж, думаю, ты больше не совершишь эту ошибку. Я не думаю, что какие-либо из приведенных ниже рабочих решений доступны для начинающих студентов, если только они не прошли весь процесс.   -  person agf    schedule 01.09.2011
comment
@agf: В этом вы определенно правы. Это единственная ошибка, которую я больше не повторю в будущем. Я боюсь, что назначение этого моим ученикам с их ограниченными базовыми инструментами и методами на самом деле сделало эту, казалось бы, простую программу намного, НАМНОГО сложнее.   -  person Victor Grobler    schedule 01.09.2011


Ответы (3)


Программа, которая находит все решения, может быть элегантно выражена в функциональном стиле.

Перегородки

Во-первых, напишите функцию, которая разбивает вашу строку всеми возможными способами. (Следующая реализация основана на http://code.activestate.com/recipes/576795/.) Пример:

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)]

Предикат

Теперь вам нужно отфильтровать список, чтобы найти те разделы, которые добавляются к желаемому значению. Итак, напишите небольшую функцию, чтобы проверить, удовлетворяет ли раздел этому критерию:

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))

Основная программа

Наконец, напишите свою основную программу:

strInput = '2415043'
iTarget = 289

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

Обратите внимание, что результат вычисляется в одной строке кода.

Результат: [['241', '5', '043'], ['241', '5', '0', '43']]

person Mechanical snail    schedule 31.08.2011
comment
Более аккуратная версия того же метода, который я использовал. - person agf; 31.08.2011

В любом случае рекурсия не лучший инструмент для работы. itertools.product есть.

Вот как я его ищу:

Представьте пространство поиска как все двоичные строки длины l, где l — длина вашей строки минус один.

Возьмите одну из этих двоичных строк

Запишите числа в двоичной строке между числами строки поиска.

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

Превратите 1 в запятые, а 0 в ничто.

2,4 1,5 0,4 3

Добавьте все это.

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

Это 289? Неа. Попробуйте еще раз с другой двоичной строкой.

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

Вы поняли идею.

На код!

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 опубликовал другую версию кода. Он объединяет строку вместо моего несколько хакерского умножения на 10 и добавления подхода. Кроме того, он использует true и false вместо моих констант JOIN и COMMA. Я бы сказал, что оба подхода одинаково хороши, но, конечно, я я предвзят. :)

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
person Nick ODell    schedule 31.08.2011
comment
Мне нравится этот метод; Я думаю, что это самое ясное. Вы можете упростить его еще больше (добавлены точки с запятой, чтобы показать разрывы строк): 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; - person agf; 02.09.2011

Чтобы расширить подсказку pst, вместо того, чтобы просто использовать стек вызовов, как это делает рекурсия, вы можете создать явный стек и использовать его для реализации рекурсивного алгоритма, фактически не вызывая ничего рекурсивно. Детали оставлены в качестве упражнения для студента ;)

person Tom Zych    schedule 31.08.2011
comment
Разве это не сложнее, чем использование рекурсии? - person Nick ODell; 31.08.2011
comment
@Ник ОДелл: Конечно. И теперь, когда ОП сообщил нам причину этого ограничения, мы видим, что это неподходящий подход. OTOH, если бы ОП был студентом, имеющим дело с произвольно наложенным ограничением (найдите способ сделать это без рекурсии), это было бы правильным решением. - person Tom Zych; 31.08.2011