web-dev-qa-db-fra.com

Nettoyer la ficelle? Y a-t-il une meilleure façon de le faire?

J'utilise cette méthode pour nettoyer la chaîne

public static string CleanString(string dirtyString)
{
    string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
    string result = dirtyString;

    foreach (char c in removeChars)
    {
        result = result.Replace(c.ToString(), string.Empty);
    }

    return result;
}

Cette méthode fonctionne bien .. MAIS il y a un problème de performance dans cette méthode. chaque fois que je passe la chaîne, chaque caractère passe en boucle, si j'ai une grande chaîne, cela prendrait trop de temps pour retourner l'objet.

Y a-t-il une autre meilleure façon de faire la même chose? comme dans LINQ ou JQUERY/Javascript

Toute suggestion serait appréciée.

20
patel.milanb

OK, considérons le test suivant:

public class CleanString
{
    //by MSDN http://msdn.Microsoft.com/en-us/library/844skk0h(v=vs.71).aspx
    public static string UseRegex(string strIn)
    {
        // Replace invalid characters with empty strings.
        return Regex.Replace(strIn, @"[^\w\.@-]", "");
    }

    // by Paolo Tedesco
    public static String UseStringBuilder(string strIn)
    {
        const string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
        // specify capacity of StringBuilder to avoid resizing
        StringBuilder sb = new StringBuilder(strIn.Length);
        foreach (char x in strIn.Where(c => !removeChars.Contains(c)))
        {
            sb.Append(x);
        }
        return sb.ToString();
    }

    // by Paolo Tedesco, but using a HashSet
    public static String UseStringBuilderWithHashSet(string strIn)
    {
        var hashSet = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
        // specify capacity of StringBuilder to avoid resizing
        StringBuilder sb = new StringBuilder(strIn.Length);
        foreach (char x in strIn.Where(c => !hashSet.Contains(c)))
        {
            sb.Append(x);
        }
        return sb.ToString();
    }

    // by SteveDog
    public static string UseStringBuilderWithHashSet2(string dirtyString)
    {
        HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
        StringBuilder result = new StringBuilder(dirtyString.Length);
        foreach (char c in dirtyString)
            if (removeChars.Contains(c))
                result.Append(c);
        return result.ToString();
    }

    // original by patel.milanb
    public static string UseReplace(string dirtyString)
    {
        string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
        string result = dirtyString;

        foreach (char c in removeChars)
        {
            result = result.Replace(c.ToString(), string.Empty);
        }

        return result;
    }

    // by L.B
    public static string UseWhere(string dirtyString)
    {
        return new String(dirtyString.Where(Char.IsLetterOrDigit).ToArray());
    }
}

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        var dirtyString = "sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf////fj()=/§(§&/(\"&sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf////fj()=/§(§&/(\"&sdfdf.dsf8908()=(=(sadfJJLef@ssyd€sdöf";
        var sw = new Stopwatch();

        var iterations = 50000;

        sw.Start();
        for (var i = 0; i < iterations; i++)
            CleanString.<SomeMethod>(dirtyString);
        sw.Stop();
        Debug.WriteLine("CleanString.<SomeMethod>: " + sw.ElapsedMilliseconds.ToString());
        sw.Reset();

        ....
        <repeat>
        ....       
    }
}

Sortie

CleanString.UseReplace: 791
CleanString.UseStringBuilder: 2805
CleanString.UseStringBuilderWithHashSet: 521
CleanString.UseStringBuilderWithHashSet2: 331
CleanString.UseRegex: 1700
CleanString.UseWhere: 233

Conclusion

Peu importe quelle méthode vous utilisez.

La différence de temps entre la méthode à jeun (UseWhere: 233ms) et la plus lente (UseStringBuilder: 2805ms) est de 2572ms lorsqu'elle est appelée 50000 (!) Fois de suite. Vous ne devriez probablement pas avoir besoin de vous en soucier si vous n'exécutez pas la méthode aussi souvent. 

Mais si vous le faites, utilisez la méthode UseWhere (écrite par L.B); mais notez également que c'est légèrement différent.

35
sloth

Si vous recherchez uniquement la vitesse et l'efficacité, je vous recommanderais de procéder de la manière suivante:

public static string CleanString(string dirtyString)
{
    HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");
    StringBuilder result = new StringBuilder(dirtyString.Length);
    foreach (char c in dirtyString)
        if (!removeChars.Contains(c)) // prevent dirty chars
            result.Append(c);
    return result.ToString();
}

RegEx est certes une solution élégante, mais elle ajoute des frais généraux supplémentaires. En spécifiant la longueur de départ du constructeur de chaînes, il ne sera nécessaire d'allouer la mémoire qu'une seule fois (et une seconde fois pour la variable ToString à la fin). Cela réduira l'utilisation de la mémoire et augmentera la vitesse, en particulier sur les chaînes plus longues.

Cependant, comme L.B. Si vous utilisez ceci pour coder correctement le texte lié à la sortie HTML, vous devriez utiliser HttpUtility.HtmlEncode au lieu de le faire vous-même.

5
Steven Doggart

utilisez regex [?&^$#@!()+-,:;<>’\'-_*] pour le remplacer par une chaîne vide

3
burning_LEGION

Je ne sais pas si, du point de vue des performances, utiliser Regex ou LINQ constituerait une amélioration.
Ce qui pourrait être utile serait de créer la nouvelle chaîne avec une StringBuilder au lieu d'utiliser string.Replace à chaque fois:

using System.Linq;
using System.Text;

static class Program {
    static void Main(string[] args) {
        const string removeChars = " ?&^$#@!()+-,:;<>’\'-_*";
        string result = "x&y(z)";
        // specify capacity of StringBuilder to avoid resizing
        StringBuilder sb = new StringBuilder(result.Length);
        foreach (char x in result.Where(c => !removeChars.Contains(c))) {
            sb.Append(x);
        }
        result = sb.ToString();
    }
}
2
Paolo Tedesco
1
Stuart.Sklinar

Peut-être qu'il serait utile d'expliquer d'abord le «pourquoi», puis le «quoi». Les performances ralentissent parce que c # copie et remplace les chaînes pour chaque remplacement. D'après mon expérience en utilisant Regex dans .NET, ce n'est pas toujours mieux - bien que dans la plupart des scénarios (je pense y compris celui-ci), cela fonctionnera probablement très bien.

Si j'ai vraiment besoin de performances, je ne laisse généralement pas de chance, je dis simplement au compilateur ce que je veux, c'est-à-dire: créez une chaîne avec le nombre de caractères supérieur et copiez tous les caractères dont vous avez besoin. Il est également possible de remplacer le hashset par un switch/case ou un tableau, auquel cas vous pourriez vous retrouver avec une table de saut ou une recherche de tableau - ce qui est encore plus rapide. 

La meilleure solution "pragmatique", mais rapide, est la suivante:

char[] data = new char[dirtyString.Length];
int ptr = 0;
HashSet<char> hs = new HashSet<char>() { /* all your excluded chars go here */ };
foreach (char c in dirtyString)
    if (!hs.Contains(c))
        data[ptr++] = c;
return new string(data, 0, ptr);

BTW: cette solution est incorrecte lorsque vous souhaitez traiter des caractères Unicode de substitution élevés, mais vous pouvez facilement l'adapter pour inclure ces caractères.

-Stefan.

1
atlaste

Celui-ci est encore plus rapide!
utilisation: 

string dirty=@"tfgtf$@$%gttg%$% 664%$";
string clean = dirty.Clean();


    public static string Clean(this String name)
    {
        var namearray = new Char[name.Length];

        var newIndex = 0;
        for (var index = 0; index < namearray.Length; index++)
        {
            var letter = (Int32)name[index];

            if (!((letter > 96 && letter < 123) || (letter > 64 && letter < 91) || (letter > 47 && letter < 58)))
                continue;

            namearray[newIndex] = (Char)letter;
            ++newIndex;
        }

        return new String(namearray).TrimEnd();
    }
1
gd73

Je ne suis pas en mesure de passer du temps à tester cela à l'acide, mais cette ligne n'a pas réellement nettoyé les barres comme souhaité 

HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’\'-_*");

J'ai dû ajouter des barres obliques individuellement et échapper à la barre oblique inverse

HashSet<char> removeChars = new HashSet<char>(" ?&^$#@!()+-,:;<>’'-_*");
removeChars.Add('/');
removeChars.Add('\\');
0
user2623295