Независимое от регистра сравнение множеств в 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'])]
4 ответа
Вот версия, которая работает для любой пары итераций:
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'])]
К сожалению, даже если вы МОЖЕТЕ "изменить на лету" специальные методы, относящиеся к сравнению элементов наборов (__lt__
а друзья - собственно, только __eq__
нужен способ, которым наборы в настоящее время реализованы, но это деталь реализации) - и вы не можете, потому что они принадлежат встроенному типу, str
- этого было бы недостаточно, потому что __hash__
также имеет решающее значение, и к тому времени, когда вы захотите сделать пересечение, оно уже было применено, помещая элементы наборов в разные хэш-контейнеры, из которых они должны были бы оказаться, чтобы пересечение работало так, как вы хотите (т. е. нет никакой гарантии, что "Сегодня" и "сегодня" находятся в одном ведре).
Таким образом, для ваших целей вам неизбежно нужно создавать новые структуры данных - если вы считаете, что "делать это совсем не элегантно", вам просто не повезло: встроенные наборы просто не носят с собой ОГРОМНЫЙ багаж и накладные расходы, которые понадобятся, чтобы позволить людям изменить функции сравнения и хеширования, что увеличит количество вещей в 10 раз (или более), чтобы удовлетворить потребность в (возможно) одном случае использования на миллион.
Если у вас есть частые потребности, связанные с регистронезависимым сравнением, вам следует рассмотреть возможность создания подклассов или переносов str
(переопределение сравнения и хеширования) для предоставления типа "без учета регистра" str cistr
- а потом, конечно, убедитесь, что только в случаях cistr
(например) добавлены к вашим наборам (&c), представляющим интерес (либо путем подкласса set
и с, или просто оплачивая уход). Чтобы привести упрощенный пример...:
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
вместо этого возвращается каждый раз, и, хотя это вполне возможно, это НЕ тривиально).
Во-первых, ты не имеешь в виду 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
метод использовать регистронезависимую копию элементов. Это было бы более эффективным.
>>> 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'])