2009-09-25 7 views
4

J'ai deux ensembles (bien que je peux faire des listes, ou autre):comparaison insensible à la casse des jeux en Python

a = frozenset(('Today','I','am','fine')) 
b = frozenset(('hello','how','are','you','today')) 

Je veux:

frozenset(['Today']) 

ou tout au moins:

frozenset(['today']) 

La deuxième option est faisable si je mets tout en minuscule je présume, mais je cherche une manière plus élégante. Est-il possible de faire

a.intersection(b) 

d'une manière insensible à la casse?

Les raccourcis dans Django sont également très bien puisque j'utilise ce framework.

Exemple de méthode d'intersection ci-dessous (je ne pouvais pas comprendre comment obtenir cette forme dans un commentaire):

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'])] 

Répondre

9

la version est ici qui fonctionne pour une paire de iterables:

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] 

Exemple:

print intersection('Today I am fine'.split(), 
        'Hello How a re you TODAY'.split(), 
        key=str.lower) 
# -> [(['Today'], ['TODAY'])] 
+0

Juste pour noter, je reçois cette erreur en faisant cela sur le texte Unicode: *** TypeError: descripteur 'inférieur' nécessite un objet 'str' mais a reçu un 'unicode' Je cherche une solution maintenant. –

+0

@Adam: Utilisez 'unicode.lower' pour les chaînes unicode. – jfs

+0

J'utiliserais 'd = collections.defaultdict (list)' puis 'd [key (item)]. Append (item)' –

3

D'abord, ne pas dire a.intersection(b)? L'intersection (si la casse insensible) serait set(['today']). La différence serait set(['i', 'am', 'fine'])

Voici deux idées:

1.) Ecrivez une fonction pour convertir les éléments des deux ensembles en minuscules puis font l'intersection. Voici une façon que vous pouvez le faire:

>>> 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']) 
>>> 

Ce n'est pas très efficace mais parce que la conversion et de nouveaux jeux devraient être créés sur chaque appel. 2. Étendre la classe frozenset pour conserver une copie insensible à la casse des éléments. Remplacez la méthode intersection pour utiliser la copie insensible à la casse des éléments. Ce serait plus efficace.

+0

Oui, signifiait intersection ... doh. –

6

Malheureusement, même si vous pouviez "changer à la volée" les méthodes spéciales de comparaison des éléments des ensembles (__lt__ et amis - en réalité, seul __eq__ nécessitait la façon dont les ensembles sont actuellement implémentés, mais c'est un détail d'implémentation) - et vous ne pouvez pas, parce qu'ils appartiennent à un type intégré, str - ce ne serait pas suffisant, car __hash__ est aussi cruciale et au moment où vous voulez faire votre intersection, il a déjà été appliqué, en plaçant les éléments des ensembles dans différents compartiments de hachage d'où ils auraient besoin pour aboutir à faire fonctionner l'intersection comme vous le souhaitez (c.-à-d., aucune garantie que 'Aujourd'hui' et 'aujourd'hui' sont dans le même compartiment). Donc, pour vos besoins, vous avez inévitablement besoin de construire de nouvelles structures de données - si vous considérez qu'il est "inélégant" de le faire, vous n'avez vraiment pas de chance: les ensembles intégrés ne font rien t transporter les énormes bagages et les frais généraux qui seraient nécessaires pour permettre aux gens de changer les fonctions de comparaison et de hachage, ce qui ferait gonfler les choses de 10 fois (ou plus) pour un besoin ressenti dans (peut-être) un cas d'utilisation sur un million .

Si vous avez des besoins fréquents liés à la comparaison insensible à la casse, vous devriez envisager subclassing ou emballage str (comparaison exceptionnelle et le hachage) pour fournir un type « insensible à la casse str » cistr - et puis, bien sûr, assurez-vous que seules les occurrences de cistr sont (par exemple) ajoutées à vos ensembles (& c) d'intérêt (soit en sous-classes set & c, ou simplement en faisant attention). Pour donner un exemple ... simplificatrice:

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) 

cela n'émet frozenset(['Today']), selon votre désir exprimé. Bien sûr, dans la vraie vie, vous voudrez probablement faire BEAUCOUP plus de substitution (par exemple ...: la façon dont j'ai des choses ici, toute opération sur un cifrozenset renvoie frozenset, perdant la caractéristique spéciale d'indépendance de cas précieux - vous Je veux probablement m'assurer qu'un cifrozenset est retourné chaque fois à la place, et, bien que tout à fait faisable, ce n'est pas trivial).

+0

Je suppose que la mise en cache self.lower() pourrait être une première optimisation intéressante? – NicDumZ

+1

Pour le problème donné (en enveloppant le bâtiment et l'intersection dans une fonction, en utilisant return au lieu de print) le code que je donne prend 30 usec, avec la mise en cache de self.lower() 44 usec. Caching le hachage aussi, 49 usec. Des chaînes plus longues (3x) semblent se comporter de manière similaire (c'est-à-dire, aucune mise en cache ne semble fonctionner mieux). Accéder aux valeurs mises en cache est un peu plus lent que de les recalculer à la volée. –

1
>>> a_, b_ = map(set, [map(str.lower, a), map(str.lower, b)]) 
>>> a_ & b_ 
set(['today']) 

Ou ... avec moins de cartes,

>>> a_ = set(map(str.lower, a)) 
>>> b_ = set(map(str.lower, b)) 
>>> a_ & b_ 
set(['today']) 
Questions connexes