Нечувствительное к регистру сравнение наборов в 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'])