2008-10-09 31 views
94

A menos que me falta un método incorporado obvio, ¿cuál es la forma más rápida de obtener n la aparición de una cadena dentro de una cadena?¿Obtiene el índice de la enésima aparición de una cadena?

Me doy cuenta de que podría recorrer el método IndexOf al actualizar su índice de inicio en cada iteración del ciclo. Pero hacerlo de esta manera me parece un desperdicio.

+0

Similar: http://stackoverflow.com/a/9908392/1305911 – JNF

+0

Utilizaría expresiones regulares para eso, entonces tiene que optimizar forma de hacer coincidir la cadena dentro de la cadena. Esto en una de las hermosas DSL que todos deberíamos usar cuando sea posible. [Un ejemplo] (http://www.regular-expressions.info/dotnet.html "Link") en VB.net el código es casi el mismo en C#. – bovium

+2

Me gustaría poner un buen dinero en la versión de expresiones regulares que es mucho más difícil de obtener a la derecha que "seguir haciendo bucles y haciendo simple String.IndexOf". Las expresiones regulares tienen su lugar, pero no deberían usarse cuando existen alternativas más simples. –

Respuesta

51

Eso es básicamente lo que necesita hacer, o al menos, es la solución más fácil. Lo único que "malgastaría" es el costo de n invocaciones de métodos: en realidad no revisará ningún caso dos veces, si lo piensa. (IndexOf volverá tan pronto como encuentre la coincidencia, y continuará desde donde se quedó.)

+2

Supongo que tiene razón, parece que debería haber un método integrado, estoy seguro de que es una ocurrencia común. – PeteT

+4

¿De verdad? No recuerdo haber tenido que hacerlo en aproximadamente 13 años de desarrollo de Java y C#. Eso no significa que realmente nunca tuve que hacerlo, pero no lo suficiente como para recordarlo. –

+0

Hablando de Java, tenemos 'StringUtils.ordinalIndexOf()'. C# con todo el Linq y otras características maravillosas, simplemente no tiene un soporte incorporado para esto. Y sí, es muy imperativo contar con su apoyo si se trata de analizadores y tokenizadores. – Annie

99

Podría utilizar realmente la expresión regular /((s).*?){n}/ para buscar n-ésima aparición de la subcadena s.

En C# que podría tener este aspecto:

public static class StringExtender 
{ 
    public static int NthIndexOf(this string target, string value, int n) 
    { 
     Match m = Regex.Match(target, "((" + Regex.Escape(value) + ").*?){" + n + "}"); 

     if (m.Success) 
      return m.Groups[2].Captures[n - 1].Index; 
     else 
      return -1; 
    } 
} 

Nota: he añadido a Regex.Escape solución original para permitir buscar caracteres que tienen un significado especial para motor de expresiones regulares.

+2

¿debería estar escapando del' valor'? En mi caso yo estaba buscando un punto http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.escape.aspx – russau

+1

Esta expresión regular no funciona si la cadena de destino contiene saltos de línea. ¿Podrías arreglarlo? Gracias. –

+0

Parece que se bloquea si no hay una enésima coincidencia. Necesitaba limitar un valor de coma separado a 1000 valores, y esto se colgó cuando el csv tenía menos. Así que @Yogesh - probablemente no sea una gran respuesta aceptada como es. ;) Usar una variante de [esta respuesta] (http://stackoverflow.com/a/6004505/1028230) (hay una cadena para la versión de cadena [aquí] (http://stackoverflow.com/a/11773674/1028230)) y [cambió el ciclo para detenerse en el n-ésimo recuento] (http://pastebin.com/w6aPDn3x). – ruffin

14
private int IndexOfOccurence(string s, string match, int occurence) 
{ 
    int i = 1; 
    int index = 0; 

    while (i <= occurence && (index = s.IndexOf(match, index + 1)) != -1) 
    { 
     if (i == occurence) 
      return index; 

     i++; 
    } 

    return -1; 
} 

o en C# con los métodos de extensión

public static int IndexOfOccurence(this string s, string match, int occurence) 
{ 
    int i = 1; 
    int index = 0; 

    while (i <= occurence && (index = s.IndexOf(match, index + 1)) != -1) 
    { 
     if (i == occurence) 
      return index; 

     i++; 
    } 

    return -1; 
} 
+4

Si no me equivoco, este método falla si la cadena para unir empieza en la posición 0, que se puede corregir estableciendo 'index' inicialmente en -1. –

+1

Es posible que también desee comprobar cadenas nulas o vacías y coincidir o se lanzará, pero esa es una decisión de diseño. Gracias –

+0

@PeterMajeed - Si es ' "Bob" .IndexOf ("B")' devuelve 0, por lo que deberían para esta función 'IndexOfOccurence ("Bob", "B", 1)' – PeterX

16

That's basically what you need to do - or at least, it's the easiest solution. All you'd be "wasting" is the cost of n method invocations - you won't actually be checking any case twice, if you think about it. (IndexOf will return as soon as it finds the match, and you'll keep going from where it left off.)

Aquí está la implementación recursiva (de lo anterior idea) como un método de extensión, imitando el formato del método del marco (s):

public static int IndexOfNth(this string input, 
          string value, int startIndex, int nth) 
{ 
    if (nth < 1) 
     throw new NotSupportedException("Param 'nth' must be greater than 0!"); 
    if (nth == 1) 
     return input.IndexOf(value, startIndex); 
    var idx = input.IndexOf(value, startIndex); 
    if (idx == -1) 
     return -1; 
    return input.IndexOfNth(value, idx + 1, --nth); 
} 

Además, he aquí algunas pruebas de unidad (MBUnit) que t ayuda que (para demostrar que es correcta):

using System; 
using MbUnit.Framework; 

namespace IndexOfNthTest 
{ 
    [TestFixture] 
    public class Tests 
    { 
     //has 4 instances of the 
     private const string Input = "TestTest"; 
     private const string Token = "Test"; 

     /* Test for 0th index */ 

     [Test] 
     public void TestZero() 
     { 
      Assert.Throws<NotSupportedException>(
       () => Input.IndexOfNth(Token, 0, 0)); 
     } 

     /* Test the two standard cases (1st and 2nd) */ 

     [Test] 
     public void TestFirst() 
     { 
      Assert.AreEqual(0, Input.IndexOfNth("Test", 0, 1)); 
     } 

     [Test] 
     public void TestSecond() 
     { 
      Assert.AreEqual(4, Input.IndexOfNth("Test", 0, 2)); 
     } 

     /* Test the 'out of bounds' case */ 

     [Test] 
     public void TestThird() 
     { 
      Assert.AreEqual(-1, Input.IndexOfNth("Test", 0, 3)); 
     } 

     /* Test the offset case (in and out of bounds) */ 

     [Test] 
     public void TestFirstWithOneOffset() 
     { 
      Assert.AreEqual(4, Input.IndexOfNth("Test", 4, 1)); 
     } 

     [Test] 
     public void TestFirstWithTwoOffsets() 
     { 
      Assert.AreEqual(-1, Input.IndexOfNth("Test", 8, 1)); 
     } 
    } 
} 
+0

He actualizado mis formatos y casos de prueba basados ​​en los excelentes comentarios de Weston (gracias Weston). –

-3

Esto podría hacerlo:

Console.WriteLine(str.IndexOf((@"\")+2)+1); 
+2

No veo cómo funcionaría esto. ¿Podría incluir una breve explicación de lo que hace? –

1

Tal vez también sería agradable trabajar con el Método String.Split() y comprobar si la ocurrencia solicitado está en la matriz, si no necesita el índice, pero el valor en el índice

Cuestiones relacionadas