Conversión de MatchCollection en una matriz de cadenas

81

¿Hay una manera mejor que esta para convertir una MatchCollection en una matriz de cadenas?

MatchCollection mc = Regex.Matches(strText, @"\b[A-Za-z-']+\b");
string[] strArray = new string[mc.Count];
for (int i = 0; i < mc.Count;i++ )
{
    strArray[i] = mc[i].Groups[0].Value;
}

PD: mc.CopyTo(strArray,0)lanza una excepción:

Al menos un elemento de la matriz de origen no se pudo convertir en el tipo de matriz de destino.

Vil
fuente

Respuestas:

164

Tratar:

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .Cast<Match>()
    .Select(m => m.Value)
    .ToArray();
Dave Bish
fuente
1
Lo hubiera usado OfType<Match>()para esto en lugar de Cast<Match>()... Por otra parte, el resultado sería el mismo.
Alex
4
@Alex Sabes que todo lo devuelto será un Match, por lo que no es necesario volver a comprobarlo en tiempo de ejecución. Casttiene más sentido.
Servicio
2
@DaveBish Publiqué algún tipo de código de evaluación comparativa a continuación, OfType<>resulta ser un poco más rápido.
Alex
1
@Frontenderman - No, solo lo estaba alineando con la pregunta de los autores
Dave Bish
1
Pensaría que sería un comando simple convertir a MatchCollectionen a string[], ya que es para Match.ToString(). Es bastante obvio que el tipo final que se necesita en muchos Regexusos sería una cadena, por lo que debería haber sido fácil de convertir.
n00dles
31

La respuesta de Dave Bish es buena y funciona correctamente.

Vale la pena señalar, aunque reemplazar Cast<Match>()con OfType<Match>()acelerará las cosas.

El código se convertiría en:

var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
    .OfType<Match>()
    .Select(m => m.Groups[0].Value)
    .ToArray();

El resultado es exactamente el mismo (y aborda el problema de OP exactamente de la misma manera) pero para cadenas grandes es más rápido.

Código de prueba:

// put it in a console application
static void Test()
{
    Stopwatch sw = new Stopwatch();
    StringBuilder sb = new StringBuilder();
    string strText = "this will become a very long string after my code has done appending it to the stringbuilder ";

    Enumerable.Range(1, 100000).ToList().ForEach(i => sb.Append(strText));
    strText = sb.ToString();

    sw.Start();
    var arr = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
              .OfType<Match>()
              .Select(m => m.Groups[0].Value)
              .ToArray();
    sw.Stop();

    Console.WriteLine("OfType: " + sw.ElapsedMilliseconds.ToString());
    sw.Reset();

    sw.Start();
    var arr2 = Regex.Matches(strText, @"\b[A-Za-z-']+\b")
              .Cast<Match>()
              .Select(m => m.Groups[0].Value)
              .ToArray();
    sw.Stop();
    Console.WriteLine("Cast: " + sw.ElapsedMilliseconds.ToString());
}

El resultado es el siguiente:

OfType: 6540
Cast: 8743

Por lo tanto, para cadenas muy largas, Cast () es más lento.

Alex
fuente
1
¡Muy sorprendente! Dado que OfType debe hacer una comparación 'is' en algún lugar dentro y un elenco (¿habría pensado?) ¿Alguna idea sobre por qué Cast <> es más lento? No tengo nada!
Dave Bish
Honestamente, no tengo ni idea, pero "se siente" bien para mí (OfType <> es solo un filtro, Cast <> es ... bueno, es un elenco)
Alex
Más puntos de referencia parecen mostrar que este resultado en particular se debe a la expresión regular más que a la extensión específica de linq utilizada
Alex
6

Ejecuté exactamente el mismo punto de referencia que Alex ha publicado y descubrí que a veces Castera más rápido y a veces OfTypemás rápido, pero la diferencia entre ambos era insignificante. Sin embargo, aunque feo, el ciclo for es consistentemente más rápido que los otros dos.

Stopwatch sw = new Stopwatch();
StringBuilder sb = new StringBuilder();
string strText = "this will become a very long string after my code has done appending it to the stringbuilder ";
Enumerable.Range(1, 100000).ToList().ForEach(i => sb.Append(strText));
strText = sb.ToString();

//First two benchmarks

sw.Start();
MatchCollection mc = Regex.Matches(strText, @"\b[A-Za-z-']+\b");
var matches = new string[mc.Count];
for (int i = 0; i < matches.Length; i++)
{
    matches[i] = mc[i].ToString();
}
sw.Stop();

Resultados:

OfType: 3462
Cast: 3499
For: 2650
David DeMar
fuente
no es de extrañar que linq sea más lento que for loop. Linq puede ser más fácil de escribir para algunas personas y "aumentar" su productividad a expensas del tiempo de ejecución. eso puede ser bueno a veces
gg89
1
Así que la publicación original es realmente el método más eficiente.
n00dles
2

También se podría hacer uso de este método de extensión para lidiar con la molestia de MatchCollectionno ser genérico. No es que sea un gran problema, pero es casi seguro que esto sea más eficaz que OfTypeo Cast, porque es solo enumerar, lo que ambos también tienen que hacer.

(Nota al margen: me pregunto si sería posible que el equipo de .NET MatchCollectionheredara versiones genéricas de ICollectiony IEnumerableen el futuro. Entonces no necesitaríamos este paso adicional para tener disponibles transformaciones LINQ de inmediato).

public static IEnumerable<Match> ToEnumerable(this MatchCollection mc)
{
    if (mc != null) {
        foreach (Match m in mc)
            yield return m;
    }
}
Nicholas Petersen
fuente
0

Considere el siguiente código ...

var emailAddress = "[email protected]; [email protected]; [email protected]";
List<string> emails = new List<string>();
emails = Regex.Matches(emailAddress, @"([a-zA-Z0-9_\-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([a-zA-Z0-9\-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})")
                .Cast<Match>()
                .Select(m => m.Groups[0].Value)
                .ToList();
gpmurthy
fuente
1
ugh ... Esa expresión regular es horrible de ver. Por cierto, como no existe una expresión regular infalible para validar correos electrónicos, use el objeto MailAddress. stackoverflow.com/a/201378/2437521
C. Tewalt