如何将 `filter` 与多个可迭代对象一起使用,如 `map` 所支持的那样?

How to use `filter` with multiple iterables, as is supported by `map`?

filter 只接受一个可迭代对象,而 map 接受可变数量的可迭代对象。比如我可以穷举map(operator.add, [1, 2, 3, 4], [1, 2, 2, 4])得到[2, 4, 5, 8].

我正在为 filter 寻找类似的机制,接受任何谓词和可变数量的可迭代对象。耗尽 filter(operator.eq, [1, 2, 3, 4], [1, 2, 2, 4]) 导致 TypeError 关于 filter 如何只接受 1 个可迭代对象,而不是 2 个。

我对该特定情况的预期输出是 ([1, 2, 4], [1, 2, 4]),即不满足 operator.eq 的成对元素将被删除。

这是我目前所拥有的(eager 版本只支持 2 个迭代而不是 N):

from typing import TypeVar, Callable, Iterable

A = TypeVar("A")
B = TypeVar("B")

def filter_(predicate: Callable[[A, B], bool], iterable1: Iterable[A], iterable2: Iterable[B]) -> (Iterable[A], Iterable[B]):
    filtered_iterable1 = []
    filtered_iterable2 = []

    for value1, value2 in zip(iterable1, iterable2):
        if predicate(value1, value2):
            filtered_iterable1.append(value1)
            filtered_iterable2.append(value2)

    return filtered_iterable1, filtered_iterable2

但是我的目标是 1) 能够支持 N​​ 个迭代器和 2) filter_ 变得懒惰而不是急切,就像 filter 一样。

不幸的是,没有像 starfilter 这样的 starmap 等价物,所以我能想到的等价物是:

[i for i in zip(*lists) if predicate(*i)]

lists 类似于 ([..], [..])。这导致:

[(1, 1), (2, 2), (4, 4)]

要将其变回单独的列表,请使用 tuple(map(list, zip(*result))):

([1, 2, 4], [1, 2, 4])

所以,把它放在一起:

predicate = operator.eq
lists = [1, 2, 3, 4], [1, 2, 2, 4]

result = tuple(map(list, zip(*(i for i in zip(*lists) if predicate(*i)))))

怎么样:

def filter_(predicate, *iterables):
    for t in zip(*iterables):
        if predicate(*t):
            yield t

print(list(filter_(operator.eq, [1, 2, 3, 4], [1, 2, 2, 4])))

它是懒惰的,它为你的测试用例输出 [(1, 1), (2, 2), (4, 4)],不,你不能以懒惰的方式得到 ([1, 2, 4], [1, 2, 4]) 作为结果。要从 [(1, 1), (2, 2), (4, 4)] 转换为 ([1, 2, 4], [1, 2, 4]),您可以使用:zip(*filter_(operator.eq, [1, 2, 3, 4], [1, 2, 2, 4])) 但是当然您会失去惰性。

您的答案就在您的实施中。 Map 接受一个带有多个列表的函数,这些列表必须与参数的数量相匹配。 Filter 采用单个列表进行过滤,因此差异不仅在于语义 - 过滤器仅采用单个列表是有意义的。在你的例子中,列表确实是 zip,这就是你实现的。您缺少的是取消配对结果配对的好方法:

>>> r1, r2 = zip(*filter(lambda x: predicate(*x), zip([1, 2, 3, 4, 5], [1, 1, 3, 3, 5)))
>>> r1
(1, 3, 5)
>>> r2
(1, 3, 5)