Como encontrar a enésima ocorrência de caractere em uma string?

95

Semelhante a uma pergunta postada aqui , estou procurando uma solução em Java.

Ou seja, como encontrar o índice da enésima ocorrência de um caractere / string a partir de uma string?

Exemplo: " / pasta1 / pasta2 / pasta3 / ". Nesse caso, se eu solicitar a 3ª ocorrência da barra (/), ela aparecerá antes da pasta3 e espero retornar esta posição do índice. Minha intenção real é transformá-la em substring a partir da enésima ocorrência de um caractere.

Existe algum método conveniente / pronto para uso disponível na API Java ou precisamos escrever uma pequena lógica por conta própria para resolver isso?

Além disso,

  1. Eu pesquisei rapidamente se algum método é compatível com esse propósito no StringUtils do Apache Commons Lang , mas não encontrei nenhum.
  2. As expressões regulares podem ajudar nesse sentido?
Gnanam
fonte
2
Para o seu exemplo específico, dependendo do que você deseja fazer com o resultado, pode ser mais fácil dividir a string em /, o que pode lhe dar o que você precisa diretamente?
O Arquetípico Paulo de
@Paul: Essa também é uma boa ideia.
Gnanam de

Respostas:

128

Se o seu projeto já depende do Apache Commons, você pode usar StringUtils.ordinalIndexOf, caso contrário, aqui está uma implementação:

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 postagem foi reescrita como um artigo aqui .

aioobe
fonte
Além do erro "off-by-one", há outro grande ponto positivo na solução de @Jon Skeet - com um pequeno ajuste (invertendo o loop), você também pode ter a "enésima ocorrência da última".
Karan Chadha
@KaranChadha, o mesmo se aplica a esta solução. Basta mudar para lastIndexOf.
aioobe
60

Acredito que a solução mais fácil para encontrar a enésima ocorrência de uma String é usar StringUtils.ordinalIndexOf () do Apache Commons.

Exemplo:

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

Duas opções simples ocorrem:

  • Use charAt()repetidamente
  • Use indexOf()repetidamente

Por exemplo:

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

Isso pode não funcionar tão bem quanto usar indexOfrepetidamente, mas é possivelmente mais simples de acertar.

Jon Skeet
fonte
15

Você pode tentar algo assim:

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

Observe que fiz algumas suposições na regex:

  • o caminho de entrada é absoluto (ou seja, começa com "/");
  • você não precisa do terceiro "/" no resultado.

Conforme solicitado em um comentário, tentarei explicar a regex: (/[^/]*){2}/([^/]*)

Visualização de expressão regular

  • /[^/]*é um /seguido por [^/]*(qualquer número de caracteres que não sejam a /),
  • (/[^/]*)agrupa a expressão anterior em uma única entidade. Este é o primeiro 1grupo da expressão,
  • (/[^/]*){2}significa que o grupo deve corresponder exatamente {2}vezes,
  • [^/]*é novamente qualquer número de caracteres que não sejam um /,
  • ([^/]*)agrupa a expressão anterior em uma única entidade. Este é o segundo 2grupo da expressão.

Dessa forma, você só precisa obter a substring que corresponda ao segundo grupo: return m.group(2);

Imagem cortesia de Debuggex

andcoz
fonte
1
você poderia explicar o regex em inglês simples? Tipo: Uma barra invertida seguida por qualquer coisa que não seja uma barra invertida por um número indefinido de tempo ... Então não tenho certeza.
Ced,
1
@Ced, adicionei uma explicação e uma pequena correção para regex. Espero que esteja mais claro agora.
andcoz
Obrigado por explicar o regex.
Vishwa Ratna
8

Fiz algumas alterações na resposta do aioobe e obtive uma enésima versão lastIndexOf e consertei alguns problemas de NPE. Veja o código abaixo:

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;
}
Pateta
fonte
3
Acho que é razoável que o método lance um NPE se dado nullcomo argumento. Este é o comportamento mais comum na biblioteca padrão.
aioobe
5
 ([.^/]*/){2}[^/]*(/)

Combine qualquer coisa seguida por / duas vezes e, em seguida, novamente. O terceiro é o que você quer

O estado do Matcher pode ser usado para dizer onde o último / está

O Paulo Arquetípico
fonte
Tenho certeza de que é uma resposta muito legal, mas como faço para usar isso no meu código?
ARK
Veja a resposta de @ andcoz (regexp diferente, mas a ideia é a mesma)
The Archetypal 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
fonte
3

Hoje em dia EXISTE suporte para StringUtils do Apache Commons Lang ,

Este é o primitivo:

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

para o seu problema, você pode codificar o seguinte: StringUtils.ordinalIndexOf(uri, "/", 3)

Você também pode encontrar a última enésima ocorrência de um caractere em uma string com o método lastOrdinalIndexOf .

Chexpir
fonte
3

Pode ser que você possa fazer isso por meio do método String.split (..) também.

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

Outra abordagem:

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
fonte
2

Esta resposta melhora a resposta de @aioobe. Dois bugs nessa resposta foram corrigidos.
1. n = 0 deve retornar -1.
2. A enésima ocorrência retornou -1, mas funcionou na enésima ocorrência.

Experimente isso!

    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
fonte
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
fonte
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
fonte
0

Minha solução:

/**
 * 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
fonte
0

O código retorna a enésima ocorrência de posições de substring, também conhecida como largura do campo. Exemplo. se a string "Stack overflow in low melow" for a string para pesquisar a ocorrência do token "baixo", você concordará comigo que a 2ª ocorrência está na subtring "18 e 21" . indexOfOccurance ("Stack overflow in low melow", low, 2) retorna 18 e 21 em uma string.

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

fonte
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
fonte
2
Embora este código possa resolver a questão, incluir uma explicação de como e por que isso resolve o problema realmente ajudaria a melhorar a qualidade de sua postagem e provavelmente resultaria em mais votos positivos. Lembre-se de que você está respondendo às perguntas dos leitores no futuro, não apenas da pessoa que está perguntando agora. Edite sua resposta para adicionar explicações e dar uma indicação de quais limitações e suposições se aplicam.
Pika, o Mago das Baleias,