【问题标题】:How to pythonically iterate two lists in an arbitrary order如何以任意顺序以python方式迭代两个列表
【发布时间】:2019-11-12 06:07:36
【问题描述】:

我想迭代两个列表,以便我可以从一个列表中获取任意数量的值并保持我在另一个列表中的位置。

我使用索引来存储每个列表中的当前位置,然后使用 while 循环遍历它们,但这绝对不是 Python 的。

def alternate_iterate(a,b,cond=lambda x, y : x > y):
    pos_a = 0
    pos_b = 0
    retval = []

    while(True):

        if(pos_a == len(a) and pos_b == len(b)):
            break

        if(pos_a < len(a) and cond(a[pos_a],b[pos_b])):
            retval += [a[pos_a]]
            pos_a += 1
        elif(pos_b < len(b)):
            retval += [b[pos_b]]
            pos_b += 1

    return retval

#example usage
print(alternate_iterate(['abc','abcd','ab','abc','ab'],
                        ['xy','xyz','x','xyz'],
                        cond=lambda x,y: len(x) > len(y))

这应该打印['abc','abdc','xy','xyz','ab','abc','ab','x','xyz'],您没有完美的 1:1 交替顺序。元素的顺序和元素的类型应该只取决于 cond 的定义。

【问题讨论】:

  • 由于您没有为名称 af 提供值,因此需要花费大量精力才能确定您真正需要的内容。
  • 是的,a 的值是什么,它们可以是像 'a' 这样的字符串,因为您正在比较字符串和整数(在 python 3 中不可能)
  • 我已经重新打开了这个,因为我相信cond 函数可能会改变所需的解决方案(但没有澄清,它可能会再次关闭)
  • 另外,预计b += 1 语句会带来麻烦,因为 b 是一个列表,因此当您尝试增加它时会导致 TypeError。与a += 1 类似。我怀疑这些应该对各自的 pos_* 名称进行操作。

标签: python list iterable


【解决方案1】:

更 Pythonic 的方式通常是根本不使用索引,最好不要使用异常作为控制“预期”程序逻辑的手段。您还应该避免不必要的括号。

以下是使用迭代器的方法:

def merge(a, b, cond=lambda x, y : x < y):
    Done           = []
    iterA, iterB   = iter(a), iter(b)
    valueA, valueB = next(iterA, Done), next(iterB, Done)
    result         = []
    while not(valueB is Done and valueA is Done):
        if valueB is Done or valueA is not Done and cond(valueA, valueB):
            result.append(valueA)
            valueA = next(iterA, Done)
        else:
            result.append(valueB)
            valueB = next(iterB, Done)
    return result

这还有一个额外的好处,就是让函数可以将任何可迭代的数据作为参数有效地工作。

例如:

print(merge(range(5, 10), range(7, 15)))

# [5, 6, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13, 14]

它还可以轻松地为惰性遍历创建函数的迭代器版本:

def iMerge(a, b, cond=lambda x, y : x < y):
    Done           = []
    iterA, iterB   = iter(a), iter(b)
    valueA, valueB = next(iterA, Done), next(iterB, Done)
    while not(valueB is Done and valueA is Done):
        if valueB is Done or valueA is not Done and cond(valueA, valueB):
            yield valueA
            valueA = next(iterA ,Done)
        else:
            yield valueB
            valueB = next(iterB, Done)

EDITNone 更改为 Done 以使函数支持 None 作为输入列表中的合法值。

【讨论】:

  • 优秀的答案。 next(iterA,Done)next() 鲜为人知的第二个参数的一个特别好的用法。在我看来,函数的迭代器版本应该更突出;与返回合并列表的函数相比,迭代器是一种更 Pythonic 的方式来打包此服务。
  • 更惯用的方式,只是使用一个普通对象,所以Done = object(),虽然列表确实工作
  • 只要is 始终用于检查,使用什么哨兵对象都没有关系。使用iter 的第二个参数大大简化了代码。
  • 重要的是要保证哨兵不是可能的值之一。这是通过使用在函数中创建的对象实例(完成)来实现的。我之前使用的是不符合该要求的 None 。
【解决方案2】:

欢迎来到 Stackoverflow。总而言之,您似乎希望根据某个谓词的值从一个列表或另一个列表中获取一个值。您现有的逻辑似乎没有考虑到其中一个列表被用尽的可能性,此时我假设您希望从另一个列表中复制任何剩余的值。

您可以在列表上构建一个迭代器,而不是使用索引值来选择连续的列表元素,并使用next 函数来获取下一个值。

在这种情况下,您的逻辑最终会看起来像这样:

def alternate_iterate(a_lst, b_lst, cond=lambda x, y: x > y):
    a_iter = iter(a_lst)
    b_iter = iter(b_lst)
    a = next(a_iter)
    b = next(b_iter)
    ret = []
    while True:
        if cond(a, b):
            ret.append(a)
            try:
                a = next(a_iter)
            except StopIteration:
                ret.append(b)
                for x in b_iter:
                    ret.append(x)
                return ret
        else:
            ret.append(b)
            try:
                b = next(b_iter)
            except StopIteration:
                ret.append(a)
                for x in a_iter:
                    ret.append(x)
                return ret


print(alternate_iterate(['abc','abcd','ab','abc','ab'],
                        ['xy','xyz','x','xyz'],
                        cond=lambda x,y: len(x) > len(y)))

我得到的结果是

['abc', 'abcd', 'xy', 'xyz', 'ab', 'abc', 'ab', 'x', 'xyz']

这似乎是您所期望的。

与此类示例中的常见情况一样,您编写更多逻辑来处理罕见的极端情况(在这种情况下,一个列表或另一个列表已经用尽),而不是处理事情正常进行的“快乐路径” .

【讨论】:

    【解决方案3】:

    这个版本只使用迭代器来实现懒惰的功能(这是 Pythonic):

    a = ['abc','abcd','ab','abc','ab']
    b = ['xy','xyz','x','xyz']
    
    cond=lambda x,y: len(x) > len(y)
    
    def alternate_iterate(a, b, cond):
        a, b = iter(a), iter(b)
    
        def _return_rest():
            def _f(val, it):
                yield val
                yield from it
            return _f
    
        v1, v2 = next(a, _return_rest), next(b, _return_rest)
    
        while True:
            if v1 is _return_rest:
                yield from v1()(v2, b)
                break
    
            if v2 is _return_rest:
                yield from v2()(v1, a)
                break
    
            if cond(v1, v2):
                yield v1
                v1 =  next(a, _return_rest)
            else:
                yield v2
                v2 = next(b, _return_rest)
    
    print(list(alternate_iterate(a, b, cond)))
    

    打印:

    ['abc', 'abcd', 'xy', 'xyz', 'ab', 'abc', 'ab', 'x', 'xyz']
    

    【讨论】:

      【解决方案4】:

      将您的列表放入生成器中,然后您可以在每个列表上调用next 以获得下一个值。这个答案并不意味着是一个完整的解决方案,只是为了展示生成器如何使用非常简单的 Pythonic 代码以任何顺序生成值:

      agen = iter(a)
      bgen = iter(b)
      print next(agen) # 'a'
      print next(bgen) # 1
      print next(bgen) # 2
      print next(agen) # 'b'
      

      等等。

      【讨论】:

      • 这忽略了条件,离生成输出列表还很远。 zip(agen, bgen) 将是朝这个方向迈出的一步,但本身仍然不够
      • 这并不是一个完整的解决方案,只是演示生成器以正确的顺序生成值,而不需要任何额外的索引变量。
      • 事实并非如此,这就是为什么它应该是真正的评论。
      猜你喜欢
      • 2013-12-25
      • 2012-01-04
      • 1970-01-01
      • 2015-09-18
      • 2021-08-12
      • 2023-04-10
      • 2010-11-18
      • 1970-01-01
      • 2013-07-15
      相关资源
      最近更新 更多