Создание треугольника Паскаля в словаре

Я пытаюсь создать функцию, которая возвращает словарь, описывающий треугольник Паскаля.

Например,

pascal(3)

дал бы мне

{1: [1], 2: [1,1], 3: [1,2,1]} 

В настоящее время я знаю, как создать функцию, которая возвращает список элементов в определенной строке для n, равного или большего 2.

def pascal(n):
 if n == 0:
    return {}
 elif n == 1:
    return {1:[1]}
 else:
    row = [1] + [list(pascal(n-1))[i] + list(pascal(n-1))[i+1] for i in range(n-2)] + [1]
    return row

С помощью этой функции

pascal(3)

дает мне

[1,2,1]

Можно ли изменить мою функцию таким образом, чтобы

pascal(3)

возвращает желаемый результат

{1: [1], 2: [1,1], 3: [1,2,1]} 

Любая помощь будет оценена.


person 1231231231231    schedule 06.11.2018    source источник
comment
Вы готовы иметь список списков вместо словаря? в любом случае ключи вашего dict - это просто индексы строк   -  person Srini    schedule 07.11.2018


Ответы (5)


Вы можете использовать zip, чтобы объединить список, возвращаемый из рекурсивного вызова, с тем же списком, но с одним индексом, дополненным 0:

def pascal(n):
    if n == 1:
        return {1: [1]}
    p = pascal(n - 1)
    p[n] = list(map(sum, zip([0] + p[n - 1], p[n - 1] + [0])))
    return p

так что:

for n in range(1, 6):
    print(pascal(n))

выходы:

{1: [1]}
{1: [1], 2: [1, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1], 5: [1, 4, 6, 4, 1]}
person blhsing    schedule 06.11.2018
comment
Понимание списка OP кажется нормальным. Поэтому вместо использования zip они также могут заменить его тем, что у них было раньше: p[n] = [1] + [p[n - 1][i] + p[n - 1][i + 1] for i in range(n - 2)] + [1] - person slider; 07.11.2018
comment
Да, но я предпочитаю zip, потому что в целом в Pythonic считается более подходящим использовать итераторы, а не индексы списков. - person blhsing; 07.11.2018

Если вы открыты для итеративного решения, я придумал следующее.

from itertools import chain 

def pascal(n):
    pad = (0,)
    result = {1: [1]}
    for i in range(2, n + 1):
        previous = list(chain(pad, result[i - 1], pad))
        result[i] = [sum(pair) for pair in zip(previous, previous[1:])]
    return result

Демо:

>>> for n in range(1, 6):
...:    print(pascal(n))
...:    
...:    
{1: [1]}
{1: [1], 2: [1, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1]}
{1: [1], 2: [1, 1], 3: [1, 2, 1], 4: [1, 3, 3, 1], 5: [1, 4, 6, 4, 1]}

С немного большим количеством строк, но с большей эффективностью памяти:

from itertools import chain, tee

def pascal(n):
    pad = (0,)
    result = {1: [1]}
    for i in range(2, n + 1):
        previous = chain(pad, result[i - 1], pad)
        c1, c2 = tee(previous)
        next(c2)
        result[i] = [sum(pair) for pair in zip(c1, c2)]
    return result

Наконец, наличие dict с последовательными целочисленными ключами не очень полезно, вы можете просто использовать список, в который вы индексируете, начиная с 0. Окончательное решение:

def pascal(n):
    pad = (0,)
    result = [[1]]
    for i in range(1, n):
        previous = chain(pad, result[i - 1], pad)
        c1, c2 = tee(previous)
        next(c2)
        result.append([sum(pair) for pair in zip(c1, c2)])
    return result

Демо:

>>> for n in range(1, 6):
...:    print(pascal(n))
...:    
[[1]]
[[1], [1, 1]]
[[1], [1, 1], [1, 2, 1]]
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]

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

person timgeb    schedule 06.11.2018
comment
Оба ваших решения возвращают n-1th, а не nth - person NinjaKitty; 07.11.2018

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

Очевидно, что при n = 1 функция вызывается один раз.

Когда n = 2, функция вызывается один раз, а затем функция вызывает себя дважды, всего 3 вызова.

Для n = 3 функция вызывается один раз, затем функция вызывает себя дважды, а затем эти два вызова вызывают функцию четыре раза ... Итак, это 11 вызовов.

Итак, количество звонков numCalls = 1 + 2 + 2*4 + 2*4*6 + ... + 2*4*6*...*2n)

Эта последовательность растет очень быстро ... Когда n равно 20, это 1308293051285742128434781 Звонки

Рекурсия - это не всегда зло, вам просто нужно быть осторожным, это решение вызывает себя n раз:

    def genPascalDict(nMax):
        if nMax < 2:
            return {1: [1]}
        else:
            pascalDict = genPascalDict(nMax - 1)
            lastRow = pascalDict[nMax - 1]
            pascalDict[nMax] = [1] + [lastRow[n + 1] + lastRow[nMax - n - 2] for n in range(nMax - 2)] + [1]
            return pascalDict
person Theo Emms    schedule 06.11.2018

Вы можете сделать это быстро, создавая свой диктант в качестве побочного эффекта:

_cache = {}

def pascal(n):
    try:
        result = _cache[n]
    except KeyError:
        if n == 0:
            result = []
        elif n == 1:
            result = [1]
        else:
            previous = pascal(n - 1)
            result = [1] + [previous[i] + previous[i + 1] for i in range(n - 2)] + [1]
        _cache[n] = result
    return result

pascal(500)

print(_cache)

Вам не нужно вычислять pascal(n) больше одного раза: это не похоже на изменение. Так что запомните, каким был ваш окончательный ответ, сохранив его в кеширующем словаре, причем указанный словарь был тем, что вы действительно хотели в первую очередь.

На создание словаря на моем ноутбуке уходит около 0,08 с.

person Kirk Strauser    schedule 06.11.2018

Вы можете использовать закрытие с рекурсией:

def pascal(n:int) -> dict:
  def _pascal(_s, _e, _last, _base={1:[1], 2:[1, 1]}):
    return _last if not _e-_s else _pascal(_s+1, _e, {**_last, **{_s:_base.get(_s, [1, *[_last[_s-1][i]+_last[_s-1][i+1] for i in range(len(_last)-1)], 1])}})
  return _pascal(1, n+1, {})

print(pascal(3))

Вывод:

{1: [1], 2: [1, 1], 3: [1, 2, 1]}
person Ajax1234    schedule 07.11.2018