¿Cómo encontrar la enésima aparición de un carácter en una cadena?

95

Similar a una pregunta publicada aquí , estoy buscando una solución en Java.

Es decir, ¿cómo encontrar el índice de la enésima aparición de un carácter / cadena de una cadena?

Ejemplo: " / carpeta1 / carpeta2 / carpeta3 / ". En este caso, si solicito la tercera aparición de barra (/), aparece antes de la carpeta3 y espero devolver esta posición de índice. Mi intención real es hacer una subcadena de la enésima aparición de un carácter.

¿Existe algún método conveniente / listo para usar disponible en la API de Java o necesitamos escribir una pequeña lógica por nuestra cuenta para resolver esto?

También,

  1. Busqué rápidamente si algún método es compatible con este propósito en StringUtils de Apache Commons Lang , pero no encontré ninguno.
  2. ¿Pueden las expresiones regulares ayudar en este sentido?
Gnanam
fuente
2
Para su ejemplo particular, dependiendo de lo que quiera hacer con el resultado, podría ser más fácil dividir la cadena en /, lo que podría proporcionarle lo que necesita directamente.
The Arquetípico Paul
@Paul: Esa también es una buena idea.
Gnanam

Respuestas:

128

Si su proyecto ya depende de Apache Commons que puede usar StringUtils.ordinalIndexOf, de lo contrario, aquí hay una implementación:

public static int ordinalIndexOf(String str, String substr, int n) {
    int pos = str.indexOf(substr);
    while (--n > 0 && pos != -1)
        pos = str.indexOf(substr, pos + 1);
    return pos;
}

Esta publicación ha sido reescrita como un artículo aquí .

aioobe
fuente
Aparte del error "off-by-one", hay otro gran positivo en la solución de @Jon Skeet: con un pequeño ajuste (invirtiendo el ciclo), también puede tener la "enésima aparición de la última".
Karan Chadha
@KaranChadha, lo mismo se aplica en esta solución. Simplemente cambie a lastIndexOf.
aioobe
60

Creo que la solución más fácil para encontrar la enésima aparición de una cadena es usar StringUtils.ordinalIndexOf () de Apache Commons.

Ejemplo:

StringUtils.ordinalIndexOf("aabaabaa", "b", 2)  == 5
Al Belsky
fuente
27

Ocurren dos opciones simples:

  • Usar charAt()repetidamente
  • Usar indexOf()repetidamente

Por ejemplo:

public static int nthIndexOf(String text, char needle, int n)
{
    for (int i = 0; i < text.length(); i++)
    {
        if (text.charAt(i) == needle)
        {
            n--;
            if (n == 0)
            {
                return i;
            }
        }
    }
    return -1;
}

Es posible que eso no funcione tan bien como usarlo indexOfrepetidamente, pero posiblemente sea más sencillo hacerlo bien.

Jon Skeet
fuente
15

Puedes probar algo como esto:

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
    public static void main(String[] args) {
      System.out.println(from3rd("/folder1/folder2/folder3/"));
    }

    private static Pattern p = Pattern.compile("(/[^/]*){2}/([^/]*)");

    public static String from3rd(String in) {
        Matcher m = p.matcher(in);

        if (m.matches())
            return m.group(2);
        else
            return null;
    }
}

Tenga en cuenta que hice algunas suposiciones en la expresión regular:

  • la ruta de entrada es absoluta (es decir, comienza con "/");
  • no necesita la 3ª "/" en el resultado.

Como se solicitó en un comentario, intentaré explicar la expresión regular: (/[^/]*){2}/([^/]*)

Visualización de expresiones regulares

  • /[^/]*es un /seguido de [^/]*(cualquier número de caracteres que no sea un /),
  • (/[^/]*)agrupa la expresión anterior en una sola entidad. Este es el 1primer grupo de la expresión,
  • (/[^/]*){2}significa que el grupo debe coincidir exactamente con los {2}tiempos,
  • [^/]*es de nuevo cualquier número de caracteres que no sean un /,
  • ([^/]*)agrupa la expresión anterior en una sola entidad. Este es el 2nd grupo de la expresión.

De esta manera, solo tiene que obtener la subcadena que coincida con el segundo grupo: return m.group(2);

Imagen cortesía de Debuggex

andcoz
fuente
1
¿podría explicar la expresión regular en inglés sencillo? Como: una barra invertida seguida de cualquier cosa que no sea una barra invertida un número indefinido de tiempo ... Entonces no estoy seguro.
Ced
1
@Ced, agregué una explicación y una pequeña corrección para regex. Espero que ahora esté más claro.
andcoz
Gracias por explicar la expresión regular.
Vishwa Ratna
8

Hice algunos cambios en la respuesta de aioobe y obtuve una enésima versión de lastIndexOf y solucioné algunos problemas de NPE. Ver código a continuación:

public int nthLastIndexOf(String str, char c, int n) {
        if (str == null || n < 1)
            return -1;
        int pos = str.length();
        while (n-- > 0 && pos != -1)
            pos = str.lastIndexOf(c, pos - 1);
        return pos;
}
Mentecato
fuente
3
Creo que es razonable que el método arroje una NPE si se da nullcomo argumento. Este es el comportamiento más común en la biblioteca estándar.
aioobe
5
 ([.^/]*/){2}[^/]*(/)

Coincide con cualquier cosa seguida de / dos veces, luego otra vez. El tercero es el que quieres

El estado de Matcher se puede usar para decir dónde está el último /

El Pablo arquetípico
fuente
Estoy seguro de que esta es una respuesta muy buena, pero ¿cómo la uso en mi código?
ARK
Mire la respuesta de @ andcoz (expresión regular diferente, pero la idea es la misma)
The Arquetípico Paul
3
public static int nth(String source, String pattern, int n) {

   int i = 0, pos = 0, tpos = 0;

   while (i < n) {

      pos = source.indexOf(pattern);
      if (pos > -1) {
         source = source.substring(pos+1);
         tpos += pos+1;
         i++;
      } else {
         return -1;
      }
   }

   return tpos - 1;
}
Saul
fuente
3

Hoy en día existe soporte para StringUtils de Apache Commons Lang ,

Este es el primitivo:

int org.apache.commons.lang.StringUtils.ordinalIndexOf(CharSequence str, CharSequence searchStr, int ordinal)

para su problema, puede codificar lo siguiente: StringUtils.ordinalIndexOf(uri, "/", 3)

También puede encontrar la última enésima aparición de un carácter en una cadena con el método lastOrdinalIndexOf .

Chexpir
fuente
3

Puede ser que pueda lograr esto a través del método String.split (..) también.

String str = "";
String[] tokens = str.split("/")
return tokens[nthIndex] == null 
Murali
fuente
2

Otro enfoque:

public static void main(String[] args) {
    String str = "/folder1/folder2/folder3/"; 
    int index = nthOccurrence(str, '/', 3);
    System.out.println(index);
}

public static int nthOccurrence(String s, char c, int occurrence) {
    return nthOccurrence(s, 0, c, 0, occurrence);
}

public static int nthOccurrence(String s, int from, char c, int curr, int expected) {
    final int index = s.indexOf(c, from);
    if(index == -1) return -1;
    return (curr + 1 == expected) ? index : 
        nthOccurrence(s, index + 1, c, curr + 1, expected);
}
Marimuthu Madasamy
fuente
2

Esta respuesta mejora la respuesta de @aioobe. Se solucionaron dos errores en esa respuesta.
1. n = 0 debería devolver -1.
2. La enésima aparición devolvió -1, pero funcionó en la n-1ª aparición.

Prueba esto !

    public int nthOccurrence(String str, char c, int n) {
    if(n <= 0){
        return -1;
    }
    int pos = str.indexOf(c, 0);
    while (n-- > 1 && pos != -1)
        pos = str.indexOf(c, pos+1);
    return pos;
}
ARCA
fuente
1
public class Sam_Stringnth {

    public static void main(String[] args) {
        String str="abcabcabc";
        int n = nthsearch(str, 'c', 3);
        if(n<=0)
            System.out.println("Character not found");
        else
            System.out.println("Position is:"+n);
    }
    public static int nthsearch(String str, char ch, int n){
        int pos=0;
        if(n!=0){
            for(int i=1; i<=n;i++){
                pos = str.indexOf(ch, pos)+1;
            }
            return pos;
        }
        else{
            return 0;
        }
    }
}
SAN
fuente
0
/* program to find nth occurence of a character */

import java.util.Scanner;

public class CharOccur1
{

    public static void main(String arg[])
    {
        Scanner scr=new Scanner(System.in);
        int position=-1,count=0;
        System.out.println("enter the string");
        String str=scr.nextLine();
        System.out.println("enter the nth occurence of the character");
        int n=Integer.parseInt(scr.next());
        int leng=str.length();
        char c[]=new char[leng];
        System.out.println("Enter the character to find");
        char key=scr.next().charAt(0);
        c=str.toCharArray();
        for(int i=0;i<c.length;i++)
        {
            if(c[i]==key)
            {
                count++;
                position=i;
                if(count==n)
                {
                    System.out.println("Character found");
                    System.out.println("the position at which the " + count + " ocurrence occurs is " + position);
                    return;
                }
            }
        }
        if(n>count)
        { 
            System.out.println("Character occurs  "+ count + " times");
            return;
        }
    }
}
Rosa
fuente
0

Mi solución:

/**
 * Like String.indexOf, but find the n:th occurance of c
 * @param s string to search
 * @param c character to search for
 * @param n n:th character to seach for, starting with 1
 * @return the position (0-based) of the found char, or -1 if failed
 */

public static int nthIndexOf(String s, char c, int n) {
    int i = -1;
    while (n-- > 0) {
        i = s.indexOf(c, i + 1);
        if (i == -1)
            break;
    }
    return i;
}
Per Lindberg
fuente
0

El código devuelve la subcadena de posiciones de ocurrencia n, también conocida como ancho de campo. Ejemplo. si la cadena "Stack overflow in low melow" es la cadena para buscar la segunda ocurrencia del token "low", estará de acuerdo conmigo en que la segunda ocurrencia está en la subcadena "18 y 21" . indexOfOccurance ("Stack overflow in low melow", low, 2) devuelve 18 y 21 en una cadena.

class Example{
    public Example(){
    }
            public String indexOfOccurance(String string, String token, int nthOccurance) {
                    int lengthOfToken = token.length();
                    int nthCount = 0;
                    for (int shift = 0,count = 0; count < string.length() - token.length() + 2; count++, shift++, lengthOfToken++)
                        if (string.substring(shift, lengthOfToken).equalsIgnoreCase(token)) { 
                    // keeps count of nthOccurance
                            nthCount++; 
                        if (nthCount == nthOccurance){
                    //checks if nthCount  == nthOccurance. If true, then breaks 
                             return String.valueOf(shift)+ " " +String.valueOf(lengthOfToken);   
                        }  
                    }
                    return "-1";
                }
    public static void main(String args[]){
    Example example = new Example();
    String string = "the man, the woman and the child";
    int nthPositionOfThe = 3;
   System.out.println("3rd Occurance of the is at " + example.indexOfOccurance(string, "the", nthPositionOfThe));
    }
    }

fuente
0
public static int findNthOccurrence(String phrase, String str, int n)
{
    int val = 0, loc = -1;
    for(int i = 0; i <= phrase.length()-str.length() && val < n; i++)
    {
        if(str.equals(phrase.substring(i,i+str.length())))
        {
            val++;
            loc = i;
        }
    }

    if(val == n)
        return loc;
    else
        return -1;
}
wess
fuente
2
Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación y probablemente resultaría en más votos a favor. Recuerde que está respondiendo la pregunta a los lectores en el futuro, no solo a la persona que pregunta ahora. Edite su respuesta para agregar explicaciones y dar una indicación de las limitaciones y suposiciones que se aplican.
Pika the Wizard of the Whales