2010-11-24 11 views
9

Escribí esta extensión de cadena un tiempo atrás, y en realidad estoy sacando bastante provecho de ella.¿Alguna forma de mejorar este método de corte de cadena?

public static string Slice(this string str, int? start = null, int? end = null, int step = 1) 
{ 
    if (step == 0) throw new ArgumentException("Step cannot be zero.", "step"); 

    if (start == null) 
    { 
     if (step > 0) start = 0; 
     else start = str.Length - 1; 
    } 
    else if (start < 0) 
    { 
     if (start < -str.Length) start = 0; 
     else start += str.Length; 
    } 
    else if (start > str.Length) start = str.Length; 

    if (end == null) 
    { 
     if (step > 0) end = str.Length; 
     else end = -1; 
    } 
    else if (end < 0) 
    { 
     if (end < -str.Length) end = 0; 
     else end += str.Length; 
    } 
    else if (end > str.Length) end = str.Length; 

    if (start == end || start < end && step < 0 || start > end && step > 0) return ""; 
    if (start < end && step == 1) return str.Substring((int)start, (int)(end - start)); 

    int length = (int)(((end - start)/(float)step) + 0.5f); 
    var sb = new StringBuilder(length); 
    for (int i = (int)start, j = 0; j < length; i += step, ++j) 
     sb.Append(str[i]); 
    return sb.ToString(); 
} 

Dado que ahora está en todos mis proyectos, me pregunto si podría haberlo hecho mejor. ¿Más eficiente, o produciría resultados inesperados en cualquier caso?


Rebanar. Funciona como la notación de matriz de Python.

"string"[start:end:step] 

Muchos otros idiomas tienen algo como esto también. string.Slice(1) es equivalente a string.Substring(1). string.Substring(1,-1) recorta el primer y el último carácter. string.Substring(null,null,-1) invertirá la cadena. string.Substring(step:2) devolverá una cadena con cualquier otro carácter ... también similar a JS's slice pero con un argumento adicional.


Re-revisada en función de sus sugerencias:

public static string Slice(this string str, int? start = null, int? end = null, int step = 1) 
{ 
    if (step == 0) throw new ArgumentException("Step size cannot be zero.", "step"); 

    if (start == null) start = step > 0 ? 0 : str.Length - 1; 
    else if (start < 0) start = start < -str.Length ? 0 : str.Length + start; 
    else if (start > str.Length) start = str.Length; 

    if (end == null) end = step > 0 ? str.Length : -1; 
    else if (end < 0) end = end < -str.Length ? 0 : str.Length + end; 
    else if (end > str.Length) end = str.Length; 

    if (start == end || start < end && step < 0 || start > end && step > 0) return ""; 
    if (start < end && step == 1) return str.Substring(start.Value, end.Value - start.Value); 

    var sb = new StringBuilder((int)Math.Ceiling((end - start).Value/(float)step)); 
    for (int i = start.Value; step > 0 && i < end || step < 0 && i > end; i += step) 
     sb.Append(str[i]); 
    return sb.ToString(); 
} 
+5

¿Qué se supone que haga? Sé que podría solucionarlo, pero me siento un poco flojo ... – ChrisF

+0

es esa subcadena? – Fredou

+0

¿Me intriga saber para qué lo usa? El paso es intrigante. Entiendo lo que hace, pero ¿cuál es la aplicación práctica? Solo interesado. –

Respuesta

2

Si usted tiene un montón de casos de prueba, a continuación, la detección de resultados inesperados no deberían ser un problema si desea experimentar con diferentes implementaciones.

Desde una perspectiva API, consideraría optional arguments en lugar de nulos.

actualización

Después de leer el código de cerca, puedo ver que la administración de "Inicio" y "fin" de un valor nulo, tiene un significado especial cuando se toma "paso" en consideración, por lo tanto, podrían no se representan como parámetros int opcionales solos, sin embargo, aún podrían ser parámetros opcionales.

Después de mirar el código más de cerca, es un poco de una API funky ya que los valores de los parámetros individuales tienen un efecto el uno sobre el otro. Mi comentario anterior alude a esto. Realmente debe conocer la implementación para resolver esto, generalmente no es un buen aspecto de la API. Y posiblemente sea una experiencia de lectura difícil.

Veo cómo "step" se puede utilizar para invertir una cadena, que es potencialmente útil. Pero, ¿no sería mejor un método de extensión inversa para esto? Mucho más legible y menos acelerado mental.

+0

¿Quieres decir que debería sobrecargar el método un montón de veces? Realmente no puedo hacer eso, ya que son todos enteros ... no sabrá cuál es cuál. Funciona mejor con .net 4 donde puedes simplemente 'string.Slice (end: -1)' para saltar los primeros 2 argumentos. – mpen

+0

@Mark No hay argumentos opcionales que sean una nueva característica de lenguaje C# 4.0. He actualizado mi respuesta con un enlace. –

+0

@chibacity: No entiendo. ¿Cómo quieres que los haga opcionales si no pueden ser nulos? Necesito darles un valor predeterminado. 0 es un valor legal, así que tengo que usar algo más. – mpen

1

puedo ver 3 cosas, muy muy pequeña de un

cambio el interior si en ternaria como

 if (start == null) 
     { 
      start = step > 0 ? 0 : str.Length - 1; 
     } 
     else if (start < 0) 
     { 
      start = start < -str.Length ? 0 : str.Length + start; 
     } 
     else if (start > str.Length) 
      start = str.Length; 

tal vez cambiar el (int) int? en int.Valor

cambio

var sb = new StringBuilder(length); 

en

StringBuilder sb = new StringBuilder(length); 

y la gran pregunta es, if it does what it need, why fixing it?


actualizará para mostrar cómo hacerlo con LINQ, forma más lenta (es hay una manera de acelerarlo?)

using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Text; 
    using System.Diagnostics; 

    namespace ConsoleApplication1 
    { 
     class Program 
     { 
      static void Main(string[] args) 
      { 
       Stopwatch sw; 
       string str; 

       sw = Stopwatch.StartNew(); 
       for (int i = 0; i < 1000000; i++) 
        str = "Step cannot be zero.".Slice(null, null, -3, true); 
       sw.Stop(); 
       Console.WriteLine("LINQ " + sw.Elapsed.TotalSeconds.ToString("0.#######") + " seconds"); 

       sw = Stopwatch.StartNew(); 
       for (int i = 0; i < 1000000; i++) 
        str = "Step cannot be zero.".Slice(null, null, -3, false); 
       sw.Stop(); 
       Console.WriteLine("MANUAL " + sw.Elapsed.TotalSeconds.ToString("0.#######") + " seconds"); 

       Console.ReadLine(); 
      } 
     } 

     static class test 
     { 
      public static string Slice(this string str, int? start, int? end, int step, bool linq) 
      { 
       if (step == 0) throw new ArgumentException("Step cannot be zero.", "step"); 

       if (linq) 
       { 

        if (start == null) start = 0; 
        else if (start > str.Length) start = str.Length; 

        if (end == null) end = str.Length; 
        else if (end > str.Length) end = str.Length; 

        if (step < 0) 
        { 
         str = new string(str.Reverse().ToArray()); 
         step = Math.Abs(step); 
        } 
       } 
       else 
       { 
        if (start == null) 
        { 
         if (step > 0) start = 0; 
         else start = str.Length - 1; 
        } 
        else if (start < 0) 
        { 
         if (start < -str.Length) start = 0; 
         else start += str.Length; 
        } 
        else if (start > str.Length) start = str.Length; 

        if (end == null) 
        { 
         if (step > 0) end = str.Length; 
         else end = -1; 
        } 
        else if (end < 0) 
        { 
         if (end < -str.Length) end = 0; 
         else end += str.Length; 
        } 
        else if (end > str.Length) end = str.Length; 


       } 

       if (start == end || start < end && step < 0 || start > end && step > 0) return ""; 
       if (start < end && step == 1) return str.Substring(start.Value, end.Value - start.Value); 

       if (linq) 
       { 
        return new string(str.Skip(start.Value).Take(end.Value - start.Value).Where((s, index) => index % step == 0).ToArray());; 
       } 
       else 
       { 
        int length = (int)(((end.Value - start.Value)/(float)step) + 0.5f); 
        var sb = new StringBuilder(length); 
        for (int i = start.Value, j = 0; j < length; i += step, ++j) 
         sb.Append(str[i]); 
        return sb.ToString(); 
       } 
      } 

     } 
    } 
+3

El cambio de 'var' a' StringBuilder' es puramente cosmético y una cuestión de elección personal o normas de codificación de la empresa. No tiene ningún efecto sobre la eficiencia (o no) del código. – ChrisF

+0

Sorprendido, no vi la oportunidad para un operador ternario allí ... ¡Por lo general soy todo eso! Gracias :) No sabía que podía hacer 'int? .Value'. – mpen

1

Cuando pregunto Python para "abcdefghijklmn"[::6] vuelve 'agm', pero cuando le pregunto su función para "abcdefghijklmn".Slice(step:6) vuelve "ag".

recomendaría la eliminación de la length cálculo incorrecto y simplemente realizar el bucle de esta manera:

var sb = new StringBuilder((end - start).Value/step); 
for (int i = start.Value; step > 0 && i < end || step < 0 && i > end; i += step) 
    sb.Append(str[i]); 
+0

Estaba preocupado de que todavía pudiera haber un error en ese bit. ¡¡Gracias!! – mpen

+0

Hrm ... 14/6 = 2.33. Se suponía que el +5 lo redondeaba a 3 ... ¿quizás un 'ceil' hubiera sido más apropiado? – mpen

Cuestiones relacionadas