indexOf rozróżniana wielkość liter?


81

Czy metoda indexOf (String) rozróżnia wielkość liter? Jeśli tak, czy istnieje wersja bez rozróżniania wielkości liter?


3
Nie żebym był wielkim wykonawcą czy kimkolwiek innym (właściwie uważam, że dostrajanie wydajności jest rodzajem zła), ale .toUpperCase kopiuje twój ciąg za każdym razem, gdy go wywołasz, więc jeśli robisz to w pętli, spróbuj usunąć .toUpperCase pętli, jeśli to możliwe.
Bill K,

Odpowiedzi:


75

We indexOf()wszystkich metodach rozróżniana jest wielkość liter. Możesz uczynić je (z grubsza, w zepsuty sposób, ale działając w wielu przypadkach) bez rozróżniania wielkości liter, konwertując wcześniej swoje ciągi na duże / małe litery:

s1 = s1.toLowerCase(Locale.US);
s2 = s2.toLowerCase(Locale.US);
s1.indexOf(s2);

4
Uważaj na problemy z internacjonalizacją (np. Turecki İ) podczas korzystania z toUpperCase. Bardziej odpowiednim rozwiązaniem jest użycie str.toUpperCase (Locale.US) .indexOf (...);
James Van Huis,

2
Jestem całkiem pewien, że konwertowanie wielkości liter, a następnie porównywanie nie jest całkowicie poprawne zgodnie z regułami porównywania Unicode. Działa to w niektórych przypadkach (mianowicie zawijanie wielkości liter, które jest zwykle używane tylko w kontekstach analizy składni), ale w przypadku języka naturalnego mogą istnieć specjalne przypadki, w których dwa ciągi, które powinny porównać, są równe nie, pod obydwiema dużymi lub obiema małymi literami. Nie mogę jednak wymyślić żadnych przykładów od razu.
nielsm

7
Nie zadziała. Niektóre dziwne, międzynarodowe znaki są konwertowane na wiele znaków podczas konwersji na małe / duże litery. Na przykład:"ß".toUpperCase().equals("SS")
Simon

ß nie jest postacią dziwną i prawie nie międzynarodową, używaną tylko w Niemczech i Austrii. Ale tak, to jest tak dobre, jak to tylko możliwe, ale w rzeczywistości nie jest to porównanie bez uwzględniania wielkości liter, jak nielsm już zauważył trzy lata temu.
Joey,

Nie działa dla tureckiego Unicode, który pochodzi prosto z czyjegoś e-maila.
Alexander Pogrebnyak

43

Czy metoda indexOf (String) rozróżnia wielkość liter?

Tak, rozróżniana jest wielkość liter:

@Test
public void indexOfIsCaseSensitive() {
    assertTrue("Hello World!".indexOf("Hello") != -1);
    assertTrue("Hello World!".indexOf("hello") == -1);
}

Jeśli tak, czy istnieje wersja bez rozróżniania wielkości liter?

Nie, nie ma. Możesz przekonwertować oba ciągi na małe litery przed wywołaniem indexOf:

@Test
public void caseInsensitiveIndexOf() {
    assertTrue("Hello World!".toLowerCase().indexOf("Hello".toLowerCase()) != -1);
    assertTrue("Hello World!".toLowerCase().indexOf("hello".toLowerCase()) != -1);
}

8
och, proszę, proszę, nie zapomnij użyć konwersji niezmiennej kultury w Locale.US, mieliśmy wystarczająco dużo problemów z aplikacjami Java działającymi pod tureckim językiem.
idursun

@idursun - wymuszenie na język amerykański nie rozwiązuje problemu, ponieważ nadal nie działa w przypadku ciągów zawierających znaki, które są problematyczne na początku (na przykład "ı".toLowerCase(Locale.US).indexOf("I".toLowerCase(Locale.US))powinno zwrócić 0, ponieważ pierwszy ciąg jest turecką małą literą "I", i dlatego powinien być porównywany jako równy wielkiej litery "I"w drugim, ale zwraca -1, ponieważ "i"zamiast tego ta ostatnia jest konwertowana na ).
Jules

20

W klasie StringUtils biblioteki Apache Commons Lang istnieje metoda ignorowania wielkości liter

indexOfIgnoreCase (CharSequence str, CharSequence searchStr)


Powinna to być akceptowana odpowiedź, ponieważ obecna nie działa dla niektórych ciągów innych niż ASCII, które zawierają znaki sterujące Unicode. Na przykład działa to w przypadku tekstu napisanego w języku tureckim. Za kulisami Apache używa regionMatches i to działa.
Alexander Pogrebnyak

17

Tak, indexOfrozróżnia się wielkość liter.

Najlepszy sposób na niewrażliwość na wielkość liter, jaki znalazłem, to:

String original;
int idx = original.toLowerCase().indexOf(someStr.toLowerCase());

To spowoduje, że wielkość liter nie będzie uwzględniana indexOf().


2
Nie. Nigdy tego nie rób. Powodem jest to, że original.toLowerCase().length()nie zawsze równa się original.length(). Wynik idxnie może zostać poprawnie odwzorowany na original.
Cheok Yan Cheng

14

Oto moje rozwiązanie, które nie alokuje żadnej pamięci sterty, dlatego powinno być znacznie szybsze niż większość innych wymienionych tutaj implementacji.

public static int indexOfIgnoreCase(final String haystack,
                                    final String needle) {
    if (needle.isEmpty() || haystack.isEmpty()) {
        // Fallback to legacy behavior.
        return haystack.indexOf(needle);
    }

    for (int i = 0; i < haystack.length(); ++i) {
        // Early out, if possible.
        if (i + needle.length() > haystack.length()) {
            return -1;
        }

        // Attempt to match substring starting at position i of haystack.
        int j = 0;
        int ii = i;
        while (ii < haystack.length() && j < needle.length()) {
            char c = Character.toLowerCase(haystack.charAt(ii));
            char c2 = Character.toLowerCase(needle.charAt(j));
            if (c != c2) {
                break;
            }
            j++;
            ii++;
        }
        // Walked all the way to the end of the needle, return the start
        // position that this was found.
        if (j == needle.length()) {
            return i;
        }
    }

    return -1;
}

A oto testy jednostkowe, które weryfikują prawidłowe zachowanie.

@Test
public void testIndexOfIgnoreCase() {
    assertThat(StringUtils.indexOfIgnoreCase("A", "A"), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("a", "A"), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("A", "a"), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("a", "a"), is(0));

    assertThat(StringUtils.indexOfIgnoreCase("a", "ba"), is(-1));
    assertThat(StringUtils.indexOfIgnoreCase("ba", "a"), is(1));

    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", " Royal Blue"), is(-1));
    assertThat(StringUtils.indexOfIgnoreCase(" Royal Blue", "Royal Blue"), is(1));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "royal"), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "oyal"), is(1));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "al"), is(3));
    assertThat(StringUtils.indexOfIgnoreCase("", "royal"), is(-1));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", ""), is(0));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "BLUE"), is(6));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "BIGLONGSTRING"), is(-1));
    assertThat(StringUtils.indexOfIgnoreCase("Royal Blue", "Royal Blue LONGSTRING"), is(-1));  
}

Jak to odpowiada na pytanie?
Quality Catalyst

7
Odpowiedź brzmi: „nie, nie ma wersji indeksu bez rozróżniania wielkości liter”. Jednak dodałem tutaj rozwiązanie, ponieważ ludzie będą szukać tej strony w poszukiwaniu rozwiązań. Udostępniłem moje rozwiązanie z przypadkami testowymi, aby następna osoba mogła użyć mojego kodu do rozwiązania dokładnie tego samego problemu. Dlatego przepełnienie stosu jest przydatne, prawda? Mam dziesięcioletnie doświadczenie w pisaniu kodu o wysokiej wydajności, z tego połowę w Google. Właśnie przekazałem bezpłatnie dobrze przetestowane rozwiązanie, aby pomóc społeczności.
Zach Vorhies

3
To jest dokładnie to, co mnie interesuje. Okazało się, że jest to około 10-15% szybsze niż wersja Apache Commons. Gdybym mógł zagłosować za nim wiele razy, zrobiłbym to. Dzięki!
Jeff Williams,

Dzięki Jeff, cieszę się, że dało ci to dużą wartość. Są inni, którzy zalecają, aby ten post, który zapewnia rozwiązanie, poszedł w górę. Jeśli ktoś inny polubi mój kod, to pokornie proszę o głosowanie za tym rozwiązaniem.
Zach Vorhies,

2
Oto brakujący przypadek testowy:assertThat(StringUtils.indexOfIgnoreCase("ı" /* Turkish lower-case I, U+0131 */, "I"), is(0));
Jules

10

Tak, rozróżniana jest wielkość liter. Możesz nie rozróżniać wielkości liter indexOf, konwertując String i parametr String na duże litery przed wyszukiwaniem.

String str = "Hello world";
String search = "hello";
str.toUpperCase().indexOf(search.toUpperCase());

Zauważ, że toUpperCase może nie działać w pewnych okolicznościach. Na przykład to:

String str = "Feldbergstraße 23, Mainz";
String find = "mainz";
int idxU = str.toUpperCase().indexOf (find.toUpperCase ());
int idxL = str.toLowerCase().indexOf (find.toLowerCase ());

idxU będzie miało 20 lat, co jest złe! idxL będzie równe 19, co jest poprawne. Przyczyną problemu jest to, że toUpperCase () konwertuje znak „ß” na DWIE znaki, „SS”, co powoduje wyłączenie indeksu.

W związku z tym zawsze trzymaj się toLowerCase ()


1
Trzymanie się małych liter nie pomaga: jeśli zmienisz findna "STRASSE", nie znajduje go w ogóle w wariancie z małymi literami, ale poprawnie znajduje go w wersji z dużymi literami.
Jules

3

Co robisz ze zwróconą wartością indeksu?

Jeśli używasz go do manipulowania ciągiem, czy nie możesz zamiast tego użyć wyrażenia regularnego?

import static org.junit.Assert.assertEquals;    
import org.junit.Test;

public class StringIndexOfRegexpTest {

    @Test
    public void testNastyIndexOfBasedReplace() {
        final String source = "Hello World";
        final int index = source.toLowerCase().indexOf("hello".toLowerCase());
        final String target = "Hi".concat(source.substring(index
                + "hello".length(), source.length()));
        assertEquals("Hi World", target);
    }

    @Test
    public void testSimpleRegexpBasedReplace() {
        final String source = "Hello World";
        final String target = source.replaceFirst("(?i)hello", "Hi");
        assertEquals("Hi World", target);
    }
}

Zaskoczony brakiem głosów pozytywnych. Na stronie zdominowanej przez nieprawidłowe odpowiedzi jest to jedna z trzech stron, które faktycznie działają poprawnie.
Jules

2

Właśnie spojrzałem na źródło. Porównuje znaki, więc rozróżnia wielkość liter.


2
@Test
public void testIndexofCaseSensitive() {
    TestCase.assertEquals(-1, "abcDef".indexOf("d") );
}

To nawet nie odpowiada na pełne pytanie… nie mówi nawet, czy test
zakończy się pomyślnie

2
Masz rację. Nie, miałem nadzieję, że skłoni to oryginalnego pytającego do samodzielnego przeprowadzenia testu i może przyzwyczaić się
Paul McKenzie.

2
Cóż, w porządku ... ale uważam, że lepiej byłoby zagłosować za pytaniem, które faktycznie daje odpowiedź, niż na test. StackOverflow próbuje być repozytorium kodu Q i A. Zatem pełne odpowiedzi byłyby najlepsze.
jjnguy

1
@jjnguy: Zawsze miałem wrażenie, że ludzie, którzy publikowali testy, publikowali testy, które zdały. @dfa zrobił coś podobnego. (Ale odpowiedź @ dfa jest bardziej kompletna).
Tom

Ale zamieścił też kilka słów (opis) ... Te zwykle są pomocne.
jjnguy

2

Tak, jestem całkiem pewien, że tak. Jedną z metod obejścia tego za pomocą biblioteki standardowej jest:

int index = str.toUpperCase().indexOf("FOO"); 

2

Miałem ten sam problem. Wypróbowałem wyrażenia regularne i metodę Apache StringUtils.indexOfIgnoreCase-Method, ale oba były dość powolne ... Więc sam napisałem krótką metodę ...:

public static int indexOfIgnoreCase(final String chkstr, final String searchStr, int i) {
    if (chkstr != null && searchStr != null && i > -1) {
          int serchStrLength = searchStr.length();
          char[] searchCharLc = new char[serchStrLength];
          char[] searchCharUc = new char[serchStrLength];
          searchStr.toUpperCase().getChars(0, serchStrLength, searchCharUc, 0);
          searchStr.toLowerCase().getChars(0, serchStrLength, searchCharLc, 0);
          int j = 0;
          for (int checkStrLength = chkstr.length(); i < checkStrLength; i++) {
                char charAt = chkstr.charAt(i);
                if (charAt == searchCharLc[j] || charAt == searchCharUc[j]) {
                     if (++j == serchStrLength) {
                           return i - j + 1;
                     }
                } else { // faster than: else if (j != 0) {
                         i = i - j;
                         j = 0;
                    }
              }
        }
        return -1;
  }

Według moich testów jest znacznie szybszy ... (przynajmniej jeśli twój ciąg searchString jest raczej krótki). jeśli masz jakieś sugestie dotyczące ulepszeń lub błędów, byłoby miło dać mi znać ... (ponieważ używam tego kodu w aplikacji ;-)


Jest to w rzeczywistości bardzo sprytne, ponieważ szukany ciąg będzie znacznie krótszy niż tekst do przeszukania i tworzy tylko wersję z dużymi i małymi literami. Dziękuję za to!
fiffy

W moich testach jest to znacznie wolniejsze niż wersja StringUtils. Jednak odpowiedź Zacha jest o 10-15% szybsza.
Jeff Williams

To rozwiązanie jest około 10% szybsze niż to podane przez Zacha Vorhiesa. Dziękuję za to rozwiązanie.
gogognome

To rozwiązanie nie daje poprawnej odpowiedzi w obecności ciągów, które zmieniają długość przy konwersji na duże litery (np. Jeśli wyszukujesz "ß", znajdzie je w dowolnym ciągu zawierającym pojedynczą wielką "S") lub w tekście, który używa alternatywnych wielkich liter (np. indexOfIgnoreCase("İ","i")powinien zwrócić 0, ponieważ İjest to poprawna wielkość liter idla tekstu w języku tureckim, ale zamiast tego zwraca -1, ponieważ ijest pisane wielką literą do najczęściej używanych I).
Jules

1

Na pierwsze pytanie udzielono już wielu odpowiedzi. Tak, we String.indexOf()wszystkich metodach rozróżniana jest wielkość liter.

Jeśli potrzebujesz wrażliwego na ustawienia regionalne, indexOf()możesz użyć Collator . W zależności od ustawionej wartości siły, możesz uzyskać porównanie bez rozróżniania wielkości liter, a także traktować litery akcentowane jako takie same, jak te bez akcentu itp. Oto przykład, jak to zrobić:

private int indexOf(String original, String search) {
    Collator collator = Collator.getInstance();
    collator.setStrength(Collator.PRIMARY);
    for (int i = 0; i <= original.length() - search.length(); i++) {
        if (collator.equals(search, original.substring(i, i + search.length()))) {
            return i;
        }
    }
    return -1;
}

Zaskoczony brakiem głosów pozytywnych. Na stronie zdominowanej przez nieprawidłowe odpowiedzi jest to jedna z trzech stron, które faktycznie działają poprawnie.
Jules

1

Podsumowując, 3 rozwiązania:

  • przy użyciu toLowerCase () lub toUpperCase
  • przy użyciu StringUtils z Apache
  • używając wyrażenia regularnego

Zastanawiałem się, który z nich jest najszybszy? Domyślam się, że średnio pierwszy.


0

Ale nie jest trudno napisać jedną:

public class CaseInsensitiveIndexOfTest extends TestCase {
    public void testOne() throws Exception {
        assertEquals(2, caseInsensitiveIndexOf("ABC", "xxabcdef"));
    }

    public static int caseInsensitiveIndexOf(String substring, String string) {
        return string.toLowerCase().indexOf(substring.toLowerCase());
    }
}

Jak wspomniano powyżej, nie pozwala to poprawnie zidentyfikować, że "ı"jest to wariant pisany małymi literami (tylko nie domyślny w większości języków) "I". Lub alternatywnie, jeśli zostanie uruchomiony na komputerze z ustawieniami narodowymi, w których "ı" jest to ustawienie domyślne, nie zauważy, że "i"jest to również wariant z małymi literami "I".
Jules

0

Konwersja obu ciągów na małe litery zwykle nie jest wielkim problemem, ale byłaby wolna, gdyby niektóre ciągi były długie. A jeśli zrobisz to w pętli, byłoby naprawdę źle. Z tego powodu polecam indexOfIgnoreCase.


0
 static string Search(string factMessage, string b)
        {

            int index = factMessage.IndexOf(b, StringComparison.CurrentCultureIgnoreCase);
            string line = null;
            int i = index;
            if (i == -1)
            { return "not matched"; }
            else
            {
                while (factMessage[i] != ' ')
                {
                    line = line + factMessage[i];
                    i++;
                }

                return line;
            }

        }

1
Wygląda na to, że może to być C #
weston

0

Oto wersja bardzo przypominająca wersję StringUtils Apache:

public int indexOfIgnoreCase(String str, String searchStr) {
    return indexOfIgnoreCase(str, searchStr, 0);
}

public int indexOfIgnoreCase(String str, String searchStr, int fromIndex) {
    // /programming/14018478/string-contains-ignore-case/14018511
    if(str == null || searchStr == null) return -1;
    if (searchStr.length() == 0) return fromIndex;  // empty string found; use same behavior as Apache StringUtils
    final int endLimit = str.length() - searchStr.length() + 1;
    for (int i = fromIndex; i < endLimit; i++) {
        if (str.regionMatches(true, i, searchStr, 0, searchStr.length())) return i;
    }
    return -1;
}

0

Chciałbym zgłosić roszczenie do JEDNEGO i jedynego opublikowanego do tej pory rozwiązania, które faktycznie działa. :-)

Trzy klasy problemów, z którymi należy się uporać.

  1. Nieprzechodnie reguły dopasowania dla małych i wielkich liter. W innych odpowiedziach często wspominano o tureckim problemie I. Zgodnie z komentarzami w źródle Androida dla String.regionMatches, gruzińskie reguły porównawcze wymagają dodatkowej konwersji na małe litery podczas porównywania pod kątem równości bez uwzględniania wielkości liter.

  2. Przypadki, w których duże i małe litery mają różną liczbę liter. W takich przypadkach prawie wszystkie opublikowane do tej pory rozwiązania zawodzą. Przykład: Niemiecki STRASSE vs Straße mają równość bez rozróżniania wielkości liter, ale mają różne długości.

  3. Wiążące mocne strony znaków akcentowanych. Ustawienia regionalne i kontekst wpływają niezależnie od tego, czy akcenty są zgodne, czy nie. W języku francuskim wielką literą „é” jest „E”, chociaż pojawia się ruch w kierunku używania wielkich liter z akcentami. W kanadyjskim francuskim, wielką literą „é” jest bez wyjątku „É”. Użytkownicy w obu krajach oczekiwaliby, że podczas wyszukiwania „e” będzie pasować do „é”. To, czy znaki akcentowane i bez akcentów pasują do siebie, zależy od języka. Rozważmy teraz: czy „E” równa się „É”? Tak. To robi. W każdym razie we francuskich lokalizacjach.

Obecnie używam android.icu.text.StringSearchdo poprawnej implementacji poprzednich implementacji operacji indexOf bez uwzględniania wielkości liter.

Użytkownicy systemów innych niż Android mogą uzyskać dostęp do tych samych funkcji za pośrednictwem pakietu ICU4J, używając com.ibm.icu.text.StringSearchklasy.

Uważaj, aby odwoływać się do klas w odpowiednim pakiecie icu ( android.icu.textlub com.ibm.icu.text), ponieważ zarówno system Android, jak i środowisko JRE mają klasy o tej samej nazwie w innych przestrzeniach nazw (np. Collator).

    this.collator = (RuleBasedCollator)Collator.getInstance(locale);
    this.collator.setStrength(Collator.PRIMARY);

    ....

    StringSearch search = new StringSearch(
         pattern,
         new StringCharacterIterator(targetText),
         collator);
    int index = search.first();
    if (index != SearchString.DONE)
    {
        // remember that the match length may NOT equal the pattern length.
        length = search.getMatchLength();
        .... 
    }

Przypadki testowe (ustawienia regionalne, wzorzec, tekst docelowy, oczekiwany wynik):

    testMatch(Locale.US,"AbCde","aBcDe",true);
    testMatch(Locale.US,"éèê","EEE",true);

    testMatch(Locale.GERMAN,"STRASSE","Straße",true);
    testMatch(Locale.FRENCH,"éèê","EEE",true);
    testMatch(Locale.FRENCH,"EEE","éèê",true);
    testMatch(Locale.FRENCH,"éèê","ÉÈÊ",true);

    testMatch(new Locale("tr-TR"),"TITLE","tıtle",true);  // Turkish dotless I/i
    testMatch(new Locale("tr-TR"),"TİTLE","title",true);  // Turkish dotted I/i
    testMatch(new Locale("tr-TR"),"TITLE","title",false);  // Dotless-I != dotted i.

PS: O ile potrafię najlepiej określić, siła wiązania PODSTAWOWA powinna działać właściwie, gdy reguły specyficzne dla lokalizacji rozróżniają znaki akcentowane i nieakcentowane zgodnie z regułami słownikowymi; ale nie wiem, którego języka użyć do przetestowania tej przesłanki. Podarowane przypadki testowe będą wdzięczne.


1
Jeśli chcesz uzyskać podwójną licencję na swój kod, zrób to za pośrednictwem innej platformy i umieść tam link. Ogromna kropla języka prawniczego dodana na końcu każdej odpowiedzi dodaje rażąco bałaganu do przepełnienia stosu.
meagar

W takim razie może powinieneś znaleźć skuteczniejszy sposób rozwiązania problemu CC-BY-SA zastosowanego do fragmentów kodu,
Robin Davies

Wydaje się również niewłaściwe, abyś odebrał udzieloną przeze mnie licencję na fragmenty kodu, do których posiadam prawa autorskie.
Robin Davies

-2

indexOf rozróżnia wielkość liter. Dzieje się tak, ponieważ używa metody equals do porównywania elementów na liście. To samo dotyczy zawiera i usuwa.


Pierwotne pytanie dotyczy metody indexOf Stringa.
John Topley,

Nie wiedziałem, o czym on mówi. Nie zdawałem sobie z tego sprawy, dopóki inni nie powiedzieli czegoś. Zasada jest jednak ta sama.
Robbie

2
Nie, nie jest. Elementy wewnętrzne metody indexOf Stringa porównują znaki, a nie obiekty, więc nie używa metody equals.
John Topley,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.