删除列表项的pythonic方式是另一个的``子列表''

时间:2018-11-20 16:37:14

标签: python-3.x list

我有这种方法可以从棋盘上获取所有有效的举动。

def get_all_moves(self) -> list:
    moves = []
    pieces = self.__get_all_turn_pieces()

    for p in pieces:
        self.__jump_moves(p.coord, moves)
        self.__b.wipe()
        self.__simple_moves(p, moves)

    moves.sort(key=len, reverse=True)
    for i in range(len(moves)):
        for j in range(len(moves)):
            if moves[i][:len(moves[j])] == moves[j] and len(moves[i]) > len(moves[j]):
                moves.remove(moves[j])
    return moves

问题是:根据跳棋规则,玩家必须在移动中执行最多次数的捕获。因此,我需要删除无效的举动。

让我们分析此输出:

[[3, 0, 5, 2, 3, 4], [5, 0, 3, 2, 5, 4], [1, 0, 2, 1], [1, 2, 2, 3], [1, 2, 2, 1], [1, 4, 2, 3], [2, 5, 3, 6], [2, 5, 3, 4], [2, 7, 3, 6], [3, 0, 5, 2], [5, 0, 3, 2]]

输出列表的第一项和第二项分别包含最后两项。所以我需要删除它们,因为它们是第一个的“子列表”。

问题是我的嵌套循环无法解决此问题。程序溢出此错误:

Traceback (most recent call last):
  File "damas.py", line 435, in <module>
    print(m.get_all_moves())
  File "damas.py", line 418, in get_all_moves
    if moves[i][:len(moves[j])] == moves[j] and len(moves[i]) > len(moves[j]):
IndexError: list index out of range

过了一会儿,我想知道解决这个问题的最 pythonic 方法是什么。

3 个答案:

答案 0 :(得分:0)

您可能已经解决过,问题是您只在开始时检查了moves的{​​{1}}的长度。从其中删除项目时,将其缩短,这将导致循环变量i最终超出列表的大小。

有多种方法可以解决此问题。对于这种情况,我通常的处理方法是必须从列表的 end 开始,然后再重新开始。

i

请注意,我正在评估for i in reversed(range(len(moves))): for j in range(len(moves)): if i != j and moves[i] == moves[j][:len(moves[i])]: del moves[i] break 而不是moves[i]。如果发现要删除它,请这样做,然后立即退出内部moves[j]循环。这只会影响列表{em> 位置for之后的列表的结构(我们已经考虑并决定保留的项目),而不是之前的列表(我们尚未考虑的项目) )。因此,我们不会遇到任何讨厌的IndexErrors。

答案 1 :(得分:0)

我不知道这是否是最Python化的处理方式,但这是我用于检查子字符串的一种变体,非常简洁:

def check_for_sublist(sub_list,longer_list): 
    return any(sub_list == longer_list[offset:offset+len(sub_list)] for offset in range(len(longer_list)))

或者是lambda版本,建议不要在注释中使用。我从评论中学到了一些东西,就把它留在这里作为学习工具!

x_sublist_y = lambda x, y: any(x == y[offset:offset+len(x)] for offset in range(len(y)))

然后我认为这可以解决问题。它会创建条目列表,其中arraycheck的返回值不为True(请确保排除对自己的条件进行检查)。

moves = [[3, 0, 5, 2, 3, 4], [5, 0, 3, 2, 5, 4], [1, 0, 2, 1], [1, 2, 2, 3], [1, 2, 2, 1], [1, 4, 2, 3], [2, 5, 3, 6], [2, 5, 3, 4], [2, 7, 3, 6], [3, 0, 5, 2], [5, 0, 3, 2]]

filtered_moves = [move1 for move1 in moves if all([not check_for_sublist(move1, move2) for move2 in moves if move1 != move2])]

答案 2 :(得分:0)

这是一个使用列表理解的解决方案,当您想使事情变得尽可能Pythonic时,这通常是一种有用的方法

def is_sublist( seq, lists ):
    for candidate in lists:
        if len( candidate ) > len( seq ) and candidate[ :len( seq ) ] == seq:
            return True
    return False

moves = [[3, 0, 5, 2, 3, 4], [5, 0, 3, 2, 5, 4], [1, 0, 2, 1], [1, 2, 2, 3], [1, 2, 2, 1], [1, 4, 2, 3], [2, 5, 3, 6], [2, 5, 3, 4], [2, 7, 3, 6], [3, 0, 5, 2], [5, 0, 3, 2]]

# here comes the list comprehension:
pruned = [ eachMove for eachMove in moves if not is_sublist( eachMove, moves ) ]

要就地修改序列moves,您应该分配给moves[:]而不是新变量pruned

但是,上面的解决方案并不是最有效的,如果潜在动作的数量很大,则可能会令人担忧。建议采用如下所示的不太优雅的方法,因为随着候选举动被拒绝,我们减少了必须检查每个潜在潜在子列表的潜在超级列表的数量:

accepted = []
while moves:
    eachMove = moves.pop( 0 )
    if not is_sublist( eachMove, moves ) and not is_sublist( eachMove, accepted ):
        accepted.append( eachMove )

moves[:] = accepted

如果移动列表的顺序并不重要,则在此例程的顶部执行moves.sort()可以使其效率更高(实际上,我们可以进一步优化代码,因为每个移动只有一次必须与列表中的下一个移动进行比较。