在一个字符串中生成所有可能的字符组合

9

假设我有一个字符串列表:

li = ['a', 'b', 'c']

我想构建一个新列表,使得新列表的每个条目都是原始列表中3个条目的连接。请注意,可以重复选择每个条目:
new_li=['abc', 'acb', 'bac', 'bca', 'cab', 'cba', 'aab', 'aac',....'aaa', 'bbb', 'ccc']

一种暴力的方法是构建一个三重嵌套的for循环,并将每个3组合插入新列表中。我想知道是否有任何Pythonic的方法来处理这个问题?谢谢。

更新: 稍后我会将新列表转换为集合,所以顺序无论如何都不重要。


必须是随机的吗?列表应该有多长? - whackamadoodle3000
@whackamadoodle3000 请查看更新。列表的长度应为3^n,其中n是原始列表中条目的数量。 - James LT
长度应该是n的n次方而不是3的n次方。 - Karem Mohamed
6个回答

18

这看起来像是itertools.product的工作。

import itertools

def foo(l):
     yield from itertools.product(*([l] * 3)) 

for x in foo('abc'):
     print(''.join(x))

aaa
aab
aac
aba
abb
abc
aca
acb
acc
baa
bab
bac
bba
bbb
bbc
bca
bcb
bcc
caa
cab
cac
cba
cbb
cbc
cca
ccb
ccc

yield from自Python3.3及以上版本可用。对于旧版本,可以在循环中使用yield


def foo(l):
     for i in itertools.product(*([l] * 3)) :
         yield i

谢谢。如果原始列表中的每个条目不是单个字符,例如li=['a1','b2','c3'],该怎么办? - James LT
2
@James 它的工作方式完全相同。你可以试一试。 - cs95

12

获取列表的所有组合(也称笛卡尔积)的最佳方法是使用itertools.product,并将您的可迭代对象的长度作为repeat参数(这是与其他答案不同的地方):

from itertools import product
li = ['a', 'b', 'c']
for comb in product(li, repeat=len(li)):
    print(''.join(comb))

或者如果您想要结果以列表形式呈现:

>>> combs = [''.join(comb) for comb in product(li, repeat=len(li))]
>>> combs
['aaa', 'aab', 'aac', 'aba', 'abb', 'abc', 'aca', 'acb', 'acc', 'baa', 
 'bab', 'bac', 'bba', 'bbb', 'bbc', 'bca', 'bcb', 'bcc', 'caa', 'cab', 
 'cac', 'cba', 'cbb', 'cbc', 'cca', 'ccb', 'ccc']

使用repeat参数比手动乘以及展开列表更加简洁。


3
一种使用列表推导式的替代方法:
li = ['a', 'b', 'c']

new_li = [a+b+c for a in li for b in li for c in li]

2
目前你的回答不够清晰,请编辑并添加更多细节,以帮助其他人理解它如何回答问题。你可以在帮助中心找到有关如何编写好答案的更多信息。 - Community
聪明,但仅适用于三个元素。如果我有10或100个元素怎么办?你能改进它吗? :) - Ignatius Reilly

0

我将向您展示一种无需任何库即可完成此操作的方法,以便您了解实现它的逻辑。

首先,我们需要了解如何在数学上实现所有组合。

让我们来看看从a-b范围内长度为“1”的每个可能字符组合的模式。

a
b

看起来没什么可看的,但从我们所看到的来看,列表中每个字符只有一个集合。让我们将字符串长度增加到“2”,看看会出现什么模式。

aa
ab
ba
bb

观察这个模式,我们可以看到添加了一列新的数据。最右边的一列与第一个例子相同,只有1组字符,但这次是循环的。最左边的一列有2组字符。每添加一列,是否会再添加一组字符呢?让我们通过将字符串长度增加到“3”来查看并找出答案。

aaa
aab
aba
abb
baa
bab
bba
bbb

我们可以看到右侧的两列保持不变,而左侧的新列每个字符都有4个!这不是我们预期的结果。因此,每列字符的数量并不是每列增加1个。相反,如果您注意到模式,实际上是以2的幂次递增。

只有“1”组字符的第一列:2 ^ 0 = 1

有“2”组字符的第二列:2 ^ 1 = 2

有“4”组字符的第三列:2 ^ 2 = 4

因此,答案是,每添加一列,该列中每个字符的数量由其幂次位置确定,右侧的第一列为x ^ 0,然后是x ^ 1,然后是x ^ 2...等等。

但是x是什么?在我给出的示例中,x = 2。但它总是2吗?让我们来看看。

现在,我将给出从a-c范围内每种可能组合的示例。

aa
ab
ac
ba
bb
bc
ca
cb
cc

如果我们计算右侧第一列中有多少个字符,每次循环仍然只有一个字符集,这是因为右侧的第一列始终等于x ^ 0,而任何数的0次方都是1。但是,如果我们看第二列,我们会发现每次循环都有3个相同的字符。因此,如果x ^ 1是第二列,则x = 3。对于我给出的第一个范围为a-b(范围为2)的示例,以及使用范围a-c(范围为3)的第二个示例,似乎x始终是您组合中使用的字符长度。

有了这个第一个模式,我们可以开始构建一个函数,该函数可以识别每个列应该表示什么。如果我们想从范围a-b构建字符串长度为3的每个字符组合,则需要一个函数,该函数可以理解每个列中的每组字符如下:[4, 2, 1]。

现在创建一个函数,通过返回表示基于其位置的列中字符总数的数字列表来查找每个列中应该有多少字符集。我们使用幂来实现这一点。

记得,如果我们使用从 a 到 b 的字符范围(2),则每个集合的每个列应该有 x ^ y 个字符总数,其中 x 表示所使用的字符长度,y 表示其列位置,最右边的第一列是第 0 列。

例如:

在字符串长度为 3 的情况下,由 ['a','b'] 范围内的字符组合将在每个集合的最左侧列中具有 4 个 a 和 b, 在下一个中每个集合中有 2 个 a 和 b, 在最后一个中每个集合中有 1 个 a 和 b。

要返回一个列表,其中包含与其各自列对应的字符总数,如 [4, 2, 1],我们可以这样做

def getCharPower(stringLength, charRange):
    charpowers = []
    for x in range(0, stringLength):
            charpowers.append(len(charRange)**(stringLength - x - 1))
    return charpowers

使用上述函数,如果我们想要创建从a-b(2)范围内的每个可能的字符组合,并且字符串长度为4,就像这样

aaaa
aaab
aaba
aabb
abaa
abab
abba
abbb
baaa
baab
baba
babb
bbaa
bbab
bbba
bbbb

假设我们有一个包含(8)个a和b,(4)个a和b,(2)个a和b以及(1)个a和b的总集合,那么我们想要返回一个列表[8, 4, 2, 1]。字符串长度为4,字符范围为['a', 'b'],我们函数的结果是[8, 4, 2, 1]

现在我们只需要根据返回列表中每个元素的值,打印出每个字符相应的次数即可。

但是,为了实现这一点,我们需要找出每个集合在其列中打印的次数。看一下前面组合示例右侧的第一列。虽然a和b每个集合只打印一次,但它会循环并重复打印7次(总共8次)。如果字符串长度只有3个字符,则循环总共4次。

这是因为我们字符串的长度决定了总共会有多少种组合。计算公式为x ^ y = a,其中x代表字符范围,y代表字符串长度,a代表在这些规格内可能的总组合数。
因此,为了解决这个问题,我们需要找出:
1. 每个集合中有多少个字符放入每一列 2. 每个集合在每一列中重复多少次
我们已经通过之前创建的函数解决了第一个选项。
第二个选项可以通过计算charRange ^ stringLength来找出总共有多少种组合。然后运行一个循环,在每一列中添加多少个字符集,直到达到该列的总可能组合数为止。对于每一列都要运行这个过程,就可以得到结果。
以下是解决此问题的函数:
def Generator(stringLength, charRange):
    workbench = []
    results = []
    charpowers = getCharPower(stringLength, charRange)
    for x in range(0, stringLength):
            while len(workbench) < len(charRange)**stringLength:
                    for char in charRange:
                            for z in range(0, charpowers[x]):
                                    workbench.append(char)
            results.append(workbench)
            workbench = []
    results = ["".join(result) for result in list(zip(*results))]
    return results

该函数将返回您提供的每个字符和字符串长度的所有可能组合。

更简单的方法是仅为您的总长度运行for循环来解决此问题。

因此,要创建长度为2的从a-b的每个可能字符组合

characters = ['a', 'b']
for charone in characters:
    for chartwo in characters:
        print(charone+chartwo)

虽然这个方法更简单,但是它有限制。这段代码只能打印长度为2的所有组合。如果想要创建更多长度的组合,我们需要手动添加另一个for循环。然而,在这段代码之前我提供给你的函数可以打印任何字符串长度的组合,使其100%适应并且是手动解决此问题而不使用任何库的最佳方法。

powers() 可以用 ** 操作符替换掉。 - AKX
你知道的越多,你就会变得更厉害! - cap1hunna

0
import itertools
repeat=int(input("Enter length: ")
def password():
    def foo(l):
        yield from itertools.product(*([l] * repeat)))

    for x in foo('abcdefghijklmnopqrstuvwxyz'): 
        # you could also use string.ascii_lowercase or ["a","b","c"]
        print(''.join(x))

password()

请在您的回答中提供更多细节。目前的写法让人难以理解您的解决方案。 - Community

0
感谢来自@cap1hunna的精彩见解回复,我想出了一个更快的解决方案,以防速度对你很重要。
不必手动构建每个组合,我决定先构建列,然后再将它们组合起来。
from timeit import default_timer


def create_column(
    *,
    char_list: str,
    column_position: int,
    string_total_length: int,
    ) -> str:
    repeated_chars = ''
    for character in char_list:
        repeated_chars += character * (len(char_list)**column_position)
    column_length = len(char_list)**string_total_length
    return repeated_chars * int(column_length/len(repeated_chars))


def combine_columns(
    *,
    columns: list[list[str]],
    ) -> list[str]:
    result = []
    columns.reverse()
    columns_count = len(columns)
    strings_length = len(columns[0])
    for iterator in range(0, strings_length):
        substring = ''
        for subiterator in range(0, columns_count):
            substring += columns[subiterator][iterator]
        result.append(substring)
    return result


def main():
    lowcase_characters = 'abcdefghijklmnopqrstuvwxyz'
    string_total_length = 3
    columns = []
    for column_position in range(0, string_total_length):
        temp_column = create_column(
            char_list=lowcase_characters,
            column_position=column_position,
            string_total_length=string_total_length
            )
        columns.append(temp_column)
    combinations = combine_columns(columns=columns)
    print(combinations)    


if __name__ == '__main__':
    start_time = default_timer()
    main()
    end_time = default_timer()
    print(f'[INFO] Execution lasted: {end_time - start_time} seconds.')

没有打印语句的情况下,接受的答案和其他返回这些小写字符的3种组合,耗时0.005秒。 这段代码在0.004秒内返回相同的结果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接