Независимое от регистра сравнение множеств в 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'])
Другие вопросы по тегам