Python: набор фильтров/список кортежей на основе свойства мощности

Я ищу способ фильтровать набор (или список) кортежей по количеству раз. элемент появляется в одной из других позиций кортежа.

Моя текущая цель немного сложна, поэтому я разделил проблему на три небольших шага.

1. Начнем с самого простого случая: только одно значение, которое применяется только к первому элементу кортежа.

Например:

my_filter([(1,2),(1,3),(2,4),(3,1),(3,4),(3,5),(5,2),(5,4)], 2)

Должно вернуться:

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

Потому что это единственные кортежи, для которых первый элемент кортежа появляется во всем списке только дважды.

Наивный способ сделать это: для каждого первого элемента кортежа в списке подсчитать, сколько раз этот элемент появляется в качестве первого элемента во всех кортежах, и, если счетчик соответствует выбранному числу, добавить все кортежи, имеющие этот элемент в первой позиции. .

Но мне кажется, что это настолько неоптимально, и мне приходится перебирать список для каждого возможного значения, что я наверняка упускаю лучший способ сделать это.

2. Сделайте это взаимным

В идеале хотелось бы иметь возможность применить ту же обработку на основе второго элемента кортежа, но с другим параметром мощности.

Например:

my_filter([(1,2),(1,3),(2,4),(3,1),(3,4),(3,5),(5,2),(5,4)], 2, 1)

Здесь мы хотим сохранить только кортежи, в которых первый элемент появляется ровно два раза, а второй элемент появляется только один раз (пересечение двух условий). Это должно вернуться:

[(1,3)]

3. Обобщение на несколько значений

my_filter([(1,2),(1,3),(2,4),(3,1),(3,4),(3,5),(5,2),(5,4)], 2, [1,3])

В этом случае мы разрешаем фильтру мощности принимать несколько возможных значений. В этом примере мы хотим сохранить кортежи, для которых первый элемент появляется ровно два раза (в первой позиции), а второй элемент появляется один или три раза (во второй позиции). Это должно вернуться:

[(1,3),(5,4)]

Еще раз: у меня нет проблем с написанием простого решения, которое просто перебирало бы все разрешенные значения и объединяло наборы результатов, но я ищу что-то поумнее.

Я чувствую, что в библиотеке itertools могут быть некоторые полезные функции, но мне она не очень нравится. Любой совет ? Спасибо.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
0
0
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вот решение с линейным временем (O(n)) для частей 2 и 3 (1 можно реализовать с некоторыми изменениями):

Сначала мы преобразуем второй и третий аргументы в набор, равный O(n).

Затем мы вычисляем частоту каждого элемента в позициях 0 и 1, снова O(n).

Затем мы перебираем список и проверяем, соответствует ли он нашим критериям. Поиск в Counter и наборах - это O(1), так что эта вещь снова эффективна, в целом O(n).

from collections import Counter


def my_filter(list: list[tuple[int, int]], first: list[int], second: list[int]):
    first_set = set(first)
    second_set = set(second)
    first_counter = Counter(a for (a, _) in list)
    second_counter = Counter(b for (_, b) in list)
    return [
        (a, b)
        for (a, b) in list
        if first_counter[a] in first_set and second_counter[b] in second_set
    ]


print(
    my_filter(
        [(1, 2), (1, 3), (2, 4), (3, 1), (3, 4), (3, 5), (5, 2), (5, 4)], [2], [1]
    )
)

print(
    my_filter(
        [(1, 2), (1, 3), (2, 4), (3, 1), (3, 4), (3, 5), (5, 2), (5, 4)], [2], [1, 3]
    )
)

Выход:

[(1, 3)]
[(1, 3), (5, 4)]

Отлично спасибо ! Он работает нормально. Counter это то, чего мне не хватало. Очевидно, что первый случай легко разрешим, поскольку это частный случай.

ibi0tux 02.08.2024 14:38

Это можно сделать значительно быстрее, если не использовать Counter и выполнить итерацию по списку только один раз для создания first_counter и Second_counter.

SIGHUP 02.08.2024 16:03

Поскольку Counter itertools написан на C и высоко оптимизирован, я сомневаюсь, что ванильный Python сможет превзойти его использование, хотя нам нужно дважды перебрать список с помощью Counter

ibi0tux 02.08.2024 16:18

@ibi0tuxЯ добавил еще один ответ в качестве доказательства своего утверждения.

SIGHUP 02.08.2024 16:48

Улучшение производительности по сравнению с отличным предложением @Dogbert.

from collections import Counter
from timeit import timeit

# code from the accepted answer
def my_filter1(list: list[tuple[int, int]], first: list[int], second: list[int]):
    first_set = set(first)
    second_set = set(second)
    first_counter = Counter(a for (a, _) in list)
    second_counter = Counter(b for (_, b) in list)
    return [
        (a, b)
        for (a, b) in list
        if first_counter[a] in first_set and second_counter[b] in second_set
    ]

# code that avoids use of Counter
def my_filter2(list: list[tuple[int, int]], first: list[int], second: list[int]):
    first_set = set(first)
    second_set = set(second)
    first_counter = {}
    second_counter = {}
    for a, b in list:
        first_counter[a] = first_counter.get(a, 0) + 1
        second_counter[b] = second_counter.get(b, 0) + 1
    return [
        (a, b)
        for (a, b) in list
        if first_counter[a] in first_set and second_counter[b] in second_set
    ]


X = [(1, 3), (2, 4), (3, 1), (3, 4), (3, 5), (5, 2), (5, 4),(1,2)]
Y = [2]
Z = [1]

assert my_filter1(X, Y, Z) == my_filter2(X, Y, Z)

for func in my_filter1, my_filter2:
    print(func.__name__, timeit(lambda: func(X,Y,Z)))

Выход:

my_filter1 2.4996074590017088
my_filter2 1.1667742919817101

Платформа:

Apple M2
macOS Sonoma 14.6
Python 3.12.4

Отличное улучшение производительности, спасибо!

ibi0tux 05.08.2024 07:52

Другие вопросы по теме