Циклически сокращать список операндов до одного результата - PullRequest
0 голосов
/ 25 июня 2018

(Это перефразированная версия другого вопроса с самостоятельным ответом, которая была закрыта из-за того, что она не была задана должным образом.)

У меня есть список целых чисел:

numbers = [1, 2, 3, 4, 5, 6]

Моя цель состоит в том, чтобы альтернативно применять операторы sum и умножения последовательно к этим числам, чтобы получить один результат.

Например, для этого ввода результат должен быть

((1 + 2) * 3 + 4) * 5 + 6

Что уменьшает до 71. По сути, это можно разбить на:

t1 =  1 + 2 
t2 = t1 * 3 
t3 = t2 + 4
... 

и так далее.

Бонус: приветствуется решение, которое может обобщать более двух циклических операций.

Ответы [ 3 ]

0 голосов
/ 25 июня 2018

Вот немного другой ответ, избегая использования next внутри лямбда-функции.

import operator
from itertools import cycle

def apply_cyclic(numbers, functions):
    numbers = iter(numbers)
    functions = cycle(functions)
    result = next(numbers)
    for num, fun in zip(numbers, functions):
        result = fun(result, num)
    result num

print(apply_cyclic([1,2,3,4,5,6], [operator.add, operator.mul]))
0 голосов
/ 25 июня 2018

Вот не-itertools подход для этой ситуации.

Во-первых, представьте, что существует версия functools.reduce, которая принимает 3 элемента из итерируемого одновременно. Давайте назовем эту гипотетическую функцию reduce3.

Если бы это существовало, мы могли бы сделать что-то вроде:

reduce3(lambda a, b, c: (a+b)*c, numbers)

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

1, 2, 3, 4, 5, 6  # Initial list
9, 4, 5, 6        # Step 1
65, 6             # Step 2
(65 + 6) * ??     # Step 3

Так что это почти то, что мы хотим, за исключением того, что на третьем шаге нет 3-го элемента, на который нужно умножиться. Фактически, это произойдет для любого чётного списка. Хорошо, тогда давайте просто добавим 1 к списку, если его длина равна:

if not len(numbers) % 2:
    numbers.append(1)

После этого третьим шагом будет:

(65 + 6)*1

Что дает правильный ответ 71.

К сожалению, этой магической функции не существует. Но мы можем изменить исходный список, чтобы имитировать эту функцию. Нам просто нужно взять список чисел и сгруппировать последовательные пары чисел, не включая первый элемент, в кортежи. Также, если список четной длины, нам нужно добавить элемент 1 в конец.

По сути, давайте напишем функцию preprocess(), чтобы превратить [1, 2, 3, 4, 5, 6] в [1, (2, 3), (4, 5), (6, 1)].

def preprocess(myList):
    my_output = [myList[0], *zip(numbers[1::2], numbers[2::2])]
    if not len(myList) % 2:
        my_output.append((myList[-1], 1))
    return my_output

print(preprocess(numbers))
#[1, (2, 3), (4, 5), (6, 1)]

Теперь мы можем reduce обработанный список:

from functools import reduce
result = reduce(lambda a, b: (a+b[0])*b[1], preprocess(numbers))
print(result)
#71

reducer принимает 2 входа - число и кортеж. Он добавляет число к первому элементу кортежа и умножает результат на второй. В результате получается другое число, которое затем передается следующей операции сокращения.


Обновление

Вот общая реализация reduceN. N определяется длиной переданных функций, поэтому ее можно обобщить для любого числа функций.

from itertools import islice  # couldn't get away from itertools this time

def reduceN(functions, iterable, initializer=None):
    it = iter(iterable)
    n = len(functions)
    if initializer is None:
        value = next(it)
    else:
        value = initializer
    elements = list(islice(it, n))
    while(elements):
        for fn, el in zip(functions, elements):
            value = fn(value, el)
        elements = list(islice(it, n))
    return value

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

from operator import add, mul
numbers = [1, 2, 3, 4, 5, 6]
functions = [add, mul]
print(reduceN(functions, numbers))
#71

И если мы удалим последний элемент из numbers:

print(reduceN(functions=functions, iterable=[1, 2, 3, 4, 5]))
#65
0 голосов
/ 25 июня 2018

Одно решение будет включать построение циклического генератора с использованием itertools.cycle и применение каждой функции поочередно внутри functools.reduce.

from itertools import cycle
from functools import reduce
import operator

fn = cycle((operator.add, operator.mul))
result = reduce(lambda x, y: next(fn)(x, y), numbers)

print(result)
71

Преимущество этого решения заключается в том, чтоВы можете изменить определение fn, чтобы иметь любое число операторов, которые должны быть применены последовательно:

fn = cycle((operator.add, operator.mul, operator.sub, ...))

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

Примечание: унарные операторы не поддерживаются.

...