为什么我像这样使用 for 循环但仍然得到 IndexError?

Why I use for loop like this but still get an IndexError?

我一直认为如果我这样使用for循环:

for line in lines:
    dosomething()

我永远不会让我的列表索引超出范围。然而,我错了..

我的代码的目的是输出输入集的所有子集。

示例:

输入:

[2,1,3]

输出:

[[],[3],[2],[2,3],[1],[1,3],[1,2],[1,2,3]]

代码如下:

def bin_list(bit_len, list_len):
# To get a binary numbers list like this: 
# ['000', '001', '010', '011', '100', '101', '110', '111']
    list_Bin = [None] * list_len

    for i in range(list_len):
        extra_d = bit_len-len(bin(i)[2:])
        list_Bin[i] = '0'*extra_d + bin(i)[2:]

    return list_Bin

def subsets(nums):
    nums.sort()
    max_element = len(nums)
    size_subset = 2 ** max_element

    list_Bin = bin_list(max_element, size_subset)

    list_Result = [[None]*max_element for i in range(size_subset)]

    for i, Bin in enumerate(list_Bin):
        for j in range(max_element):
            if Bin[j] == '1':
                #1 list_Result[i][j] = nums[i]
                #2 list_Result[i].append(nums[i])

    print(list_Result)


nums = [1,2,3]
subsets(nums)

所以我的核心概念是使用二进制列表来创建子集列表。

例如,当我在二进制列表中读取 001 时。我会

保留3,因为第三位是1

由于第一位和第二位均为0,因此删除1、2。

因此,如果我使用 0-7(二进制 == 000-111),我可以获得 [1,2,3] 的所有子集。

但我总是得到 IndexError: list index out of range。不管我用

#1 list_Result[i][j] = nums[i]

#2 list_Result[i].append(nums[i])

由于以下原因您收到错误消息:

  1. len(nums) == 3
  2. size_subset == 2**3 == 8
  3. nums[3]i0len[list_Bin] 的循环中被访问。

解决方案可能是访问 nums[j] 而不是 nums[i],因为 j0 变为 len(nums)

IndexError 的原因是您混淆了索引。在 subsets 函数中的嵌套 for 循环中,i 范围超过子集的数量,j 范围超过 nums 集中的元素数量。但是当你 应该 nums[j] 时你尝试访问 nums[i],所以当 i 大于或等于 len(nums) 你将尝试访问超出 nums 列表末尾的内容,因此 IndexError


您的代码可以稍微简化。无需使用 bin 函数:您可以告诉 format 以所需长度格式化二进制整数。

def bin_list(bit_len):
    list_len = 2 ** bit_len
    return ['{0:0{1}b}'.format(i, bit_len) for i in range(list_len)]

def subsets(seq):
    all_subsets = []
    for bits in bin_list(len(seq)):
        s = [seq[i] for i, b in enumerate(bits) if b == '1']
        all_subsets.append(s)
    return all_subsets

print(subsets([1,2,3]))
print(subsets('ABCD'))

输出

[[], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]]
[[], ['D'], ['C'], ['C', 'D'], ['B'], ['B', 'D'], ['B', 'C'], ['B', 'C', 'D'], ['A'], ['A', 'D'], ['A', 'C'], ['A', 'C', 'D'], ['A', 'B'], ['A', 'B', 'D'], ['A', 'B', 'C'], ['A', 'B', 'C', 'D']]

我承认 '{0:0{1}b}'.format(i, bit_len) 有点晦涩难懂,所以我会尽可能简单地解释它,自由引用 Python 3 Format String Syntax 的官方文档].

表达式 '{0:0{1}b}'.format(i, bit_len) 创建一个字符串,其中包含 i 的二进制表示形式。创建的字符串为 bit_len 个字符宽,如有必要,在左侧用零填充。

'{0:0{1}b}' 部分称为格式字符串。格式字符串包含由花括号 {} 包围的“替换字段”。这是一个复杂的示例,因为它有一个替换字段嵌套在另一个替换字段中。

在替换字段中,紧跟在 { 之后的项目称为 field_name。它用于指定 .format 的哪个参数与此替换字段相关联。 field_name 可以是参数的名称、位置,也可以省略(在 Python 2.7 及更高版本中)并且 Python 将仅按数字顺序使用 .format 参数当它与替换字段匹配时。

field_name 之后可选地跟一个转换字段,前面有一个感叹号 !,和一个 format_spec (格式规范),前面有一个冒号:。这些为替换值指定了非默认格式。此特定格式字符串没有转换字段,因此使用默认转换。

A format_spec 以一个字母结尾,它指定了我们想要的格式类型。我们的 format_spec 以 b 结尾,表示我们要将整数格式化为二进制。 b 之前的数字指定了结果位串的宽度;通过在其前面放置一个零,我们表示我们希望用零而不是空格填充字符串。因此 08b 的 format_spec 会为我们提供 8 个字符宽的位串(或者如果需要更宽以正确表示参数),短位串用零填充。

但我们需要比这更奇特的东西,因为我们没有固定的位串宽度 - 我们希望我们的位串为 bit_len 个字符宽。但这没关系,因为格式语法允许我们将一个替换字段放在另一个替换字段中!因此,我们有 0{1}b 而不是 08b,现在位置 1 中的参数值用作位串宽度。

前面提到field_name可以是参数名,所以我们原来的格式表达式也可以写成:

'{val:0{width}b}'.format(val=i, width=bit_len)

我想这个表格比原来的更易读。 :)

我还提到可以省略 field_name(在 Python 2.7 及更高版本中),它看起来像这样:

'{:0{}b}'.format(i, bit_len)

但我不建议这样做,因为虽然它更短,但比原来的更神秘,恕我直言。


然而,我们并不真正需要 bin_list 中的位串:我们可以只在子集的索引上使用按位运算符。

def subsets(seq):
    bit_len = len(seq)
    bitrange = range(bit_len)
    all_subsets = []
    for j in range(1 << bit_len):
        s = [seq[i] for i in bitrange if j & (1<<i)]
        all_subsets.append(s)
    return all_subsets

print(subsets([1,2,3]))
print(subsets('ABCD'))  

输出

[[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
[[], ['A'], ['B'], ['A', 'B'], ['C'], ['A', 'C'], ['B', 'C'], ['A', 'B', 'C'], ['D'], ['A', 'D'], ['B', 'D'], ['A', 'B', 'D'], ['C', 'D'], ['A', 'C', 'D'], ['B', 'C', 'D'], ['A', 'B', 'C', 'D']]

这确实会以不同的顺序生成子集,但更改它并不难,而且我认为这种顺序更自然。

我们可以通过使用嵌套列表理解使上一个版本更加紧凑​​:

def subsets(seq):
    bit_len = len(seq)
    bitrange = range(bit_len)
    return [[seq[i] for i in bitrange if j & (1<<i)]
        for j in range(1 << bit_len)]

FWIW,这是制作子集的另一种方法。效率有点低,但是对于小的输入序列还是可以的。它按照与以前版本相同的顺序生成子集。

def subsets(seq):
    z = [[]]
    for x in seq:
        z += [y + [x] for y in z]
    return z