indexЗавдяки регістру?


81

Чи чутливий до методу indexOf (String) регістр? Якщо так, чи існує версія, що не враховує регістр?


3
Не те, що я великий хлопець з продуктивності чи щось інше (я насправді вважаю, що налаштування продуктивності є злим), але .toUpperCase копіює ваш рядок кожного разу, коли ви його викликаєте, тому, якщо ви робите це в циклі, спробуйте перемістити .toUpperCase циклу, якщо це можливо.
Bill K

Відповіді:


75

Всі indexOf()методи чутливі до регістру. Ви можете зробити їх (приблизно, ламаним способом, але працюючи для багатьох випадків), не враховуючи регістр, попередньо перетворивши свої рядки у верхній / нижній регістр:

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

4
Остерігайтеся питань інтернаціоналізації (тобто турецької İ) при використанні toUpperCase. Більш правильним рішенням є використання str.toUpperCase (Locale.US) .indexOf (...);
James Van Huis

2
Я цілком впевнений, що перетворення регістрів, а потім порівняння не є цілком коректним згідно з правилами порівняння Unicode. Це працює для деяких речей (а саме для складання регістрів, яке зазвичай використовується лише в контекстах синтаксичного розбору), але для природної мови можуть бути особливі випадки, коли два рядки, які повинні порівнювати рівні, не, як у верхньому, так і в нижньому регістрі. Однак я не можу придумати жодних прикладів.
nielsm

7
Не буде працювати. Деякі дивні, міжнародні символи перетворюються на кілька символів при перетворенні на нижній / верхній регістр. Наприклад:"ß".toUpperCase().equals("SS")
Саймон

ß навряд чи є дивним персонажем і навряд чи є міжнародним, оскільки використовується лише в Німеччині та Австрії. Але так, це настільки ж добре, наскільки це вдається, але насправді це не порівняння, яке не враховує регістр, як уже три роки тому зазначав nielsm.
Joey

Не працює для турецького Unicode, який надходить прямо з чиєїсь електронної пошти.
Олександр Погребняк

43

Чи чутливий до методу indexOf (String) регістр?

Так, це чутливо до регістру:

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

Якщо так, чи існує версія, що не враховує регістр?

Ні, немає. Ви можете перетворити обидва рядки на малі регістри перед викликом indexOf:

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

8
о, будь ласка, будь ласка, не забудьте використовувати перетворення інваріантів культури з Locale.US, у нас було достатньо проблем із програмами Java, що працюють під турецькою мовою.
idursun

@idursun - примус до американської мови не вирішує проблему, оскільки вона все ще не працює для рядків, які насправді містять символи, з яких проблематично почати (наприклад, "ı".toLowerCase(Locale.US).indexOf("I".toLowerCase(Locale.US))слід повернути 0, оскільки перший рядок - турецька мала літера "I", і тому слід порівнювати як рівний верхній регістр "I"у другому, але повертає -1, оскільки останній "i"замість цього перетворюється на ).
Жуль

20

У класі StringUtils бібліотеки Apache Commons Lang існує метод ігнорування регістру

indexOfIgnoreCase (CharSequence str, CharSequence searchStr)


Це має бути прийнятою відповіддю, оскільки поточна не працює для певних не-ascii рядків, які містять символи керування Unicode. Наприклад, це працює для тексту, написаного турецькою мовою. За лаштунками Apache використовує regionMatches, і це працює.
Олександр Погребняк

17

Так, indexOfчутливий до регістру.

Я знайшов найкращий спосіб нечутливості до справи:

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

Це призведе до нечутливості справи indexOf().


2
Ні. Ніколи цього не роби. Причина в тому, що original.toLowerCase().length()не завжди дорівнює original.length(). Результат idxне може правильно зіставити original.
Чеок Ян Ченг

14

Ось моє рішення, яке не виділяє кучу пам'яті, тому воно повинно бути значно швидшим, ніж більшість інших реалізацій, згаданих тут.

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;
}

І ось модульні тести, які перевіряють правильну поведінку.

@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));  
}

Як це відповідає на питання ??
Каталізатор якості

7
Відповідь: "ні, немає версій indexOf, що не враховують регістр". Однак я додав рішення сюди, тому що люди збираються знайти цю сторінку, яка шукає рішення. Я зробив своє рішення доступним із тестовими кейсами, щоб наступна людина, яка проходить через нього, могла використовувати мій код для вирішення точно тієї ж проблеми. Ось чому переповнення стека корисно, чи не так? У мене є десятиліття досвіду написання високопродуктивного коду, половина якого в Google. Я просто дав добре перевірене рішення безкоштовно, щоб допомогти громаді.
Зак Ворхіес,

3
Це саме те, що мене зацікавило. Я виявив, що це приблизно на 10-15% швидше, ніж версія Apache Commons. Якби я міг підтримати його ще багато разів, я б. Дякую!
Джефф Вільямс,

Дякую, Джеффе, я радий, що це принесло вам велику цінність. Є й інші, які рекомендують, щоб ця публікація, яка пропонує рішення, йшла вгору. Якщо комусь ще сподобався мій код, я покірно прошу вас проголосувати за це рішення.
Zach Vorhies

2
Ось пропущений тестовий випадок:assertThat(StringUtils.indexOfIgnoreCase("ı" /* Turkish lower-case I, U+0131 */, "I"), is(0));
Жуль

10

Так, це чутливо до регістру. Ви можете зробити нечутливими indexOfдо регістру , перетворивши String і параметр String на регістр перед пошуком.

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

Зверніть увагу, що toUpperCase може не працювати при деяких обставинах. Наприклад, це:

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

idxU буде 20, що неправильно! idxL буде 19, що правильно. Проблема полягає в тому, що toUpperCase () перетворює символ "ß" у ДВА символи "SS", і це відкидає індекс.

Отже, завжди дотримуйтесь toLowerCase ()


1
Дотримання нижнього регістру не допомагає: якщо ви перейдете findдо "STRASSE", він взагалі не знайде його у нижньому регістрі, але правильно знайде у верхньому регістрі.
Жуль,

3

Що ви робите зі значенням індексу після повернення?

Якщо ви використовуєте його для маніпулювання рядком, то чи не могли б ви замість цього використовувати регулярний вираз?

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);
    }
}

Здивований відсутністю тут голосів. На сторінці, де переважають неправильні відповіді, це одна з трьох, яка насправді працює правильно.
Жуль,

2

Я щойно подивився джерело. Він порівнює символи, тому чутливий до регістру.


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

Це навіть не відповідає на повне запитання .. навіть не сказано, якщо тест пройде ....
jjnguy

2
Ви маєте рацію, я цього не зробив, я сподівався, що спонукає оригінального допитувача пройти тест і, можливо, увійде в звичку
Пол Маккензі

2
Ну, це добре ... але я стверджую, що було б краще проголосувати за питання, яке насправді дає відповідь, ніж за тест. StackOverflow намагається бути кодом Q та сховищем A. Отже, найкращими будуть повні відповіді.
jjnguy

1
@jjnguy: У мене завжди складалося враження, що люди, які розміщували тести, публікували тести, які здавали. @dfa зробив подібне. (Але відповідь @ dfa є повнішою).
Том

Але він також опублікував кілька слів (опис) ... Це, як правило, корисно.
jjnguy

2

Так, я впевнений, що це так. Одним із методів обходу, який використовує стандартну бібліотеку, буде:

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

2

Була та сама проблема. Я спробував регулярний вираз та апаш StringUtils.indexOfIgnoreCase-Method, але обидва були досить повільними ... Тож я сам написав короткий метод ...:

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;
  }

Згідно з моїми тестами, це набагато швидше ... (принаймні, якщо ваш searchString досить короткий). якщо у вас є якісь пропозиції щодо вдосконалення або помилок, було б непогано повідомити мене ... (оскільки я використовую цей код у додатку ;-)


Це насправді дуже розумно, оскільки рядок пошуку буде значно коротшим, ніж текст для пошуку, і він створює лише верхню та малу версію рядка пошуку. Дякую тобі за це!
п'ятдесят

Це значно повільніше, ніж версія StringUtils у моєму тестуванні. Однак відповідь Зака ​​на 10-15% швидша.
Джефф Вільямс,

Це рішення приблизно на 10% швидше, ніж рішення Зака ​​Ворхієса. Дякуємо за це рішення.
gogognome

Це рішення не дає правильної відповіді за наявності рядків, які змінюють довжину при перетворенні на великі регістри (наприклад, якщо ви шукаєте "ß", він знайде його в будь-якому рядку, що містить єдину велику літеру "S"), або тексту, який використовує альтернативні великі літери (наприклад, indexOfIgnoreCase("İ","i")має повернути 0, оскільки İце правильна велика літера iдля турецького тексту, але замість цього повертає -1, оскільки iвеликі букви вводяться до загальніших I).
Жуль

1

На перше питання вже давали відповіді багато разів. Так, усі String.indexOf()методи чутливі до регістру.

Якщо вам потрібна локальна інформація,indexOf() ви можете скористатися Collator . Залежно від встановленого значення сили ви можете отримати порівняння без урахування регістру, а також розглядати букви з наголосом такими ж, як і з ненаголошеними, тощо. Ось приклад того, як це зробити:

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;
}

Здивований відсутністю тут голосів. На сторінці, де переважають неправильні відповіді, це одна з трьох, яка насправді працює правильно.
Жуль

1

Просто підсумувавши, 3 рішення:

  • за допомогою toLowerCase () або toUpperCase
  • за допомогою StringUtils apache
  • за допомогою регулярного виразу

Тепер мені цікаво було, який з них найшвидший? Я вгадую в середньому перший.


0

Але це не складно написати:

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());
    }
}

Як зазначалось вище, це не дає "ı"змоги правильно визначити, що це варіант нижнього регістру (просто не той, який використовується за замовчуванням у більшості мов) "I". Або ж, якщо працювати на безліч машин на місцевість , де "ı" є за замовчуванням, він буде не в змозі помітити , що "i"також є рядковим варіантом "I".
Жуль

0

Перетворення обох рядків на малі регістри, як правило, не становить великої праці, але це буде повільно, якщо деякі з них є довгими. І якщо ви зробите це в циклі, то це буде дуже погано. З цієї причини я б рекомендував 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
Схоже, це може бути C #
weston

0

Ось версія, що дуже нагадує версію 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

Я хотів би заявити претензію на ЄДИНЕ та єдине рішення, опубліковане досі, яке насправді працює. :-)

Три класи проблем, з якими доводиться вирішувати.

  1. Неперехідні правила відповідності для нижнього та верхнього регістру. Проблема турецького I часто згадувалась в інших відповідях. Згідно з коментарями в джерелі Android для String.regionMatches, грузинські правила порівняння вимагають додаткового перетворення на малі регістри при порівнянні з урахуванням регістру, що не враховує регістр.

  2. Випадки, коли форми верхнього та нижнього регістру мають різну кількість літер. У цих випадках майже всі опубліковані рішення не вдаються. Приклад: німецькі STRASSE проти Straße мають нечутливі до регістру рівності, але мають різну довжину.

  3. Сильні сторони наголошених символів. Ефект локалі та контексту, збігаються акценти чи ні. У французькій мові великою буквою "é" є "E", хоча існує рух до наголошення на регістрі. У канадській французькій мові великою буквою "é" є "É", без винятку. Користувачі обох країн очікували б, що "e" відповідає "é" під час пошуку. Відповідність наголошених та ненаголошених символів залежить від мови. А тепер подумайте: чи дорівнює "E" "É"? Так. Це робить. Так чи інакше, французькою мовою.

В даний час я використовую android.icu.text.StringSearchдля правильної реалізації попередніх реалізацій нечутливих до регістру операцій indexOf.

Користувачі, що не є Android, можуть отримати доступ до тієї ж функціональності через пакет ICU4J, використовуючи com.ibm.icu.text.StringSearchклас.

Будьте обережні, посилаючись на класи у правильному пакунку icu ( android.icu.textабо com.ibm.icu.text), оскільки Android та JRE мають класи з однаковим іменем в інших просторах імен (наприклад, 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();
        .... 
    }

Тестові випадки (локаль, шаблон, цільовий текст, очікуваний результат):

    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: Якнайкраще я можу визначити, ПРИМАРНА міцність прив’язки повинна робити правильно, коли правила, характерні для локалі, розмежовують наголошені та ненаголошені символи відповідно до правил словника; але я не знаю, яку локаль використовувати для перевірки цієї передумови. Подаровані тестові випадки будуть вдячні.


1
Якщо ви хочете подвійно ліцензувати свій код, будь-ласка, зробіть це за допомогою іншої платформи та додайте там посилання. Масивна легальна легалізація, додана в кінці кожної відповіді, додає надзвичайну кількість безладу до Stack Overflow.
meagar

Тоді, можливо, вам слід знайти більш ефективний спосіб вирішення проблеми CC-BY-SA, застосованої до фрагментів коду,
Робін Девіс,

Вам також видається недоречним видалення ліцензій, наданих мною для фрагментів коду, на які я володію авторським правом.
Робін Девіс,

-2

indexOf чутливий до регістру. Це пов’язано з тим, що він використовує метод equals для порівняння елементів у списку. Те саме стосується вмісту та видалення.


Оригінальне питання стосується методу String's indexOf.
Джон Топлі

Я не знав, про що він говорив. Я не усвідомлював цього, поки інші люди щось не сказали. Принцип все ж той самий.
Роббі

2
Ні, це не так. Внутрішні елементи методу String's indexOf порівнюють символи, а не об'єкти, тому він не використовує метод equals.
Джон Топлі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.