it-swarm-eu.dev

Groß- und Kleinschreibung in Python-Zeichenfolgen ignorieren

Was ist der einfachste Weg, Zeichenfolgen in Python zu vergleichen, ohne dabei die Groß- und Kleinschreibung zu berücksichtigen?

Natürlich kann man (str1.lower () <= str2.lower ()) usw. machen, aber dies erzeugte zwei zusätzliche temporäre Zeichenfolgen (mit dem offensichtlichen Allocation/g-c-Overhead).

Ich denke, ich suche nach einem Äquivalent zu C's stricmp ().

[Etwas mehr Kontext gefragt, also werde ich mit einem trivialen Beispiel zeigen:]

Angenommen, Sie möchten eine lange Liste von Zeichenfolgen sortieren. Sie machen einfach theList.sort () . Dies sind O (n * log (n)) - Zeichenfolgenvergleiche und keine Speicherverwaltung (da alle Zeichenfolgen- und Listenelemente eine Art intelligente Zeiger sind). Du bist glücklich.

Jetzt möchten Sie dasselbe tun, aber ignorieren Sie den Fall (vereinfachen Sie uns und sagen Sie........................... Sie können theList.sort (key = lambda s: s.) .lower ()), aber dann verursachen Sie zwei neue Zuordnungen pro Vergleich und belasten den Garbage Collector mit den duplizierten (herabgesetzten) Zeichenfolgen. Jedes dieser Speicherverwaltungsgeräusche ist um Größenordnungen langsamer als der einfache Zeichenfolgenvergleich.

Mit einer In-Place-Funktion von stricmp () tun Sie Folgendes: theList.sort (cmp = stricmp) Und es ist so schnell und so speicherfreundlich wie theList.sort (). Du bist wieder glücklich.

Das Problem ist, dass bei jedem Python-basierten Vergleich die Groß- und Kleinschreibung nicht beachtet wird und implizite String-Duplikationen enthalten. Daher habe ich erwartet, einen C-basierten Vergleich zu finden (möglicherweise in Modul-String).

Konnte so etwas nicht finden, daher hier die Frage ... (hoffentlich klärt die Frage).

51
Paul Oyster

Hier ist ein Benchmark, der zeigt, dass die Verwendung von str.lower schneller ist als die von der akzeptierten Antwort vorgeschlagene Methode (libc.strcasecmp):

#!/usr/bin/env python2.7
import random
import timeit

from ctypes import *
libc = CDLL('libc.dylib') # change to 'libc.so.6' on linux

with open('/usr/share/dict/words', 'r') as wordlist:
    words = wordlist.read().splitlines()
random.shuffle(words)
print '%i words in list' % len(words)

setup = 'from __main__ import words, libc; gc.enable()'
stmts = [
    ('simple sort', 'sorted(words)'),
    ('sort with key=str.lower', 'sorted(words, key=str.lower)'),
    ('sort with cmp=libc.strcasecmp', 'sorted(words, cmp=libc.strcasecmp)'),
]

for (comment, stmt) in stmts:
    t = timeit.Timer(stmt=stmt, setup=setup)
    print '%s: %.2f msec/pass' % (comment, (1000*t.timeit(10)/10))

typische Zeiten auf meiner Maschine:

235886 words in list
simple sort: 483.59 msec/pass
sort with key=str.lower: 1064.70 msec/pass
sort with cmp=libc.strcasecmp: 5487.86 msec/pass

Die Version mit str.lower ist also nicht nur die mit Abstand schnellste, sondern auch die tragbarste und Pythonic-Lösung aller hier vorgeschlagenen Lösungen .. Ich habe die Speicherauslastung nicht profiliert, aber das Originalposter hat noch keinen zwingenden Grund dafür mach dir darüber Sorgen. Wer sagt auch, dass ein Aufruf in das libc-Modul keine Zeichenfolgen dupliziert?

Hinweis: Die String-Methode lower() hat auch den Vorteil, dass sie vom Gebietsschema abhängt. Etwas, das Sie wahrscheinlich nicht richtig machen werden, wenn Sie Ihre eigene "optimierte" Lösung schreiben. Aufgrund von Fehlern und fehlenden Funktionen in Python kann dieser Vergleich jedoch zu falschen Ergebnissen in einem Unicode-Kontext führen.

74
user3850

Verwenden Sie diesen Vergleich in einem sehr häufig ausgeführten Pfad einer hochperformanten Anwendung? Oder führen Sie das auf Zeichenfolgen aus, die Megabytes groß sind? Wenn nicht, sollten Sie sich nicht um die Leistung sorgen und nur die .lower () -Methode verwenden.

Der folgende Code demonstriert, dass das Vergleichen von Groß- und Kleinschreibung durch den Aufruf von .lower () an zwei Zeichenfolgen, die jeweils fast ein Megabyte groß sind, auf meinem 1,8-GHz-Desktopcomputer ungefähr 0,009 Sekunden dauert:

from timeit import Timer

s1 = "1234567890" * 100000 + "a"
s2 = "1234567890" * 100000 + "B"

code = "s1.lower() < s2.lower()"
time = Timer(code, "from __main__ import s1, s2").timeit(1000)
print time / 1000   # 0.00920499992371 on my machine

Wenn dies tatsächlich ein äußerst wichtiger, leistungskritischer Codeabschnitt ist, empfehle ich, eine Funktion in C zu schreiben und von Ihrem Python-Code aus aufzurufen, da Sie damit eine wirklich effiziente Suche ohne Berücksichtigung der Groß- und Kleinschreibung durchführen können. Details zum Schreiben von C-Erweiterungsmodulen finden Sie hier: https://docs.python.org/extending/extending.html

7
Eli Courtwright

Ihre Frage impliziert, dass Sie kein Unicode benötigen. Versuchen Sie das folgende Codefragment. Wenn es für Sie funktioniert, sind Sie fertig:

Python 2.5.2 (r252:60911, Aug 22 2008, 02:34:17)
[GCC 4.3.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import locale
>>> locale.setlocale(locale.LC_COLLATE, "en_US")
'en_US'
>>> sorted("ABCabc", key=locale.strxfrm)
['a', 'A', 'b', 'B', 'c', 'C']
>>> sorted("ABCabc", cmp=locale.strcoll)
['a', 'A', 'b', 'B', 'c', 'C']

Erläuterung: Falls dies auf den ersten Blick nicht offensichtlich ist, scheint locale.strcoll die Funktion zu sein, die Sie benötigen, wobei die str.lower- oder locale.strxfrm-Zeichenketten "duplicate" vermieden werden.

7
tzot

Ich kann keine andere integrierte Methode zum Vergleichen von Groß- und Kleinschreibung finden: Das Python-Kochbuchrezept verwendet lower ().

Sie müssen jedoch vorsichtig sein, wenn Sie aufgrund des Türkisch-I-Problems für Vergleiche niedrigere verwenden. Leider ist Python's Handling für Turkish Is nicht gut. ı wird zu I konvertiert, aber ich werde nicht zu ı konvertiert. © wird in i konvertiert, aber ich wird nicht in © konvertiert. 

5
Douglas Leeder

Es gibt kein eingebautes Äquivalent zu der gewünschten Funktion.

Sie können Ihre eigene Funktion schreiben, die jedes Zeichen in .lower () umwandelt, um zu vermeiden, dass beide Zeichenfolgen dupliziert werden. 

Wenn Sie nicht mit extrem langen Zeichenfolgen arbeiten (so lange, dass es beim Duplizieren zu einem Speicherproblem kommen kann), würde ich es einfach halten und verwenden 

str1.lower() == str2.lower()

Du wirst ok sein

3
Ricardo Reyes

Diese Frage stellt zwei sehr unterschiedliche Dinge:

  1. Was ist der einfachste Weg, Zeichenfolgen in Python zu vergleichen, ohne dabei die Groß- und Kleinschreibung zu berücksichtigen?
  2. Ich denke, ich suche nach einem Äquivalent zu C's stricmp ().

Da # 1 bereits sehr gut beantwortet wurde (dh: str1.lower () <str2.lower ()), werde ich # 2 beantworten.

def strincmp(str1, str2, numchars=None):
    result = 0
    len1 = len(str1)
    len2 = len(str2)
    if numchars is not None:
        minlen = min(len1,len2,numchars)
    else:
        minlen = min(len1,len2)
    #end if
    orda = ord('a')
    ordz = ord('z')

    i = 0
    while i < minlen and 0 == result:
        ord1 = ord(str1[i])
        ord2 = ord(str2[i])
        if ord1 >= orda and ord1 <= ordz:
            ord1 = ord1-32
        #end if
        if ord2 >= orda and ord2 <= ordz:
            ord2 = ord2-32
        #end if
        result = cmp(ord1, ord2)
        i += 1
    #end while

    if 0 == result and minlen != numchars:
        if len1 < len2:
            result = -1
        Elif len2 < len1:
            result = 1
        #end if
    #end if

    return result
#end def

Verwenden Sie diese Funktion nur, wenn dies sinnvoll ist, da in vielen Fällen die Kleinbuchstaben-Technik überlegen ist.

Ich arbeite nur mit ASCII-Strings, ich bin mir nicht sicher, wie sich das mit Unicode verhält.

2
trevorcroft

Wenn etwas in der Standardbibliothek nicht gut unterstützt wird, suche ich immer nach einem PyPI-Paket. Mit der Virtualisierung und der Allgegenwart moderner Linux-Distributionen verzichte ich auf Python-Erweiterungen. PyICU scheint die Rechnung zu passen: https://stackoverflow.com/a/1098160/3461

Es gibt jetzt auch eine Option, die aus reinem Python besteht. Es ist gut getestet: https://github.com/jtauber/pyuca


Alte Antwort:

Ich mag die Lösung für reguläre Ausdrücke. Hier ist eine Funktion, die Sie dank der Blockstrukturunterstützung von python in jede Funktion kopieren und einfügen können.

def equals_ignore_case(str1, str2):
    import re
    return re.match(re.escape(str1) + r'\Z', str2, re.I) is not None

Da ich match anstelle von search verwendet habe, musste ich dem regulären Ausdruck kein Caret (^) hinzufügen.

Hinweis: Hier wird nur die Gleichheit geprüft, was manchmal erforderlich ist. Ich würde auch nicht so weit gehen und sagen, dass es mir gefällt.

2
Benjamin Atkin

Das empfohlene Idiom zum Sortieren von Wertelisten mit teuren Schlüsseln für die Berechnung ist das sogenannte "dekorierte Muster". Es besteht einfach darin, eine Liste von (Schlüssel-, Wert-) Tupeln aus der ursprünglichen Liste zu erstellen und diese Liste zu sortieren. Dann ist es einfach, die Schlüssel zu entfernen und die Liste der sortierten Werte abzurufen:

>>> original_list = ['a', 'b', 'A', 'B']
>>> decorated = [(s.lower(), s) for s in original_list]
>>> decorated.sort()
>>> sorted_list = [s[1] for s in decorated]
>>> sorted_list
['A', 'a', 'B', 'b']

Oder wenn Sie Einliner mögen:

>>> sorted_list = [s[1] for s in sorted((s.lower(), s) for s in original_list)]
>>> sorted_list
['A', 'a', 'B', 'b']

Wenn Sie sich wirklich Sorgen um die Kosten für den Aufruf von lower () machen, können Sie einfach überall Tupel (abgesenkte Zeichenfolge, ursprüngliche Zeichenfolge) speichern. Tupel sind die billigsten Container in Python. Sie sind auch hashable, sodass sie als Wörterbuchschlüssel, Set-Member usw. verwendet werden können.

1
Antoine P.

So machen Sie es mit re:

import re
p = re.compile('^hello$', re.I)
p.match('Hello')
p.match('hello')
p.match('HELLO')
1
Moses Ting

Für gelegentliche oder sogar wiederholte Vergleiche sollten einige zusätzliche String-Objekte keine Rolle spielen, solange dies nicht in der innersten Schleife des Kerncodes geschieht oder Sie nicht über genügend Daten verfügen, um die Auswirkungen auf die Leistung tatsächlich zu bemerken. Sehen Sie, wenn Sie es tun: Dinge "dumm" zu machen, ist viel weniger dumm, wenn Sie es auch weniger tun.

Wenn Sie wirklich fortfahren möchten, viele und viele Textzeichen unabhängig von der Groß- und Kleinschreibung zu vergleichen, können Sie die Kleinbuchstaben der Zeichenfolgen zur Hand haben, um die Fertigstellung und Neuerstellung zu vermeiden, oder den gesamten Datensatz in Kleinbuchstaben normalisieren. Dies hängt natürlich von der Größe des Datensatzes ab. Wenn es relativ wenige Nadeln und einen großen Heuhaufen gibt, ist das Ersetzen der Nadeln durch kompilierte Regex-Objekte eine Lösung. Wenn es schwer zu sagen ist, ohne ein konkretes Beispiel zu sehen.

0
yason

Sie können jeden String einmalig in Kleinbuchstaben übersetzen - nur träge, wenn Sie ihn benötigen, oder als Präfix für die Sortierung, wenn Sie wissen, dass Sie die gesamte Sammlung von Strings sortieren werden. Es gibt mehrere Möglichkeiten, diesen Vergleichsschlüssel an die tatsächlich sortierten Daten anzuhängen. Diese Techniken sollten jedoch in einem gesonderten Problem behandelt werden.

Beachten Sie, dass diese Technik nicht nur zur Behandlung von Groß-/Kleinschreibungsthemen verwendet werden kann, sondern auch für andere Sortierungstypen, wie z. B. die ländereinstellungsspezifische Sortierung oder die Titelsortierung nach "Bibliothekstyp", bei der führende Artikel ignoriert und die Daten vor dem Sortieren normalisiert werden.

0
Dale Wilson

Verwenden Sie einfach die str().lower()-Methode, es sei denn, Hochleistung ist wichtig - schreiben Sie in diesem Fall diese Sortiermethode als C-Erweiterung.

"Wie schreibe ich eine Python-Erweiterung" scheint ein ordentliches Intro zu sein ..

Interessanter ist Dieser Leitfaden vergleicht die Verwendung der Bibliothek ctypes mit dem Schreiben eines externen C-Moduls (der ctype ist erheblich langsamer als die C-Erweiterung).

0
dbr
import re
if re.match('tEXT', 'text', re.IGNORECASE):
    # is True
0
Venkatesh Bachu

Ich bin mir ziemlich sicher, dass Sie entweder .lower () oder einen regulären Ausdruck verwenden müssen. Ich kenne keine integrierte Zeichenfolgenvergleichsfunktion, bei der die Groß-/Kleinschreibung nicht beachtet wird.

0
Mark Biek