filter n'accepte qu'un seul itérable, alors que map accepte un nombre variadique d'itérables. Par exemple, je peux épuiser map(operator.add, [1, 2, 3, 4], [1, 2, 2, 4]) pour obtenir [2, 4, 5, 8].

Je recherche un mécanisme similaire pour filter, acceptant n'importe quel prédicat et un nombre variable d'itérables. L'épuisement filter(operator.eq, [1, 2, 3, 4], [1, 2, 2, 4]) provoque un TypeError sur la façon dont filter n'accepte que 1 itérable, pas 2.

Ma sortie attendue pour ce cas particulier est ([1, 2, 4], [1, 2, 4]), c'est-à-dire que les éléments par paires qui ne satisfont pas operator.eq sont supprimés.

Voici ce que j'ai jusqu'à présent (version impatiente ne prenant en charge que 2 itérables au lieu de 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

Cependant mon objectif est de 1) être capable de supporter N itérables et 2) d'avoir filter_ être paresseux au lieu d'être impatient, comme c'est le cas avec filter.

1
Mario Ishac 4 juin 2020 à 11:34

3 réponses

Meilleure réponse

Que diriez-vous:

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

Il est paresseux, il affiche [(1, 1), (2, 2), (4, 4)] pour votre cas de test, et non, vous ne pouvez pas avoir ([1, 2, 4], [1, 2, 4]) comme résultat de manière paresseuse. Pour convertir de [(1, 1), (2, 2), (4, 4)] en ([1, 2, 4], [1, 2, 4]), vous pouvez utiliser: zip(*filter_(operator.eq, [1, 2, 3, 4], [1, 2, 2, 4])) mais bien sûr, vous perdez la paresse.

1
Błotosmętek 4 juin 2020 à 08:45

Votre réponse est dans votre mise en œuvre. Map accepte une fonction prenant plusieurs listes qui doivent correspondre au nombre d'arguments. Le filtre prend une seule liste à filtrer, donc la différence n'est pas seulement sémantique - il est logique que le filtre ne prenne qu'une seule liste. Dans votre cas, la liste est bien le zip, et c'est ce que vous implémentez. Ce qui vous manque, c'est un moyen astucieux de dissocier les résultats appariés:

>>> 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)
1
kabanus 4 juin 2020 à 09:46

Malheureusement, il n'y a pas d'équivalent à starmap comme starfilter, donc l'équivalent auquel je peux penser est:

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

lists étant ici quelque chose comme ([..], [..]). Cela se traduit par:

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

Pour transformer cela en listes séparées, utilisez tuple(map(list, zip(*result))):

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

Donc, en le rassemblant:

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)))))
2
deceze 4 juin 2020 à 08:44