indexOf Case Sensitive?

81

¿El método indexOf (String) distingue entre mayúsculas y minúsculas? Si es así, ¿hay una versión que no distinga entre mayúsculas y minúsculas?

Brian
fuente
3
No es que sea un tipo de gran rendimiento ni nada (de hecho, considero que la afinación de rendimiento es algo maligna), pero .toUpperCase copia tu cadena cada vez que la llamas, así que si haces esto en un bucle, intenta sacar el .toUpperCase del bucle si es posible.
Bill K

Respuestas:

75

Los indexOf()métodos son todas mayúsculas y minúsculas. Puede hacerlos (aproximadamente, de manera discontinua, pero funcionando para muchos casos) que no distingan entre mayúsculas y minúsculas convirtiendo sus cadenas a mayúsculas / minúsculas de antemano:

s1 = s1.toLowerCase(Locale.US);
s2 = s2.toLowerCase(Locale.US);
s1.indexOf(s2);
Joey
fuente
4
Tenga cuidado con los problemas de internacionalización (es decir, la © turca) cuando utilice toUpperCase. Una solución más adecuada es usar str.toUpperCase (Locale.US) .indexOf (...);
James Van Huis
2
Estoy bastante seguro de que la conversión de mayúsculas y minúsculas y luego la comparación no es del todo correcto según las reglas de comparación Unicode. Funciona para algunas cosas (es decir, el plegado de mayúsculas y minúsculas, que generalmente se usa solo en contextos de análisis sintáctico), pero para el lenguaje natural puede haber casos especiales en los que dos cadenas que deberían compararse iguales no lo hacen, ya sea en mayúsculas o en minúsculas. Sin embargo, no se me ocurren ejemplos desde el principio.
nielsm
7
No funcionará. Algunos caracteres internacionales extraños se convierten en varios caracteres cuando se convierten a minúsculas / mayúsculas. Por ejemplo:"ß".toUpperCase().equals("SS")
Simon
ß no es un carácter extraño y tampoco es internacional, ya que se usa solo en Alemania y Austria. Pero sí, esto es tan bueno como parece, pero en realidad no es una comparación que no distinga entre mayúsculas y minúsculas, como ya señaló Nielsm hace tres años.
Joey
No funciona para unicode turco, que proviene directamente del correo electrónico de alguien.
Alexander Pogrebnyak
43

¿El método indexOf (String) distingue entre mayúsculas y minúsculas?

Sí, distingue entre mayúsculas y minúsculas:

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

Si es así, ¿hay una versión que no distinga entre mayúsculas y minúsculas?

No, no lo hay. Puede convertir ambas cadenas a minúsculas antes de llamar a indexOf:

@Test
public void caseInsensitiveIndexOf() {
    assertTrue("Hello World!".toLowerCase().indexOf("Hello".toLowerCase()) != -1);
    assertTrue("Hello World!".toLowerCase().indexOf("hello".toLowerCase()) != -1);
}
dfa
fuente
8
Oh, por favor, no olvide utilizar la conversión de cultura invariante con Locale.US, ya tuvimos suficientes problemas con las aplicaciones Java que se ejecutan en la configuración regional turca.
idursun
@idursun: forzar la configuración regional de EE. UU. no resuelve el problema, porque aún no funciona para cadenas que realmente contienen los caracteres que son problemáticos para empezar (por ejemplo, "ı".toLowerCase(Locale.US).indexOf("I".toLowerCase(Locale.US))debería devolver 0 porque la primera cadena es minúscula turca "I", y por lo tanto debería comparar como igual a la mayúscula "I"en el segundo, pero devuelve -1 porque el último se convierte en su "i"lugar).
Jules
20

Hay un método de caso ignorado en la clase StringUtils de la biblioteca Apache Commons Lang

indexOfIgnoreCase (CharSequence str, CharSequence searchStr)

deepika
fuente
Esta debería ser una respuesta aceptada, ya que la actual no funciona para ciertas cadenas no ascii que contienen caracteres de control Unicode. Por ejemplo, esto funciona para texto escrito en turco. Detrás de escena, Apache usa regionMatches, y eso funciona.
Alexander Pogrebnyak
17

Si, indexOf distingue entre mayúsculas y minúsculas.

La mejor manera de hacer insensibilidad a mayúsculas y minúsculas que he encontrado es:

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

Eso no distingue entre mayúsculas y minúsculas indexOf().

jjnguy
fuente
2
No. Nunca hagas eso. La razón es que, original.toLowerCase().length()no siempre es igual a original.length(). El resultado idxno se puede asignar correctamente a original.
Cheok Yan Cheng
14

Aquí está mi solución que no asigna ninguna memoria de pila, por lo tanto, debería ser significativamente más rápida que la mayoría de las otras implementaciones mencionadas aquí.

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

Y aquí están las pruebas unitarias que verifican el comportamiento correcto.

@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));  
}
Zach Vorhies
fuente
¿Cómo responde esto a la pregunta?
Quality Catalyst
7
La respuesta es "no, no hay versiones de indexOf que no distingan entre mayúsculas y minúsculas". Sin embargo, agregué la solución aquí porque la gente va a encontrar esta página buscando soluciones. Puse mi solución a disposición con casos de prueba para que la próxima persona que venga pueda usar mi código para resolver exactamente el mismo problema. Es por eso que el desbordamiento de pila es útil, ¿verdad? Tengo una década de experiencia escribiendo código de alto rendimiento, la mitad de eso en Google. Acabo de regalar una solución bien probada de forma gratuita para ayudar a la comunidad.
Zach Vorhies
3
Esto es exactamente lo que me interesaba. Descubrí que esto era aproximadamente un 10-15% más rápido que la versión de Apache Commons. Si pudiera votarlo muchas veces más, lo haría. ¡Gracias!
Jeff Williams
Gracias Jeff, me alegro de que te haya dado mucho valor. Hay otros que recomiendan que esta publicación que proporciona una solución vaya hacia arriba. Si a alguien más le gusta mi código, le pido humildemente que vote a favor de esta solución.
Zach Vorhies
2
Aquí hay un caso de prueba faltante:assertThat(StringUtils.indexOfIgnoreCase("ı" /* Turkish lower-case I, U+0131 */, "I"), is(0));
Jules
10

Sí, distingue entre mayúsculas y minúsculas. Puede hacer una diferencia entre mayúsculas y minúsculas indexOfconvirtiendo su String y el parámetro String a mayúsculas antes de buscar.

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

Tenga en cuenta que es posible que toUpperCase no funcione en algunas circunstancias. Por ejemplo esto:

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

idxU será 20, ¡lo cual está mal! idxL será 19, lo cual es correcto. Lo que está causando el problema es que toUpperCase () convierte el carácter "ß" en DOS caracteres, "SS" y esto arroja el índice.

En consecuencia, quédese siempre con toLowerCase ()

Nick Lewis
fuente
1
Ceñirse a minúsculas no ayuda: si cambia finda "STRASSE", no lo encuentra en absoluto en la variante de minúsculas, pero lo encuentra correctamente en la versión en mayúsculas.
Jules
3

¿Qué está haciendo con el valor del índice una vez devuelto?

Si lo está usando para manipular su cadena, ¿no podría usar una expresión regular en su lugar?

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);
    }
}
caja de herramientas
fuente
Sorprendido por la falta de votos a favor aquí. En una página dominada por respuestas incorrectas, este es uno de los únicos tres que realmente funciona correctamente.
Jules
2

Acabo de mirar la fuente. Compara los caracteres para que distinga entre mayúsculas y minúsculas.

John Topley
fuente
2
@Test
public void testIndexofCaseSensitive() {
    TestCase.assertEquals(-1, "abcDef".indexOf("d") );
}
Paul McKenzie
fuente
Esto ni siquiera responde la pregunta completa ... ni siquiera dice si la prueba pasa ...
jjnguy
2
Tienes razón, no lo hice, esperaba que eso impulsara al interrogador original a realizar la prueba él mismo, y tal vez adquirir el hábito
Paul McKenzie
2
Bueno, eso está bien ... pero yo diría que sería mejor votar por una pregunta que realmente da una respuesta que una prueba. StackOverflow está intentando ser un repositorio de código Q y A. Por lo tanto, las respuestas completas serían las mejores.
jjnguy
1
@jjnguy: Siempre tuve la impresión de que las personas que publicaban pruebas publicaban pruebas que pasaban. @dfa hizo algo similar. (Pero la respuesta de @ dfa es más completa).
Tom
Pero también publicó algunas palabras (descripción) ... Suelen ser útiles.
jjnguy
2

Sí, estoy bastante seguro de que lo es. Un método para solucionarlo utilizando la biblioteca estándar sería:

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

Tuvo el mismo problema. Probé la expresión regular y el Apache StringUtils.indexOfIgnoreCase-Method, pero ambos fueron bastante lentos ... Así que escribí un método corto yo mismo ...:

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

Según mis pruebas, es mucho más rápido ... (al menos si su searchString es bastante corto). Si tiene alguna sugerencia de mejora o errores, sería bueno que me lo hiciera saber ... (ya que uso este código en una aplicación ;-)

Phil
fuente
Esto es realmente muy inteligente, ya que la cadena de búsqueda será significativamente más corta que el texto en el que buscar, y solo crea una versión en mayúsculas y minúsculas de la cadena de búsqueda. ¡Gracias por eso!
fiffy
Esto es significativamente más lento que la versión StringUtils en mis pruebas. Sin embargo, la respuesta de Zach es un 10-15% más rápida.
Jeff Williams
Esta solución es aproximadamente un 10% más rápida que la dada por Zach Vorhies. Gracias por esta solución
gogognome
Esta solución no produce una respuesta correcta en presencia de cadenas que cambian de longitud en la conversión a mayúsculas (p. Ej., Si busca "ß", la encontrará en cualquier cadena que contenga una sola "S" mayúscula) o para texto que utiliza mayúsculas alternativas (p. ej., indexOfIgnoreCase("İ","i")debe devolver 0 porque İes la mayúscula correcta de ipara el texto turco, pero en su lugar devuelve -1 porque ise escribe en mayúscula con la más común I).
Jules
1

La primera pregunta ya ha sido respondida muchas veces. Sí, todos los String.indexOf()métodos distinguen entre mayúsculas y minúsculas.

Si necesita una configuración regional, indexOf()puede usar el Collator . Dependiendo del valor de fuerza que establezca, puede obtener una comparación que no distingue entre mayúsculas y minúsculas y también tratar las letras acentuadas como si fueran las mismas que las sin acentos, etc. Aquí hay un ejemplo de cómo hacer esto:

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;
}
Bernd S
fuente
Sorprendido por la falta de votos a favor aquí. En una página dominada por respuestas incorrectas, este es uno de los únicos tres que realmente funciona correctamente.
Jules
1

Solo para resumir, 3 soluciones:

  • usando toLowerCase () o toUpperCase
  • usando StringUtils de apache
  • usando expresiones regulares

Ahora, lo que me preguntaba era cuál es el más rápido. Supongo que en promedio el primero.

max
fuente
0

Pero no es difícil escribir uno:

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());
    }
}
Carl Manaster
fuente
Como se comentó anteriormente, esto no identifica correctamente que "ı"es una variante en minúscula (pero no la predeterminada en la mayoría de los idiomas) de "I". O alternativamente, si se ejecuta en una máquina configurada en una configuración regional donde "ı" es la predeterminada, no se dará cuenta de que "i"también es una variante en minúscula de "I".
Jules
0

La conversión de ambas cadenas a minúsculas no suele ser un gran problema, pero sería lento si algunas de las cadenas son largas. Y si haces esto en un bucle, sería realmente malo. Por esta razón, lo recomendaría indexOfIgnoreCase.

Jakub Vrána
fuente
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;
            }

        }
Jawwad Rafiq
fuente
1
Esto parece que podría ser C #
weston
0

Aquí hay una versión que se parece mucho a la versión StringUtils de 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;
}
Ernie Thomason
fuente
0

Me gustaría reclamar la ÚNICA y única solución publicada hasta ahora que realmente funciona. :-)

Tres clases de problemas que deben resolverse.

  1. Reglas de coincidencia no transitivas para minúsculas y mayúsculas. El problema del turco I se ha mencionado con frecuencia en otras respuestas. Según los comentarios en la fuente de Android para String.regionMatches, las reglas de comparación georgianas requieren una conversión adicional a minúsculas cuando se compara para la igualdad que no distingue entre mayúsculas y minúsculas.

  2. Casos en los que las formas en mayúsculas y minúsculas tienen un número diferente de letras. Prácticamente todas las soluciones publicadas hasta ahora fallan, en estos casos. Ejemplo: alemán STRASSE vs. Straße tienen una igualdad que no distingue entre mayúsculas y minúsculas, pero tienen diferentes longitudes.

  3. Puntos fuertes de los personajes acentuados. Efecto de configuración regional Y de contexto independientemente de que los acentos coincidan o no. En francés, la forma mayúscula de 'é' es 'E', aunque hay un movimiento hacia el uso de acentos en mayúsculas. En francés canadiense, la forma mayúscula de 'é' es 'É', sin excepción. Los usuarios de ambos países esperan que "e" coincida con "é" al realizar la búsqueda. La coincidencia de caracteres acentuados y no acentuados depende de la configuración regional. Ahora considere: ¿"E" es igual a "É"? Si. Lo hace. En los lugares franceses, de todos modos.

Estoy usando actualmente android.icu.text.StringSearch para implementar correctamente implementaciones anteriores de operaciones indexOf que no distinguen entre mayúsculas y minúsculas.

Los usuarios que no utilizan Android pueden acceder a la misma funcionalidad a través del paquete ICU4J, utilizando el com.ibm.icu.text.StringSearch clase.

Tenga cuidado de hacer referencia a clases en el paquete icu correcto ( android.icu.texto com.ibm.icu.text) ya que Android y el JRE tienen clases con el mismo nombre en otros espacios de nombres (por ejemplo, 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();
        .... 
    }

Casos de prueba (configuración regional, patrón, texto de destino, resultado esperado):

    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.

PD: Lo mejor que puedo determinar, la fuerza de enlace PRIMARY debería hacer lo correcto cuando las reglas específicas de la configuración regional diferencian entre caracteres acentuados y no acentuados según las reglas del diccionario; pero no sé qué configuración regional usar para probar esta premisa. Los casos de prueba donados serán agradecidos.

Robin Davies
fuente
1
Si desea obtener una licencia doble de su código, hágalo a través de otra plataforma e incluya un enlace allí. Una gran cantidad de jerga legal adjunta al final de cada respuesta agrega una enorme cantidad de desorden a Stack Overflow.
meagar
Entonces tal vez debería encontrar una manera más eficiente para abordar el problema de la CC-BY-SA aplicado a fragmentos de código,
Robin Davies
También parece inapropiado que elimine las concesiones de licencia que proporcioné a los fragmentos de código de los que tengo derechos de autor.
Robin Davies
-2

indexOf distingue entre mayúsculas y minúsculas. Esto se debe a que utiliza el método equals para comparar los elementos de la lista. Lo mismo ocurre con contiene y elimina.

Robbie
fuente
La pregunta original es sobre el método indexOf de String.
John Topley
No sabía que de eso estaba hablando. No me di cuenta hasta que otras personas dijeron algo. Sin embargo, el principio sigue siendo el mismo.
Robbie
2
No, no lo es. Los aspectos internos del método indexOf de String compara caracteres, no objetos, por lo que no usa el método equals.
John Topley