2010-11-24 3 views
9

J'ai écrit cette extension de chaîne il y a quelque temps, et je commence à en tirer un peu parti.Une façon d'améliorer cette méthode de tranche de chaîne?

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

Comme c'est dans tous mes projets maintenant, je me demande si j'aurais pu faire mieux. Plus efficace, ou produirait-il des résultats inattendus dans tous les cas?


Tranche. Cela fonctionne comme la notation de tableau de Python.

"string"[start:end:step] 

De nombreuses autres langues ont quelque chose comme ça aussi. string.Slice(1) est équivalent à string.Substring(1). string.Substring(1,-1) enlève le premier et le dernier caractère. string.Substring(null,null,-1) va inverser la chaîne. string.Substring(step:2) retournera une chaîne avec tous les autres caractères ... également similaire à JS's slice mais avec un argument supplémentaire.


Re révisé en fonction de vos suggestions:

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'est-ce qu'il censé faire? Je sais que je pourrais y arriver, mais je me sens un peu paresseux ... – ChrisF

+0

est-ce la sous-chaîne !? – Fredou

+0

Je suis intrigué de savoir à quoi vous l'utilisez? Le bit étape est intrigant. Je comprends ce qu'il fait, mais quelle est l'application pratique. Juste intéressé. –

Répondre

2

Si vous avez beaucoup de cas de test, puis la détection des résultats inattendus ne devraient pas être un problème si vous souhaitez expérimenter avec des implémentations différentes. Du point de vue de l'API, je considérerais optional arguments plutôt que des valeurs nulles.

Mise à jour

Après avoir lu le code près, je peux voir que donner « start » et « end » une valeur nulle, a une signification particulière lors de la prise « pas » en considération, par conséquent, ils pourraient ne sont pas représentés comme des paramètres int optionnels, mais ils peuvent toujours être des paramètres facultatifs. Après avoir examiné le code de plus près, c'est une API un peu funky, car les valeurs des paramètres individuels ont un effet l'un sur l'autre. Mon commentaire précédent fait allusion à cela. Vous devez vraiment connaître la mise en œuvre pour résoudre ce problème, pas généralement un bon aspect de l'API. Et peut-être fait pour une expérience de lisibilité difficile.

Je peux voir comment "étape" peut être utilisée pour inverser une chaîne, ce qui est potentiellement utile. Mais une méthode d'extension inverse ne serait-elle pas meilleure pour cela? Beaucoup plus lisible et moins d'une speedbump mentale.

+0

Vous voulez dire que je devrais surcharger la méthode un tas de fois alors? Je ne peux pas vraiment le faire car ils sont tous ints ... il ne saura pas lequel est quoi. Fonctionne mieux avec .net 4 où vous pouvez simplement aller 'string.Slice (end: -1)' pour passer les 2 premiers arguments. – mpen

+0

@Mark Aucun argument facultatif n'est une nouvelle fonctionnalité de langage C# 4.0. J'ai mis à jour ma réponse avec un lien. –

+0

@chibacity: Je ne comprends pas. Comment voulez-vous que je les rends facultatifs s'ils ne peuvent pas être nuls? Je dois leur donner une valeur par défaut. 0 est une valeur légale, donc je dois utiliser autre chose. – mpen

1

Je peux voir 3 choses, très vraiment mineur

changer l'intérieur si dans ternaire comme

 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; 

peut-être changer le (int) int? en int.Valeur

changement

var sb = new StringBuilder(length); 

dans

StringBuilder sb = new StringBuilder(length); 

et la grande question est, if it does what it need, why fixing it?


mise à jour pour montrer comment le faire avec LINQ, façon plus lente (est y at-il un moyen de l'accélérer?)

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

Le passage de 'var' à' StringBuilder' est purement cosmétique et relève d'un choix personnel ou de normes de codage de l'entreprise. Cela n'a aucun effet sur l'efficacité (ou autre) du code. – ChrisF

+0

Surpris je n'ai pas vu l'opportunité d'un opérateur ternaire là-bas ... Je suis d'habitude partout dessus! Merci :) Je ne savais pas que je pouvais faire 'int? .Value'. – mpen

1

Quand je demande à Python pour "abcdefghijklmn"[::6] il retourne 'agm', mais quand je demande à votre fonction pour "abcdefghijklmn".Slice(step:6) il retourne "ag".

je recommande la suppression du calcul incorrect length et juste effectuer votre boucle comme ceci:

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

Était inquiet il pourrait y avoir encore un bug dans ce bit. Je vous remercie!! – mpen

+0

Hrm ... 14/6 = 2.33. Le +5 était censé l'arrondir à 3 ... peut-être qu'un 'ceil' aurait été plus approprié? – mpen

Questions connexes