Более элегантный способ инициализации списка дублированных элементов в Python

Если мне нужен список, инициализированный 5 нулями, это очень приятно и легко:

[0] * 5

Однако, если я изменю свой код, чтобы добавить более сложную структуру данных, например список нулей:

[[0]] * 5

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

[[0] for i in xrange(5)]

который кажется громоздким и использует переменную, поэтому иногда я даже делаю:

[[0] for _ in "     "]

Но если я хочу список списков нулей, он становится более уродливым:

[[[0] for _ in "     "] for _ in "     "]

все это вместо того, что я хочу сделать:

[[[0]]*5]*5

Кто-нибудь нашел элегантный способ справиться с этой "проблемой"?

Ответ 1

Подумав немного об этом, я придумал это решение: (7 строк без импорта)

# helper
def cl(n, func):
    # return a lambda, that returns a list, where func(tion) is called
    return (lambda: [func() for _ in range(n)])

def matrix(base, *ns):
    # the grid lambda (at the start it returns the base-element)
    grid = lambda: base

    # traverse reversed, to handle the midmost values first
    for n in reversed(ns):
        # assign a new lambda with the last grid within (and call it)
        grid = cl(n, grid)

    return grid() # call the full grid (but the matrix calls you ^^)

Тесты дают следующие результаты:

>>> from pprint import pprint as pp
>>> 
>>> matrix(None, 2,3)
[[None, None, None], [None, None, None]]
>>> 
>>> matrix(None, 4,3)
[[None, None, None], [None, None, None], [None, None, None], [None, None, None]]
>>> 
>>> x = matrix(None, 3,5,2)
>>> pp(x)
[[[None, None], [None, None], [None, None], [None, None], [None, None]],
 [[None, None], [None, None], [None, None], [None, None], [None, None]],
 [[None, None], [None, None], [None, None], [None, None], [None, None]]]
>>> x[1][3][0] = "test"
>>> pp(x)
[[[None, None], [None, None], [None, None], [None, None], [None, None]],
 [[None, None], [None, None], [None, None], ['test', None], [None, None]],
 [[None, None], [None, None], [None, None], [None, None], [None, None]]]

Другое решение, которое имеет преимущество использования "[[[0]] * 5] * 5" -синтетов:

def uniq(base, l):
    # function used to replace all values with the base
    nl = []
    for i in l:
        if type(i) is list:
            nl.append(uniq(base, i)) # recursion for deep lists
        else:
            nl.append(base)
    return nl

Тест:

# first arg is the base, the 0 inside the [] is just a dummy
# (for what None is the best choice usually)
>>> x = uniq(0, [[[0]]*5]*5)
>>> x[0][3][0] = 5
>>> pp(x)
[[[0], [0], [0], [5], [0]],
 [[0], [0], [0], [0], [0]],
 [[0], [0], [0], [0], [0]],
 [[0], [0], [0], [0], [0]],
 [[0], [0], [0], [0], [0]]]

кстати. библиотека numpy имеет np.zeros(s) -функцию, где s представляет собой форму типа (3,4,5)

>>> s = (2,2)
>>> np.zeros(s)
array([[ 0.,  0.],
       [ 0.,  0.]])

Наконец, тест производительности:

# functions are already defined ...
import timeit
>>> # Alex Martelli Code
>>> t1 = timeit.Timer( lambda: multi_dimension_list(None, 3,4,5) )
>>> # the two mentioned above
>>> t2 = timeit.Timer( lambda: matrix(None, 3,4,5) )
>>> t3 = timeit.Timer( lambda: uniq(None, [[[None]*5]*4]*3) )
>>> 
>>> t1.timeit(10000)
2.1910018920898438
>>> t2.timeit(10000)
0.44953203201293945
>>> t3.timeit(10000)
0.48807907104492188

Мне было очень интересно открыть эту проблему. Итак, спасибо за вопрос:)

Ответ 2

Если у меня было частое требование для списков списков списков... Я просто упакую его в небольшую функцию factory, например:

import copy

def multi_dimension_list(baseitem, *dimensions):
  dimensions = list(dimensions)
  result = [baseitem] * dimensions.pop(-1)
  for d in reversed(dimensions):
    result = [copy.deepcopy(result) for _ in range(d)]
  return result

eg = multi_dimension_list(0, 3, 4, 5)
print(eg)
# and just to prove the parts are independent...:
eg[1][1][1] = 23
print(eg)

На практике, я даже не беспокоюсь, потому что мое использование многомерных списков такого типа немного и далеко, так что понимание встроенных списков просто отлично. Однако общая идея создания собственного модуля небольших функций полезности для простых задач, которые вам нужно выполнять часто и (по вашему мнению), не выполняется элегантно встроенными идиомами, - это единственный путь! -)

Ответ 3

Другим является расширение класса списка:

import copy
class mlist(list):
  def __mul__(self, n):
    res = mlist()
    for _ in xrange(n):
      for l in self:
    res.append(copy.deepcopy(l))
  return res

то

>>> hey = mlist([mlist([0])])
>>> hey
[[0]]
>>> hey * 4
[[0], [0], [0], [0]]
>>> blah = hey * 4
>>> blah[0][0] = 9
>>> blah
[[9], [0], [0], [0]]

но инициализация mlist раздражает.

Ответ 4

Одним из решений является наличие вспомогательной функции:

import copy
def r(i,n):
    return [copy.deepcopy(i) for _ in xrange(n)]

то

r([0],5)
r(r([0],5),5)

Но этот синтаксис уродлив.