Нечувствительное к регистру сравнение наборов в Python
У меня есть два набора (хотя я могу делать списки или что-то еще):
a = frozenset(('Today','I','am','fine'))
b = frozenset(('hello','how','are','you','today'))
Я хочу получить:
frozenset(['Today'])
или по крайней мере:
frozenset(['Today'])
Второй вариант выполним, если я опустил все, что я предполагаю, но я ищу более элегантный способ. Можно ли сделать
a.intersection(b)
нечувствительным к регистру образом?
Ярлыки в Django тоже прекрасны, так как я использую эту инфраструктуру.
Пример из метода пересечения, приведенного ниже (я не мог понять, как это отформатировать в комментарии):
print intersection('Today I am fine tomorrow'.split(),
'Hello How a re you TODAY and today and Today and Tomorrow'.split(),
key=str.lower)
[(['tomorrow'], ['Tomorrow']), (['Today'], ['TODAY', 'today', 'Today'])]
Ответы
Ответ 1
Здесь версия, которая работает для любой пары итераций:
def intersection(iterableA, iterableB, key=lambda x: x):
"""Return the intersection of two iterables with respect to `key` function.
"""
def unify(iterable):
d = {}
for item in iterable:
d.setdefault(key(item), []).append(item)
return d
A, B = unify(iterableA), unify(iterableB)
return [(A[k], B[k]) for k in A if k in B]
Пример:
print intersection('Today I am fine'.split(),
'Hello How a re you TODAY'.split(),
key=str.lower)
# -> [(['Today'], ['TODAY'])]
Ответ 2
К сожалению, даже если вы МОЖЕТЕ "изменить" на лету ", специальные методы элементов набора (__lt__
и друзей), связанные с сравнением, на самом деле, только __eq__
нуждались в том, как наборы в настоящее время реализованы, но это детализация), и вы не можете, потому что они принадлежат встроенному типу str
- этого недостаточно, потому что __hash__
является также ключевым и к тому моменту, когда вы хотите сделать свое пересечение, оно уже было применено, поместив элементы наборов в разные хэш-ковши с того места, где им нужно было закончить, чтобы сделать работу пересечения так, как вы хотите (то есть, нет гарантии что" Сегодня "и" сегодня" находятся в одном ведре).
Итак, для ваших целей вам неизбежно нужно создавать новые структуры данных - если вы считаете это "неэлегантным", что нужно сделать это вообще, вам просто не повезло: встроенные наборы просто не делают нести вокруг ОГРОМНОГО багажа и накладных расходов, которые необходимы для того, чтобы люди могли менять функции сравнения и хэширования, которые будут раздувать вещи в 10 раз (или более) для удовлетворения потребности в (возможно) одном случае использования в миллионе.
Если у вас есть частые потребности, связанные с нечувствительным к регистру, вы должны рассмотреть подклассирование или обертывание str
(переопределение сравнения и хеширование) для обеспечения типа "нечувствительность к регистру" cistr
- и, убедитесь, что только ваши экземпляры cistr
(например) добавлены к вашим наборам (& c), представляющим интерес (либо путем подкласса set
& c, либо просто путем оплаты). Чтобы дать упрощенный пример...:
class ci(str):
def __hash__(self):
return hash(self.lower())
def __eq__(self, other):
return self.lower() == other.lower()
class cifrozenset(frozenset):
def __new__(cls, seq=()):
return frozenset((ci(x) for x in seq))
a = cifrozenset(('Today','I','am','fine'))
b = cifrozenset(('hello','how','are','you','today'))
print a.intersection(b)
это испускает frozenset(['Today'])
, согласно вашему выраженному желанию. Конечно, в реальной жизни вы, вероятно, захотите сделать намного больше переопределения (например... так, как у меня есть вещи, любая операция на cifrozenset
возвращает простой frozenset
, теряя ценность независимого случая функция - вы, вероятно, захотите убедиться, что cifrozenset
возвращается каждый раз вместо этого, и, хотя это вполне возможно, это НЕ тривиально).
Ответ 3
Во-первых, вы не имеете в виду a.intersection(b)
? Пересечение (если оно нечувствительно) будет set(['today'])
. Разница была бы set(['i', 'am', 'fine'])
Вот две идеи:
1.) Напишите функцию для преобразования элементов обоих наборов в нижний регистр, а затем выполните пересечение. Здесь один из способов сделать это:
>>> intersect_with_key = lambda s1, s2, key=lambda i: i: set(map(key, s1)).intersection(map(key, s2))
>>> fs1 = frozenset('Today I am fine'.split())
>>> fs2 = frozenset('Hello how are you TODAY'.split())
>>> intersect_with_key(fs1, fs2)
set([])
>>> intersect_with_key(fs1, fs2, key=str.lower)
set(['today'])
>>>
Это не очень эффективно, хотя из-за необходимости создания конверсий и новых наборов для каждого вызова.
2.) Расширьте класс frozenset
, чтобы сохранить регистр, нечувствительный к регистру. Переопределите метод intersection
для использования нечувствительной к регистру копии элементов. Это было бы более эффективно.
Ответ 4
>>> a_, b_ = map(set, [map(str.lower, a), map(str.lower, b)])
>>> a_ & b_
set(['today'])
Или... с меньшим количеством карт,
>>> a_ = set(map(str.lower, a))
>>> b_ = set(map(str.lower, b))
>>> a_ & b_
set(['today'])