Страницы

Поиск по вопросам

среда, 3 октября 2018 г.

Сравнение двух списков на нахождение элементов которые соответствуют правилам

Вопрос заключается в том, что например у меня есть два списка:
lst1 = ['1', '2' , '3' , '4'] lst2 = ['123', '234' , '345' , '334']
Как мне найти такие элементы во втором списке, которые включают в себя только те элементы, которые есть в первом, но, если в первом списке есть одна единица, то например элемент "112" с второго списка не подходит.
Tо есть результатом программы должен быть
ls3 = ['123', '234']
'345' - не подошло потому что там есть элемент "5" которого нет в первом списке
'334' - не подошло потому что там есть два элемента "3", а в первом списке элемент "3" есть только один


Ответ

Если известно как определить, можно ли составить строку word из заданных символов chars, используя каждый символ в chars не более его числа повторений (известен can_build(word, chars) предикат), то задача сводится к:
result = list(filter(can_build, lst2))
или более читаемо:
result = [word for word in lst2 if can_build(word)]
где can_build использует chars = lst1 внутри.
Если бы требовалось использовать все символы из chars, тогда это была бы проверка является ли word анаграммой chars, к примеру: "просветитель" можно получить перестановкой букв "терпеливость". Можно использовать похожие решения, когда точное равенство заменено на "не более".
can_build() можно реализовать, найдя является ли word мультимножество подмножеством chars мультимножества. Если бы все символы в chars и word были уникальны, то
can_build = set("1234").issuperset
collections.Counter реализует идею множества, в котором элементы могут повторяться, то есть мультимножества. Как показано в элегантном решении в ответе @Timofey Bondarev, эту коллекцию можно использовать чтобы реализовать can_build
can_build = lambda word, chars=Counter(lst1): not (Counter(word) - chars)
Можно реализовать тот же алгоритм вручную, не используя collections.Counter.
from collections import defaultdict
def Counter(letters): counts = defaultdict(int) for letter in letters: counts[letter] += 1 return counts
chars_count = Counter(chars)
def can_build(word): return all(chars_count[char] >= count for char, count in Counter(word).items())
Можно использовать простой список, если все символы принадлежат какому-либо алфавиту, тогда так как chars всё время один и тот же, то можно закэшировать chars.count значения. Например, если chars может содержать только цифры 0-9
from string import digits
chars_count = [(digit, chars.count(digit)) for digit in digits]
def can_build(word): return all(word.count(digit) <= count for digit, count in chars_count)
это O(N * M) решение (M=len(digits)—размер алфавита), в отличии от O(N) решения, использующего Counter(). Если алфавит нефиксированный: alphabet = set(word), тогда это O(N**2) (квадратичный) алгоритм. Если alphabet фиксированный как в примере, то это O(N) (линейное) решение. Для небольшого алфавита, например, для boolean цифр (alphabet=(0,1)) или ДНК-строк (alphabet="GTAC"), это решение могло быть даже быстрее решения с Counter()
Ещё пример применения: если word, chars это числа, представленные их простыми множителями (например: (2,2,3) представляет 12, (5,7) представляет 35), тогда can_build() отвечает на вопрос является ли word делителем chars, то есть верно ли что: chars % word == 0
Q: как реализовать код, если избавиться от условия о том, что элемент должен повторяться столько раз сколько его есть в первом списке???
Уже ответ в этом предположении работает. Иначе это был бы случай с анаграммами: количество повторений совпадает. Решения с Counter() и с <=, >= НЕ требуют, чтобы элемент повторялся "столько раз сколько его есть в первом списке" (можно меньше).
Если вы имеете ввиду, что количество повторений вообще не важно, а интересует только есть элемент или нет, то ситуация аналогична случаю когда все элементы уникальны, то есть:
can_build = set(lst1).issuperset
как уже упомянуто выше.

Комментариев нет:

Отправить комментарий