非嵌套实现多索引无重复全遍历的公式及迭代逻辑排查
问题描述
需求参考如下三层嵌套循环的遍历逻辑:
for i in range(3): for j in range(3): for k in range(3): print("{}{}{}".format(letter, number, symbol))
要求不使用嵌套for循环,仅通过操作i、j、k索引值、以数值计算方式生成完整遍历序列,实现可覆盖所有索引排列组合、适配不同长度索引集合的通用逻辑:需要实现.tick()方法,每次调用返回序列下一个元素,全程无重复输出。
现有实现的问题
目前已尝试基于类进位计数算法编写自定义Tick类,代码如下:
class Tick: def __init__(self, collections, func): self.collections = collections self.indexes = [] self.loops = 0 self.reset = -1 self.func = func self.started = False for collection in collections: self.indexes.append(0) def size(self): total = len(self.collections[0]) for collection in self.collections[1:]: total = total * len(collection) return total def tick(self): if self.started: if self.reset != -1: self.indexes[self.reset + 1] = 0 for index in range(self.reset + 1, len(self.indexes)): self.indexes[index] = 0 self.reset = -1 else: self.loop = len(self.indexes) - 1 while self.loop != -1 and self.indexes[self.loop] == len(self.collections[self.loop]) - 1: self.loop = self.loop - 1 if self.loop == -1: return self.indexes[self.loop] = self.indexes[self.loop] + 1 if self.loop < len(self.indexes) - 1: self.reset = self.loop else: self.started = True items = [] for loop in range(len(self.indexes)): items.append(self.collections[loop][self.indexes[loop]]) return self.func(items) a = ["0", "1", "2"] b = ["0", "1", "2"] c = ["0", "1", "2"] def printer(items): output = "" for item in items: output += item return output ticker = Tick([a, b, c], printer) print(ticker.size()) for index in range(ticker.size()): print(ticker.tick())
运行后出现重复输出,结果如下:
27 000 001 002 012 010 011 012 022 020 021 022 122 100 101 102 112 110 111 112 122 120 121 122 222 200 201 202
经重复行统计,012、022、112、122等值多次出现,27次总输出仅包含22个不同值,需要排查算法逻辑错误,给出正确实现。重复值统计结果:
COUNT | LINE ----------------------------------------------------- 3 | 122 2 | 012 2 | 022 2 | 112 1 | 000 1 | 001 1 | 002 1 | 010 1 | 011 1 | 020 1 | 021 1 | 100 1 | 101 1 | 102 1 | 110 1 | 111 1 | 120 1 | 121 1 | 200 1 | 201 1 | 202 1 | 222 ----------------------------------------------------- 27 | TOTAL LINES 22 | DISTINCT LINES
问题原因
原代码的进位重置逻辑存在时序错误:
- 某一位完成进位后,没有在同一次tick流程中把所有低位索引重置为0,而是把重置逻辑延后到下一次tick执行,导致进位当次的返回结果保留了低位的旧值,出现重复。
- 多余的
started、reset状态变量增加了逻辑分支,很容易触发状态不一致的问题。
正确实现方案
直接用可变进制计数器的标准逻辑即可,不需要额外状态标记:
- 初始化时所有索引位置为0,对应第一个组合
- 每次调用tick时,先返回当前索引对应的组合值,再从最右侧(最低位)开始计算进位,为下一次返回做准备
- 如果当前位加1后超过对应集合的最大索引,就把当前位置0,向左移动一位继续进位
- 如果所有位都遍历完成(移动到索引-1的位置),标记遍历结束,后续调用返回None即可
修正后的代码:
class Tick: def __init__(self, collections, func): self.collections = collections # 存储每一位的当前索引,初始全0对应第一个组合 self.indexes = [0] * len(collections) self.func = func # 标记是否已经遍历完所有组合 self.exhausted = False def size(self): total = 1 for collection in self.collections: total *= len(collection) return total def tick(self): if self.exhausted: return None # 先返回当前索引对应的组合(第一次调用直接返回全0的初始组合) items = [col[idx] for col, idx in zip(self.collections, self.indexes)] res = self.func(items) # 从最低位(最右侧)开始进位计算,为下一次tick做准备 carry_pos = len(self.indexes) - 1 while carry_pos >= 0: self.indexes[carry_pos] += 1 # 当前位没到最大值,进位结束 if self.indexes[carry_pos] < len(self.collections[carry_pos]): break # 当前位到最大值,归零,往左进位 self.indexes[carry_pos] = 0 carry_pos -= 1 # 所有位都进位完了,标记遍历结束 else: self.exhausted = True return res # 测试 a = ["0", "1", "2"] b = ["0", "1", "2"] c = ["0", "1", "2"] def printer(items): return "".join(items) ticker = Tick([a, b, c], printer) print(ticker.size()) outputs = [] while True: val = ticker.tick() if val is None: break outputs.append(val) print(val) # 验证无重复 print(f"总输出数:{len(outputs)}, 去重后数量:{len(set(outputs))}")
运行后会按顺序输出000到222的全部27种组合,无重复、无遗漏,同时支持不同长度的输入集合,比如传入长度分别为2、3、4的三个集合,也能正确遍历全部234=24种组合。
内容的提问来源于stack exchange,提问作者Samuel Squire
相关产品推荐
相关产品推荐

