【问题标题】:The pythonic way to remove a list item thats is 'sub-list' of another删除列表项的pythonic方法是另一个的“子列表”
【发布时间】:2018-11-21 02:17:34
【问题描述】:

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

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的方法是什么。

【问题讨论】:

    标签: python-3.x list


    【解决方案1】:

    正如您可能已经解决的那样,问题是您在开始时只检查moves 的长度是否为i。当您从中删除项目时,您会缩短它,这会导致循环变量 i 最终超出列表的大小。

    有多种方法可以解决此问题。对于这种情况(您必须评估列表中的每个元素以进行删除),我通常的方法是从列表的 end 开始并返回到开始处。

    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]。如果我发现我想删除它,我会这样做,然后立即跳出内部for 循环。这只会影响列表在和之后位置i(我们已经考虑并决定保留的项目)的结构,而不影响之前(我们尚未考虑的项目)。因此,我们不会遇到任何讨厌的 IndexErrors。

    【讨论】:

    • 除了解决问题。更正我的代码的错误。非常感谢。
    • @Dagdeloo 我刚刚做了一个小改动。我没有使用list.remove() 来删除项目,而是将其更改为使用del。如果列表中有重复项,这将避免潜在的问题。
    【解决方案2】:

    我不知道这是否是最pythonic的做事方式,但这是我用来检查子字符串的一种变体,非常简洁:

    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 版本,我被建议不要在 cmets 中使用它。把它留在这里作为学习工具,因为我从评论中学到了一些东西!

    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])]
    

    【讨论】:

    • 不要将 lambda 的结果分配给名称,这消除了唯一的优势,即它们是 匿名的。否则,只需使用正常的函数定义。这也是 PEP8 指南...
    • 很公平。我只是在这里更改了它以使发生的事情更清楚,但已将其重新编辑。
    • @juanpa.arrivillaga 我意识到我误解了你的评论。又做了一个改动。感谢您指出,这不是我之前真正考虑过的事情。
    • 非常有趣。完美运行,我当然会保留这个功能。
    【解决方案3】:

    这是一个使用列表推导式的解决方案,当您想让事情尽可能 Python 化时,这通常是一种有用的方法

    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() 会提高效率(事实上,我们可以进一步优化代码,因为每个移动只需要与列表中的下一个移动进行比较)。

    【讨论】:

      猜你喜欢
      • 2017-05-01
      • 1970-01-01
      • 2017-05-09
      • 2021-09-12
      • 1970-01-01
      • 1970-01-01
      • 2019-08-02
      • 1970-01-01
      相关资源
      最近更新 更多