.net - net - ¿La mejor manera de convertir IEnumerable<char> a cadena?
regex converter (6)
¿Por qué no es posible usar un lenguaje fluido en una string
?
Por ejemplo:
var x = "asdf1234";
var y = new string(x.TakeWhile(char.IsLetter).ToArray());
¿No hay una mejor manera de convertir IEnumerable<char>
a string
?
Aquí hay una prueba que hice:
class Program
{
static string input = "asdf1234";
static void Main()
{
Console.WriteLine("1000 times:");
RunTest(1000, input);
Console.WriteLine("10000 times:");
RunTest(10000,input);
Console.WriteLine("100000 times:");
RunTest(100000, input);
Console.WriteLine("100000 times:");
RunTest(100000, "ffff57467");
Console.ReadKey();
}
static void RunTest( int times, string input)
{
Stopwatch sw = new Stopwatch();
sw.Start();
for (int i = 0; i < times; i++)
{
string output = new string(input.TakeWhile(char.IsLetter).ToArray());
}
sw.Stop();
var first = sw.ElapsedTicks;
sw.Restart();
for (int i = 0; i < times; i++)
{
string output = Regex.Match(input, @"^[A-Z]+",
RegexOptions.IgnoreCase).Value;
}
sw.Stop();
var second = sw.ElapsedTicks;
var regex = new Regex(@"^[A-Z]+",
RegexOptions.IgnoreCase);
sw.Restart();
for (int i = 0; i < times; i++)
{
var output = regex.Match(input).Value;
}
sw.Stop();
var third = sw.ElapsedTicks;
double percent = (first + second + third) / 100;
double p1 = ( first / percent)/ 100;
double p2 = (second / percent )/100;
double p3 = (third / percent )/100;
Console.WriteLine("TakeWhile took {0} ({1:P2}).,", first, p1);
Console.WriteLine("Regex took {0}, ({1:P2})." , second,p2);
Console.WriteLine("Preinstantiated Regex took {0}, ({1:P2}).", third,p3);
Console.WriteLine();
}
}
Resultado:
1000 times:
TakeWhile took 11217 (62.32%).,
Regex took 5044, (28.02%).
Preinstantiated Regex took 1741, (9.67%).
10000 times:
TakeWhile took 9210 (14.78%).,
Regex took 32461, (52.10%).
Preinstantiated Regex took 20669, (33.18%).
100000 times:
TakeWhile took 74945 (13.10%).,
Regex took 324520, (56.70%).
Preinstantiated Regex took 172913, (30.21%).
100000 times:
TakeWhile took 74511 (13.77%).,
Regex took 297760, (55.03%).
Preinstantiated Regex took 168911, (31.22%).
Conclusión: estoy dudando de qué es mejor preferir, creo que voy a tomar el TakeWhile
que es el más lento solo en la primera carrera.
De todos modos, mi pregunta es si hay alguna forma de optimizar el rendimiento al TakeWhile
el resultado de la función TakeWhile
.
¿Por qué no es posible usar un lenguaje fluido en una cadena?
Es posible. Lo hiciste en la pregunta en sí:
var y = new string(x.TakeWhile(char.IsLetter).ToArray());
¿No hay una mejor manera de convertir
IEnumerable<char>
a string?
(Mi suposición es :)
El marco no tiene dicho constructor porque las cadenas son inmutables, y tendrías que atravesar la enumeración dos veces para preasignar la memoria para la cadena. Esto no siempre es una opción, especialmente si su entrada es una transmisión.
La única solución para esto es presionar primero a una matriz de respaldo o StringBuilder
, y reasignar a medida que crece la entrada. Para algo tan bajo como una cadena, esto probablemente debería considerarse un mecanismo demasiado oculto. También empujaría los problemas de perfusión a la clase de cuerda animando a las personas a utilizar un mecanismo que no puede ser tan rápido como sea posible.
Estos problemas se resuelven fácilmente requiriendo que el usuario use el método de extensión ToArray
.
Como han señalado otros, puede lograr lo que desea (código perf y expresivo) si escribe código de soporte y ajusta ese código de soporte en un método de extensión para obtener una interfaz limpia.
¿Qué tal esto para convertir IEnumerable<char>
a string
:
string.Concat(x.TakeWhile(char.IsLetter));
A menudo puede hacer mejor en cuanto a rendimiento. Pero, ¿qué te compra eso? A menos que este sea realmente el cuello de botella para su aplicación y lo haya medido como tal, me TakeWhile()
versión de Linq TakeWhile()
: es la solución más legible y mantenible, y eso es lo que cuenta para la mayoría de todas las aplicaciones.
Si realmente está buscando un rendimiento en bruto, podría hacer la conversión manualmente; lo siguiente fue alrededor de un factor 4+ (dependiendo de la longitud de la cadena de entrada) más rápido que TakeWhile()
en mis pruebas, pero no lo usaría personalmente a menos que fuera crítico:
int j = 0;
for (; j < input.Length; j++)
{
if (!char.IsLetter(input[j]))
break;
}
string output = input.Substring(0, j);
Suponiendo que está buscando principalmente el rendimiento, algo como esto debería ser sustancialmente más rápido que cualquiera de sus ejemplos:
string x = "asdf1234";
string y = x.LeadingLettersOnly();
// ...
public static class StringExtensions
{
public static string LeadingLettersOnly(this string source)
{
if (source == null)
throw new ArgumentNullException("source");
if (source.Length == 0)
return source;
char[] buffer = new char[source.Length];
int bufferIndex = 0;
for (int sourceIndex = 0; sourceIndex < source.Length; sourceIndex++)
{
char c = source[sourceIndex];
if (!char.IsLetter(c))
break;
buffer[bufferIndex++] = c;
}
return new string(buffer, 0, bufferIndex);
}
}
return new string (foo.Select (x => x) .ToArray ());
Editado para el lanzamiento de .Net Core 2.1
Repitiendo la prueba para el lanzamiento de .Net Core 2.1, obtengo resultados como este
1000000 iteraciones de "Concat" tomaron 842ms.
1000000 iteraciones de "nueva cadena" tomaron 1009ms.
1000000 iteraciones de "sb" tomaron 902ms.
En resumen, si está utilizando .Net Core 2.1 o posterior, Concat
es el rey.
Ver la publicación del blog de MS para más detalles.
He hecho de esto el tema de otra pregunta, pero cada vez más, eso se está convirtiendo en una respuesta directa a esta pregunta.
He hecho algunas pruebas de rendimiento de 3 métodos simples para convertir un IEnumerable<char>
en una string
, esos métodos son
nueva cadena
return new string(charSequence.ToArray());
Concat
return string.Concat(charSequence)
StringBuilder
var sb = new StringBuilder();
foreach (var c in charSequence)
{
sb.Append(c);
}
return sb.ToString();
En mi prueba, eso se detalla en la pregunta vinculada , para 1000000
iteraciones de "Some reasonably small test data"
, obtengo resultados como este.
1000000 iteraciones de "Concat" tomaron 1597ms.
1000000 iteraciones de "nueva cadena" tomaron 869ms.
1000000 iteraciones de "StringBuilder" tomaron 748ms.
Esto me sugiere que no hay una buena razón para usar string.Concat
para esta tarea. Si desea simplicidad, use el nuevo enfoque de cadenas y, si lo desea, utilice StringBuilder .
Me gustaría advertir mi afirmación, en la práctica todos estos métodos funcionan bien, y todo esto podría ser más de optimización.