Заменить вложенные циклы или нет

У меня есть сценарий, который перебирает серию из четырех (или менее) строк символов. Например:

aaaa
aaab
aaac
aaad

Если удалось реализовать это с помощью вложенных циклов for, например:

chars = string.digits + string.uppercase + string.lowercase

for a in chars:
    print '%s' % a   
    for b in chars:
        print '%s%s' % (a, b)
        for c in chars:
            print '%s%s%s' % (a, b, c)
            for d in chars:
                print '%s%s%s%s' % (a, b, c, d)

Является ли такая вложенность циклов плохой вещью, и если да, то что было бы лучше для выполнения того, что я делаю?


person Matt in PA    schedule 27.01.2009    source источник
comment
попробуйте придумать лучший алгоритм, чем грубая сила. Может быть, рекурсия или разделяй и властвуй   -  person Perpetualcoder    schedule 27.01.2009
comment
@Perpetual, мне кажется, что, поскольку вы должны генерировать ВСЕ возможности, не имеет большого значения, является ли это грубой силой или нет. Алгоритмы без грубой силы хороши, когда вы можете минимизировать то, что вам нужно, но я не думаю, что здесь дело обстоит именно так. Рекурсия дает вам меньший исходный код, но это все.   -  person paxdiablo    schedule 27.01.2009


Ответы (7)


import string
import itertools

chars = string.digits + string.letters
MAX_CHARS = 4
for nletters in range(MAX_CHARS):
    for word in itertools.product(chars, repeat=nletters + 1):
        print (''.join(word))

Это напечатает все слова, которые вы ищете. Если вы хотите больше / меньше слов, просто измените переменную MAX_CHARS. В нем по-прежнему будет всего два for для любого количества символов, и вам не придется повторяться. И довольно читабельно. .

person nosklo    schedule 27.01.2009
comment
Синтаксическая ошибка ... отсутствует двоеточие. - person Algorias; 27.01.2009
comment
Следует упомянуть, что для этого требуется python 2.6, но в остальном это решение, которое я собирался опубликовать. :) - person Nick; 27.01.2009
comment
довольно читабельно? - Я все еще думаю, что оригинал более понятен, но тогда я динозавр и никогда не видел itertools, поэтому я не буду -1 вас :-). - person paxdiablo; 27.01.2009
comment
Можете ли вы объяснить, почему он не удовлетворяет вложенным for, когда я вижу две явно вложенные копии ключевого слова? - person Jonathan Leffler; 27.01.2009
comment
На самом деле я подумал, что сделаю проверку производительности, чтобы узнать, насколько быстрее itertools. Результаты оказались неожиданными - в Python 2.6.1 (Windows) itertools НАМНОГО медленнее (2,16 вместо 0,45 единиц time.clock ()). Без вывода и запускать по пять раз в сценарии, чтобы убрать время запуска. - person paxdiablo; 27.01.2009
comment
Отказаться от последнего утверждения теста (это был хитрый тест :-). Исходный вопрос занял 2,84 единицы, оптимизация (см. Мой ответ ниже) заняла 2,25, а itertools - 2,27. Итак, itertools кажется немного медленнее, но, вероятно, этого недостаточно, чтобы беспокоиться. - person paxdiablo; 27.01.2009
comment
@Nick: Если вам это нужно в python ‹2.6, здесь есть реализация product (): docs.python.org/library/itertools#itertools.product - person nosklo; 27.01.2009
comment
@Jonathan: Потому что вы можете использовать 20, 30, 40 символов, сколько хотите, не добавляя дополнительных символов. Я изменил текст, чтобы лучше отразить то, что я имею в виду. Его также можно было бы написать для использования сингла, используя itertools.chain.from_iterable (). - person nosklo; 27.01.2009
comment
строка.ascii_letters! = (строка.lowercase + строка. верхний регистр). lowercase, uppercase зависят от локали. - person jfs; 27.01.2009
comment
Я никогда не знал об itertools.product или, по крайней мере, никогда не изучал его так много. Это отличный ответ. - person sykora; 27.01.2009
comment
(string.lowercase + string.uppercase) == string.letters - person jfs; 27.01.2009
comment
@nosklo Спасибо за ответ! - person Matt in PA; 27.01.2009

Я собираюсь представить свой ответ как наиболее читаемый и наименее масштабируемый :)

import string
chars = [''] + list(string.lowercase)

strings = (a+b+c+d for a in chars
                   for b in chars
                   for c in chars
                   for d in chars)

for string in strings:
    print string

РЕДАКТИРОВАТЬ: На самом деле это неверно, так как это приведет к дублированию всех строк длиной ‹4. Удаление пустой строки из массива chars приведет к созданию строк из 4 символов.

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

person Triptych    schedule 27.01.2009
comment
Мне это нравится ... это похоже на невидимую бомбу замедленного действия. - person Tom; 27.01.2009
comment
Он охватывает все слова, обратите внимание на пустую строку в списке символов. - person sth; 27.01.2009
comment
Ваше решение гораздо более читабельно, но оно дает другой результат: stackoverflow.com/questions/482146/ - person jfs; 27.01.2009
comment
Ага, он будет производить дубликаты каждой строки длиной ‹4. Сделаю заметку. - person Triptych; 27.01.2009

Пишите сначала для программиста, а затем - для компьютера.
Если это ясно и очевидно, то это правильно.

Если скорость имеет значение, И компилятор все равно не оптимизирует ее, И если вы ее измеряете, И это проблема - тогда подумайте о более быстром и умном способе!

person Martin Beckett    schedule 27.01.2009
comment
Не каждый программист - теоретик. У большинства из нас есть работа, которую нужно выполнить к определенному сроку, и мы не привязаны к процессору. В этом случае mgb является правильным, и вам следует писать для программиста. Алгоритм OP не такой, но это не значит, что mgb неверен, хотя, возможно, и бесполезен. - person Ed S.; 27.01.2009

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

Поскольку вам нужно генерировать все возможные варианты «слов» из 1, 2, 3 и 4 символов, этот метод ничем не хуже других. Я не уверен, сколько времени это займет, поскольку вы эффективно генерируете (очень примерно) 14 миллионов строк вывода (но, вероятно, каждое решение будет иметь эту проблему).

Предварительный расчет общих префиксов может повысить скорость, но лучше измерить его, чтобы проверить (всегда проверять, никогда не предполагать):

chars = string.digits + string.uppercase + string.lowercase
for a in chars:
    print a
    for b in chars:
        ab = '%s%s' % (a, b)
        print ab
        for c in chars:
            abc = '%s%s' % (ab, c)
            print abc
            for d in chars:
                print '%s%s' % (abc, d)

РЕДАКТИРОВАТЬ: Я действительно провел несколько тестов (с Windows-Python 2.6.1) - эта версия занимает около 2,25 единиц времени по сравнению с исходной 2,84, поэтому она на 26% быстрее. Я думаю, это может оправдать его использование (опять же, если четко задокументировано, чего он пытается достичь).

person paxdiablo    schedule 27.01.2009
comment
Вы можете сэкономить время, всегда выполняя простой a+b вместо '%s%s' % (a, b) - person sth; 27.01.2009

@ nosklo's и Решения @ Triptych дают разные результаты:

>>> list(map(''.join, itertools.chain.from_iterable(itertools.product("ab", 
...     repeat=r) for r in range(4)))) # @nosklo's 
['', 'a', 'b', 'aa', 'ab', 'ba', 'bb', 'aaa', 'aab', 'aba', 'abb', 'baa', 
 'bab', 'bba', 'bbb']
>>> ab = ['']+list("ab")
>>> list(map(''.join, (a+b+c for a in ab for b in ab for c in ab)))  
['', 'a', 'b', 'a', 'aa', 'ab', 'b', 'ba', 'bb', 'a', 'aa', 'ab', 'aa', 
 'aaa', 'aab', 'ab', 'aba', 'abb', 'b', 'ba', 'bb', 'ba', 'baa', 'bab', 
 'bb',  'bba', 'bbb']

Вот модифицированное решение @ Triptych, которое дает тот же результат, что и решение @ nosklo:

>>> ab = "ab"
>>> list(map(''.join, itertools.chain([''], ab, (a+b for a in ab for b in ab),
...     (a+b+c for a in ab for b in ab for c in ab))))
['', 'a', 'b', 'aa', 'ab', 'ba', 'bb', 'aaa', 'aab', 'aba', 'abb', 'baa', 
 'bab', 'bba', 'bbb']
person jfs    schedule 27.01.2009

Есть много алгоритмов для генерации каждой перестановки набора. Здесь вам нужна связанная проблема, но не имеющая прямого отношения к ней. Рекомендуемая литература

person Sparr    schedule 27.01.2009

Это не совсем ответ на вопрос, но это вернет комбинацию nth для заданной максимальной длины и символов в алфавите для использования:

#!/usr/bin/python

def nth_combination(n, maxlen=4, alphabet='abc'):
    """
    >>> print ','.join(nth_combination(n, 1, 'abc') for n in range(3))
    a,b,c
    >>> print ','.join(nth_combination(n, 2, 'abc') for n in range(12))
    a,aa,ab,ac,b,ba,bb,bc,c,ca,cb,cc
    >>> import string ; alphabet = string.ascii_letters + string.digits
    >>> print ','.join(nth_combination(n, 4, alphabet) for n in range(16))
    a,aa,aaa,aaaa,aaab,aaac,aaad,aaae,aaaf,aaag,aaah,aaai,aaaj,aaak,aaal,aaam
    >>> print ','.join(nth_combination(n, 4, alphabet)
    ...                for n in range(0, 14000000, 10**6))
    a,emiL,iyro,mKz2,qWIF,u8Ri,zk0U,Dxav,HJi9,LVrM,P7Ap,UjJ1,YvSE,2H1h
    """
    if maxlen == 1:
        return alphabet[n]
    offset, next_n = divmod(n, 1 + len(alphabet)**(maxlen-1))
    if next_n == 0:
        return alphabet[offset]
    return alphabet[offset] + nth_combination(next_n-1, maxlen-1, alphabet)

if __name__ == '__main__':
    from doctest import testmod
    testmod()

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

Если maxlen высокий, может быть достигнута некоторая оптимизация скорости, например избавившись от конкатенации строк и повторно вычислив длину alphabet и maxlen-1 на каждом уровне рекурсии. Нерекурсивный подход тоже может иметь смысл.

person akaihola    schedule 27.01.2009