Wielkie a małe litery

85

Czy w przypadku porównań bez rozróżniania wielkości liter bardziej wydajne jest przekonwertowanie ciągu znaków na wielkie czy małe litery? Czy to w ogóle ma znaczenie?

W tym poście SO sugeruje się, że język C # jest bardziej wydajny z ToUpper, ponieważ „Microsoft zoptymalizował to w ten sposób”. Ale przeczytałem również ten argument, że konwersja ToLower vs ToUpper zależy od tego, co twoje ciągi zawierają więcej, i że zazwyczaj łańcuchy zawierają więcej małych liter, co sprawia, że ​​ToLower jest bardziej wydajne.

W szczególności chciałbym wiedzieć:

  • Czy istnieje sposób na zoptymalizowanie ToUpper lub ToLower tak, aby jeden był szybszy od drugiego?
  • Czy szybsze jest porównywanie wielkich i małych liter bez rozróżniania liter i dlaczego?
  • Czy są jakieś środowiska programistyczne (np. C, C #, Python, cokolwiek), w których jeden przypadek jest wyraźnie lepszy od drugiego i dlaczego?
Parappa
źródło

Odpowiedzi:

90

Zamiana na wielkie lub małe litery w celu dokonywania porównań bez uwzględniania wielkości liter jest niepoprawna ze względu na „interesujące” cechy niektórych kultur, zwłaszcza Turcji. Zamiast tego użyj StringComparer z odpowiednimi opcjami.

MSDN zawiera świetne wskazówki dotyczące obsługi ciągów. Możesz również sprawdzić, czy Twój kod przeszedł pomyślnie test Turcji .

EDYCJA: Zwróć uwagę na komentarz Neila na temat porównań porządkowych bez rozróżniania wielkości liter. Cała ta sfera jest dość mroczna :(

Jon Skeet
źródło
15
Tak, StringComparer jest świetny, ale nie ma odpowiedzi na pytanie ... W sytuacjach, w których nie można użyć StringComparer, takich jak instrukcja swtich przeciwko łańcuchowi; powinienem ToUpper czy ToLower w przełączniku?
joshperry
7
Użyj StringComparer i "if" / "else" zamiast używania ToUpper lub ToLower.
Jon Skeet
5
John, wiem, że konwersja na małe litery jest nieprawidłowa, ale nie słyszałem, że konwersja na wielkie litery jest nieprawidłowa. Czy możesz podać przykład lub odniesienie? Artykuł MSDN, do którego utworzyłeś łącze, mówi: „Porównania wykonane przy użyciu OrdinalIgnoreCase są behawioralnie zestawieniem dwóch wywołań: wywoływania ToUpperInvariant dla obu argumentów ciągu i wykonywania porównania porządkowego”. W sekcji zatytułowanej „Operacje na łańcuchach porządkowych” przedstawia to ponownie w kodzie.
Neil
2
@Neil: Interesujące, nie widziałem tego kawałka. W przypadku porządkowego porównania bez rozróżniania wielkości liter, myślę, że to wystarczy. W końcu trzeba coś wybrać . W przypadku porównań uwzględniających różnice kulturowe bez rozróżniania wielkości liter, myślę, że nadal byłoby miejsce na jakieś dziwne zachowanie. Zwrócę uwagę na Twój komentarz w odpowiedzi ...
Jon Skeet
4
@Triynko: Myślę, że ważne jest, aby skoncentrować się przede wszystkim na poprawności, z tym, że szybkie uzyskanie błędnej odpowiedzi nie jest zwykle lepsze (a czasem gorsze) niż powolne uzyskiwanie złej odpowiedzi.
Jon Skeet
25

Od firmy Microsoft w witrynie MSDN:

Najważniejsze wskazówki dotyczące używania ciągów znaków w programie .NET Framework

Zalecenia dotyczące użycia ciągów

Czemu? Od firmy Microsoft :

Normalizuj ciągi do wielkich liter

Istnieje niewielka grupa znaków, które po przekonwertowaniu na małe litery nie mogą odbywać podróży w obie strony.

Jaki jest przykład takiej postaci, która nie może odbyć podróży w obie strony?

  • Początek : grecki symbol Rho (U + 03f1) ϱ
  • Wielkie litery: duże greckie Rho (U + 03a1) Ρ
  • Małe litery: małe greckie Rho (U + 03c1) ρ

ϱ, Ρ , ρ

.NET Fiddle

Original: ϱ
ToUpper: Ρ
ToLower: ρ

Dlatego też, jeśli chcesz dokonywać porównań bez rozróżniania wielkości liter, konwertujesz ciągi na wielkie, a nie małe litery.

Więc jeśli musisz wybrać jedną, wybierz wielkie litery .

Ian Boyd
źródło
i jaki jest tego powód?
bjan
@bjan Powodem jest to, że źle jest tego nie robić.
Ian Boyd
1
Jaka grupa postaci? Co w ogóle oznacza podróż w obie strony?
johv,
1
@johv Z linku: „Podróż w obie strony oznacza konwersję znaków z jednego ustawienia regionalnego do innego, które inaczej reprezentuje dane znakowe, a następnie dokładne pobranie oryginalnych znaków z przekonwertowanych znaków.” Jaka grupa postaci? Nie wiem, ale zgadnę małą literę ipo turecku, kiedy stanie się İ, a nie to I, do czego jesteś przyzwyczajony. Ponadto jesteśmy przyzwyczajeni do Istawania się wielkimi literami i, ale w Turcji tak się dzieje ı.
Ian Boyd
3
Wracając do odpowiedzi na pierwotne pytanie: istnieją języki znające więcej niż jeden wariant z małymi literami dla jednego wariantu z dużymi literami. Chyba że znasz zasady, kiedy użyć której reprezentacji (inny przykład w języku greckim: mała litera sigma, używasz σ na początku słowa lub w środku, ς na końcu słowa (patrz en.wikipedia.org/wiki/Sigma ), nie można bezpiecznie wrócić do wariantu z małymi literami
Aconcagua,
19

Według MSDN bardziej wydajne jest przekazywanie łańcuchów i nakazanie porównaniu ignorowania wielkości liter:

String.Compare (strA, strB, StringComparison.OrdinalIgnoreCase) jest równoważne ( ale szybsze niż ) wywołanie

String.Compare (ToUpperInvariant (strA), ToUpperInvariant (strB), StringComparison.Ordinal).

Te porównania są nadal bardzo szybkie.

Oczywiście, jeśli w kółko porównujesz jeden ciąg, może to się nie udać.

Rob Walker
źródło
12

Opierając się na łańcuchach, które mają zwykle więcej wpisów z małych liter, ToLower powinien teoretycznie być szybszy (dużo porównań, ale niewiele przypisań).

W C lub w przypadku korzystania z indywidualnie dostępnych elementów każdego ciągu (takich jak ciągi C lub typ ciągu STL w C ++), jest to w rzeczywistości porównanie bajtów - więc porównywanie UPPERnie różni się od lower.

Gdybyś był podstępny i longzamiast tego załadował łańcuchy do tablic, uzyskałbyś bardzo szybkie porównanie całego ciągu, ponieważ może on porównać 4 bajty naraz. Jednak czas ładowania może sprawić, że nie będzie to opłacalne.

Dlaczego musisz wiedzieć, który jest szybszy? O ile nie robisz metrycznego zestawu porównań, jeden działający kilka cykli szybciej nie ma znaczenia dla ogólnej szybkości wykonywania i brzmi jak przedwczesna optymalizacja :)

królikarnia
źródło
11
Aby odpowiedzieć na pytanie, dlaczego muszę wiedzieć, co jest szybsze: nie muszę wiedzieć, po prostu chcę wiedzieć. :) To po prostu przypadek zobaczenia, jak ktoś zgłasza roszczenie (np. „Porównywanie ciągów z wielkich liter jest szybsze!”) I chce wiedzieć, czy to naprawdę prawda i / lub dlaczego tak twierdził.
Parappa
1
to ma sens - ja też jestem wiecznie ciekawy takich rzeczy :)
warren
W przypadku łańcuchów C, aby przekonwertować si przekształcić tw tablice longs takie, że łańcuchy są równe, jeśli tablice są równe, musisz iść w dół s i t, aż znajdziesz '\0'znak kończący (inaczej możesz porównać śmieci za końcami łańcuchów, który może być nielegalnym dostępem do pamięci, który wywołuje niezdefiniowane zachowanie). Ale w takim razie dlaczego nie zrobić po prostu porównań, przechodząc po postaciach jeden po drugim? Z napisami C ++ można prawdopodobnie uzyskać długość i .c_str()rzutować na a long *i porównać przedrostek długości .size() - .size()%(sizeof long). Mimo wszystko, wygląda na podejrzanego.
Jonas Kölker
6

Microsoft zoptymalizował ToUpperInvariant(), nie ToUpper(). Różnica polega na tym, że niezmiennik jest bardziej przyjazny dla kultury. Jeśli musisz wykonać porównania bez uwzględniania wielkości liter w ciągach, które mogą się różnić w kulturze, użyj niezmiennej, w przeciwnym razie wykonanie niezmiennej konwersji nie powinno mieć znaczenia.

Nie mogę jednak powiedzieć, czy ToUpper () czy ToLower () jest szybsze. Nigdy tego nie próbowałem, ponieważ nigdy nie miałem sytuacji, w której wydajność miałaby tak duże znaczenie.

Dan Herbert
źródło
jeśli Microsoft zoptymalizował kod do przeprowadzania porównań wielkich liter, czy to dlatego, że kod ASCII dla dużych liter zawiera tylko dwie cyfry 65-90, podczas gdy kod ASCII Małe litery 97-122, który zawiera 3 cyfry (potrzeba więcej przetwarzania)?
Medo Medo
3
@Medo Nie pamiętam dokładnych powodów optymalizacji, ale cyfry 2 na 3 prawie na pewno nie są powodem, ponieważ wszystkie litery są przechowywane jako liczby binarne, więc cyfry dziesiętne tak naprawdę nie mają znaczenia w oparciu o sposób ich przechowywania.
Dan Herbert
4

Jeśli wykonujesz porównanie ciągów w C #, użycie .Equals () jest znacznie szybsze zamiast konwertowania obu ciągów na wielkie lub małe litery. Kolejnym dużym plusem używania .Equals () jest to, że więcej pamięci nie jest przydzielane dla 2 nowych ciągów wielkich / małych liter.

Jon Tackabury
źródło
4
A jako bonus, jeśli wybierzesz odpowiednią opcję, faktycznie da to poprawne wyniki :)
Jon Skeet,
1

To naprawdę nie powinno mieć znaczenia. W przypadku znaków ASCII zdecydowanie nie ma to znaczenia - to tylko kilka porównań i trochę odwrócenie w dowolnym kierunku. Unicode może być nieco bardziej skomplikowany, ponieważ istnieją znaki, które zmieniają wielkość liter w dziwny sposób, ale tak naprawdę nie powinno być żadnej różnicy, chyba że tekst jest pełen tych znaków specjalnych.

Adam Rosenfield
źródło
1

Jeśli zrobisz to dobrze, konwersja na małe litery powinna mieć niewielką, nieznaczną przewagę szybkości, ale jest to, jak wielu sugerowało, zależne od kultury i nie jest dziedziczone w funkcji, ale w konwertowanych ciągach (wiele małych liter oznacza kilka przypisań do pamięci) - konwersja na duże litery jest szybsza, jeśli masz ciąg z dużą ilością dużych liter.

Wyraźniej
źródło
0

To zależy. Jak stwierdzono powyżej, zwykły tylko ASCII, jest identyczny. W .NET poczytaj o Stringu i używaj go, porównując jego poprawność z elementami i18n (kultury języków i unicode). Jeśli wiesz cokolwiek o prawdopodobieństwie danych wejściowych, użyj bardziej typowego przypadku.

Pamiętaj, że jeśli wykonujesz wiele porównań ciągów, długość jest doskonałym pierwszym dyskryminatorem.

Sanjaya R.
źródło
-2

Jeśli masz do czynienia z czystym ASCII, to nie ma znaczenia. To tylko OR x, 32 vs AND x, 224. Unicode, nie mam pojęcia ...

Brian Knoblauch
źródło
4
Jest to całkowicie błędne - operacja OR z 32 działa tylko dla AZ i znaków 64-127; schrzanił wszystkie inne postacie. AND'owanie z 32 jest jeszcze bardziej błędne - wynikiem zawsze będzie 0 (nul) lub 32 (spacja).
Adam Rosenfield,