Najbardziej wydajny sposób usuwania znaków specjalnych z łańcucha


266

Chcę usunąć wszystkie znaki specjalne z ciągu. Dozwolone znaki to AZ (wielkie lub małe), cyfry (0–9), podkreślenie (_) lub znak kropki (.).

Mam następujące, działa, ale podejrzewam (wiem!), Że to nie jest bardzo wydajne:

    public static string RemoveSpecialCharacters(string str)
    {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < str.Length; i++)
        {
            if ((str[i] >= '0' && str[i] <= '9')
                || (str[i] >= 'A' && str[i] <= 'z'
                    || (str[i] == '.' || str[i] == '_')))
                {
                    sb.Append(str[i]);
                }
        }

        return sb.ToString();
    }

Jaki jest najbardziej efektywny sposób to zrobić? Jak wyglądałoby wyrażenie regularne i jak się ma do normalnej manipulacji ciągiem?

Ciągi, które będą czyszczone, będą raczej krótkie, zwykle o długości od 10 do 30 znaków.


5
Nie podam tego w odpowiedzi, ponieważ nie będzie bardziej wydajne, ale istnieje wiele statycznych metod char, takich jak char.IsLetterOrDigit (), których można użyć w instrukcji if, aby uczynić ją przynajmniej bardziej czytelną.
Martin Harris

5
Nie jestem pewien, czy sprawdzanie od A do z jest bezpieczne, ponieważ wprowadza 6 znaków, które nie są alfabetyczne, tylko jeden z nich jest pożądany (pod kreską).
Steven Sudit

4
Skoncentruj się na zwiększeniu czytelności kodu. chyba że robisz to w pętli jak 500 razy na sekundę, wydajność nie jest wielkim problemem. Użyj wyrażenia regularnego, a będzie o wiele łatwiejsze do odczytania. L
Byron Whitlock,

4
Byron, prawdopodobnie masz rację, że musisz podkreślać czytelność. Jestem jednak sceptycznie nastawiony do tego, aby wyrażenie regularne było czytelne. :-)
Steven Sudit

2
Wyrażenia regularne, które można odczytać lub nie, są w pewnym sensie podobne do tego, że niemiecki jest czytelny lub nie; zależy to od tego, czy wiesz, czy nie (choć w obu przypadkach od czasu do czasu natrafisz na reguły gramatyczne, które nie mają sensu;)
Blixt,

Odpowiedzi:


325

Dlaczego uważasz, że twoja metoda nie jest wydajna? To w rzeczywistości jeden z najbardziej wydajnych sposobów, w jaki możesz to zrobić.

Oczywiście powinieneś odczytać znak do zmiennej lokalnej lub użyć modułu wyliczającego, aby zmniejszyć liczbę dostępów do tablicy:

public static string RemoveSpecialCharacters(this string str) {
   StringBuilder sb = new StringBuilder();
   foreach (char c in str) {
      if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '.' || c == '_') {
         sb.Append(c);
      }
   }
   return sb.ToString();
}

Jedną z rzeczy, która sprawia, że ​​taka metoda jest skuteczna, jest jej dobre skalowanie. Czas wykonania będzie zależny od długości łańcucha. Nie ma nieprzyjemnych niespodzianek, jeśli użyjesz go na dużym sznurku.

Edytować:
Zrobiłem szybki test wydajności, uruchamiając każdą funkcję milion razy z ciągiem 24 znaków. Oto wyniki:

Oryginalna funkcja: 54,5 ms.
Moja sugerowana zmiana: 47,1 ms.
Kopalnia z ustawieniem Pojemność StringBuilder: 43,3 ms.
Wyrażenie regularne: 294,4 ms.

Edycja 2: Dodałem rozróżnienie między AZ i az w powyższym kodzie. (Zmieniłem test wydajności i nie ma zauważalnej różnicy).

Edycja 3:
Przetestowałem rozwiązanie lookup + char [] i działa ono w około 13 ms.

Cena do zapłaty to oczywiście inicjalizacja ogromnej tabeli odnośników i zachowanie jej w pamięci. Cóż, to nie jest tak dużo danych, ale dużo za tak banalną funkcję ...

private static bool[] _lookup;

static Program() {
   _lookup = new bool[65536];
   for (char c = '0'; c <= '9'; c++) _lookup[c] = true;
   for (char c = 'A'; c <= 'Z'; c++) _lookup[c] = true;
   for (char c = 'a'; c <= 'z'; c++) _lookup[c] = true;
   _lookup['.'] = true;
   _lookup['_'] = true;
}

public static string RemoveSpecialCharacters(string str) {
   char[] buffer = new char[str.Length];
   int index = 0;
   foreach (char c in str) {
      if (_lookup[c]) {
         buffer[index] = c;
         index++;
      }
   }
   return new string(buffer, 0, index);
}

4
Zgadzam się. Jedyną inną zmianą, jaką chciałbym wprowadzić, jest dodanie argumentu początkowej pojemności do konstruktora StringBuilder, „= new StringBuilder (str.Length)”.
David

2
Moja odpowiedź, używając char[]bufora zamiast StringBuilder, ma niewielką przewagę na tej, zgodnie z moimi testami. (Mój jest jednak mniej czytelny, więc niewielka korzyść z wydajności prawdopodobnie nie jest tego warta.)
LukeH

1
@Steven: Może tak być, ale standardy mówią same za siebie! W moich testach użycie char[]bufora działa (nieco) lepiej niż StringBuilder, nawet przy skalowaniu do ciągów o długości dziesiątek tysięcy znaków.
Łukasza

10
@downvoter: Dlaczego głosowanie negatywne? Jeśli nie wyjaśnisz, co uważasz za złe, nie może poprawić odpowiedzi.
Guffa,

2
@SILENT: Nie, nie robi, ale powinieneś to zrobić tylko raz. Jeśli przydzielisz tablicę tak dużą za każdym razem, gdy wywołujesz metodę (i jeśli często ją wywołujesz), metoda staje się zdecydowanie najwolniejsza i powoduje wiele pracy dla śmieciarza.
Guffa

195

Cóż, chyba że naprawdę musisz wycisnąć wydajność z funkcji, po prostu wybierz to, co najłatwiejsze do utrzymania i zrozumienia. Wyrażenie regularne wyglądałoby tak:

Aby zwiększyć wydajność, możesz albo go wstępnie skompilować, albo po prostu poprosić o kompilację przy pierwszym wywołaniu (kolejne połączenia będą szybsze).

public static string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_.]+", "", RegexOptions.Compiled);
}

1
Sądzę, że jest to prawdopodobnie dość złożone zapytanie, które byłoby szybsze niż podejście PO, zwłaszcza jeśli zostało wcześniej skompilowane. Nie mam jednak dowodów na poparcie tego. To powinno być przetestowane. O ile nie jest to zdecydowanie wolniejsze, wybrałbym to podejście, ponieważ jest łatwiejsze do odczytania i utrzymania. +1
rmeador

6
Jest to bardzo prosty regex (bez cofania się lub żadnych skomplikowanych rzeczy), więc powinien być cholernie szybki.

9
@rmeador: bez kompilacji jest około 5 razy wolniejszy, skompilowany jest 3 razy wolniejszy niż jego metoda. Wciąż 10 razy prostsze :-D
użytkownik7116

6
Wyrażenia regularne nie są magicznymi młotami i nigdy nie są szybsze niż kod zoptymalizowany ręcznie.
Christian Klauser,

2
Dla tych, którzy pamiętają słynny cytat Knutha na temat optymalizacji, od tego warto zacząć. Następnie, jeśli uznasz, że potrzebujesz dodatkowej tysięcznej wydajności milisekundy, skorzystaj z jednej z pozostałych technik.
John

15

Sugeruję utworzenie prostej tabeli odnośników, którą można zainicjować w konstruktorze statycznym, aby ustawić prawidłową kombinację znaków. Umożliwia to szybką, pojedynczą kontrolę.

edytować

Ponadto, dla szybkości, będziesz chciał zainicjować pojemność swojego StringBuilder do długości łańcucha wejściowego. Pozwoli to uniknąć realokacji. Te dwie metody razem zapewnią zarówno szybkość, jak i elastyczność.

kolejna edycja

Myślę, że kompilator może go zoptymalizować, ale ze względu na styl, a także wydajność, polecam foreach zamiast for.


Dla tablic fori foreachtworzenia podobnego kodu. Nie znam jednak ciągów znaków. Wątpię, czy JIT wie o podobnej do tablicy naturze String.
Christian Klauser,

1
Założę się, że JIT wie więcej o macierzowej naturze łańcucha niż [twój żart usunięty]. Anders etal wykonał wiele pracy, optymalizując wszystko na temat ciągów znaków w .net

Zrobiłem to za pomocą HashSet <char> i jest około 2x wolniejszy niż jego metoda. Korzystanie z bool [] jest niewiele szybsze (0,0469ms / iter w. 0,055ms / iter) niż wersja, którą ma w OP ... z tym, że jest mniej czytelny.
user7116

1
Nie widziałem żadnej różnicy w wydajności między używaniem tablicy bool i int. Użyłbym tablicy bool, ponieważ zmniejsza tabelę wyszukiwania z 256 kb do 64 kb, ale wciąż jest dużo danych dla tak trywialnej funkcji ... I to tylko około 30% szybciej.
Guffa,

1
@Guffa 2) Biorąc pod uwagę, że przechowujemy tylko znaki alfanumeryczne i kilka podstawowych znaków łacińskich, potrzebujemy tylko tabeli dla małego bajtu, więc rozmiar nie jest tak naprawdę problemem. Gdybyśmy chcieli mieć uniwersalne zastosowanie, wówczas standardową techniką Unicode jest podwójna pośrednia. Innymi słowy, tabela zawierająca 256 odniesień do tabeli, z których wiele wskazuje na tę samą pustą tabelę.
Steven Sudit

12
public static string RemoveSpecialCharacters(string str)
{
    char[] buffer = new char[str.Length];
    int idx = 0;

    foreach (char c in str)
    {
        if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')
            || (c >= 'a' && c <= 'z') || (c == '.') || (c == '_'))
        {
            buffer[idx] = c;
            idx++;
        }
    }

    return new string(buffer, 0, idx);
}

1
+1, przetestowane i jest o około 40% szybsze niż StringBuilder. 0.0294ms / string v. 0.0399ms / string
user7116

Dla pewności, masz na myśli StringBuilder z wstępnym przydziałem czy bez?
Steven Sudit

Przy wstępnej alokacji jest ona nadal o 40% wolniejsza niż alokacja char [] i nowy ciąg.
user7116

2
Lubię to. Ulepszyłem tę metodęforeach (char c in input.Where(c => char.IsLetterOrDigit(c) || allowedSpecialCharacters.Any(x => x == c))) buffer[idx++] = c;
Chris Marisic,

11

Wyrażenie regularne będzie wyglądać następująco:

public string RemoveSpecialChars(string input)
{
    return Regex.Replace(input, @"[^0-9a-zA-Z\._]", string.Empty);
}

Ale jeśli wydajność jest bardzo ważna, zalecamy wykonanie testów porównawczych przed wybraniem „ścieżki wyrażeń regularnych” ...


11

Jeśli używasz dynamicznej listy znaków, LINQ może zaoferować znacznie szybsze i pełne wdzięku rozwiązanie:

public static string RemoveSpecialCharacters(string value, char[] specialCharacters)
{
    return new String(value.Except(specialCharacters).ToArray());
}

Porównałem to podejście z dwoma poprzednimi „szybkimi” podejściami (kompilacja wydania):

  • Rozwiązanie tablicy Char według Łukasza - 427 ms
  • Rozwiązanie StringBuilder - 429 ms
  • LINQ (ta odpowiedź) - 98 ms

Zauważ, że algorytm jest nieco zmodyfikowany - znaki są przekazywane jako tablica, a nie na stałe, co może mieć niewielki wpływ na rzeczy (tj. / Inne rozwiązania miałyby wewnętrzną pętlę Foor do sprawdzania tablicy znaków).

Jeśli przejdę na rozwiązanie zakodowane na stałe za pomocą klauzuli LINQ where, wyniki są następujące:

  • Rozwiązanie tablicy Char - 7ms
  • Rozwiązanie StringBuilder - 22ms
  • LINQ - 60 ms

Być może warto przyjrzeć się LINQ lub zmodyfikowanemu podejściu, jeśli planujesz napisać bardziej ogólne rozwiązanie, niż na sztywno kodować listę znaków. LINQ zdecydowanie zapewnia zwięzły, czytelny kod - nawet bardziej niż Regex.


3
To podejście wygląda ładnie, ale nie działa - Z wyjątkiem () jest ustawioną operacją, więc skończysz tylko pierwszym pojawieniem się każdego unikalnego znaku w ciągu.
McKenzieG1

5

Nie jestem przekonany, że twój algorytm jest skuteczny. To O (n) i patrzy na każdą postać tylko raz. Nie uzyskasz nic lepszego, chyba że magicznie poznasz wartości przed ich sprawdzeniem.

Zainicjowałbym jednak pojemność twojego StringBuilderrozmiaru początkowego. Zgaduję, że twój postrzegany problem z wydajnością wynika z realokacji pamięci.

Uwaga dodatkowa: Sprawdzanie A- znie jest bezpieczne. W tym[ , \, ], ^, _, i `...

Uwaga dodatkowa 2: Aby uzyskać dodatkową wydajność, należy ustawić porównania w celu zminimalizowania liczby porównań. (W najgorszym przypadku mówimy o 8 porównaniach, więc nie myśl zbyt mocno.) To zmienia się wraz z oczekiwanymi danymi wejściowymi, ale jednym z przykładów może być:

if (str[i] >= '0' && str[i] <= 'z' && 
    (str[i] >= 'a' || str[i] <= '9' ||  (str[i] >= 'A' && str[i] <= 'Z') || 
    str[i] == '_') || str[i] == '.')

Uwaga dodatkowa 3: Jeśli z jakiegokolwiek powodu NAPRAWDĘ potrzebujesz tego, aby był szybki, instrukcja zmiany może być szybsza. Kompilator powinien utworzyć dla ciebie tabelę skoków, co da tylko jedno porównanie:

switch (str[i])
{
    case '0':
    case '1':
    .
    .
    .
    case '.':
        sb.Append(str[i]);
        break;
}

1
Zgadzam się, że nie możesz pokonać O (n) na tym. Istnieje jednak koszt porównania, który można obniżyć. Wyszukiwanie tabeli ma niski, stały koszt, a seria porównań będzie rosła w miarę dodawania kolejnych wyjątków.
Steven Sudit

Jeśli chodzi o notatkę 3, czy naprawdę uważasz, że skok byłby szybszy niż wyszukiwanie?
Steven Sudit

Przeprowadziłem szybki test wydajności rozwiązania przełącznika, który działa tak samo jak porównanie.
Guffa,

@Steven Sudit - Zaryzykowałbym, że w rzeczywistości są takie same. Chcesz uruchomić test?
lc.

7
Notacja O (n) czasami mnie wkurza. Ludzie przyjmą głupie założenia na podstawie tego, że algorytm ma już wartość O (n). Gdybyśmy zmienili tę procedurę, aby zastąpić wywołania str [i] funkcją, która pobrała wartość porównawczą, tworząc jednorazowe połączenie SSL z serwerem po przeciwnej stronie świata ... cholernie na pewno zobaczysz ogromną wydajność różnica, a algorytm to STILL O (n). Koszt O (1) dla każdego algorytmu jest znaczny i NIE jest równoważny!
darron

4
StringBuilder sb = new StringBuilder();

for (int i = 0; i < fName.Length; i++)
{
   if (char.IsLetterOrDigit(fName[i]))
    {
       sb.Append(fName[i]);
    }
}

4

Możesz użyć zwykłej ekspresji w następujący sposób:

return Regex.Replace(strIn, @"[^\w\.@-]", "", RegexOptions.None, TimeSpan.FromSeconds(1.0));

3

Wydaje mi się to dobre. Jedyne ulepszenie, którego dokonałbym, to zainicjowanie StringBuilderdługością łańcucha.

StringBuilder sb = new StringBuilder(str.Length);

3

Zgadzam się z tym przykładem kodu. Jedyne, co robię, robię to w metodzie rozszerzenia typu ciągu. Abyś mógł używać go w bardzo prostej linii lub kodzie:

string test = "abc@#$123";
test.RemoveSpecialCharacters();

Dziękuję Guffa za eksperyment.

public static class MethodExtensionHelper
    {
    public static string RemoveSpecialCharacters(this string str)
        {
            StringBuilder sb = new StringBuilder();
            foreach (char c in str)
            {
                if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
                {
                    sb.Append(c);
                }
            }
            return sb.ToString();
        }
}

2

Chciałbym użyć ciągu Zamień na wyrażenie regularne, szukając „znaków specjalnych”, zastępując wszystkie znalezione znaki pustym ciągiem.


+1 z pewnością mniej kodu i zapewne bardziej czytelny, ignorując Regex po jednokrotnym zapisie.
kenny

1
@kenny - zgadzam się. Oryginalne pytanie mówi nawet, że ciągi są krótkie - 10-30 znaków. Ale najwyraźniej wiele osób nadal uważa, że ​​sprzedajemy czas procesora do drugiej ...
Tom Bushell,

Expressin regulatora działa tak leniwie, więc nie należy go zawsze używać.
RockOnGom,

2

Musiałem zrobić coś podobnego do pracy, ale w moim przypadku musiałem odfiltrować wszystko, co nie jest literą, cyfrą lub białą spacją (ale możesz łatwo zmodyfikować to do swoich potrzeb). Filtrowanie odbywa się po stronie klienta w JavaScript, ale ze względów bezpieczeństwa zajmuję się również filtrowaniem po stronie serwera. Ponieważ mogę oczekiwać, że większość ciągów będzie czysta, chciałbym unikać kopiowania ciągów, chyba że naprawdę muszę. To pozwoliło mi na implementację poniżej, która powinna działać lepiej zarówno dla czystych, jak i brudnych łańcuchów.

public static string EnsureOnlyLetterDigitOrWhiteSpace(string input)
{
    StringBuilder cleanedInput = null;
    for (var i = 0; i < input.Length; ++i)
    {
        var currentChar = input[i];
        var charIsValid = char.IsLetterOrDigit(currentChar) || char.IsWhiteSpace(currentChar);

        if (charIsValid)
        {
            if(cleanedInput != null)
                cleanedInput.Append(currentChar);
        }
        else
        {
            if (cleanedInput != null) continue;
            cleanedInput = new StringBuilder();
            if (i > 0)
                cleanedInput.Append(input.Substring(0, i));
        }
    }

    return cleanedInput == null ? input : cleanedInput.ToString();
}

1

W przypadku S & G, Linq-ified sposób:

var original = "(*^%foo)(@)&^@#><>?:\":';=-+_";
var valid = new char[] { 
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 
    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 
    'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 
    'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', 
    '9', '0', '.', '_' };
var result = string.Join("",
    (from x in original.ToCharArray() 
     where valid.Contains(x) select x.ToString())
        .ToArray());

Jednak nie sądzę, że będzie to najbardziej efektywny sposób.


2
Nie jest, ponieważ jest to wyszukiwanie liniowe.
Steven Sudit

1
public string RemoveSpecial(string evalstr)
{
StringBuilder finalstr = new StringBuilder();
            foreach(char c in evalstr){
            int charassci = Convert.ToInt16(c);
            if (!(charassci >= 33 && charassci <= 47))// special char ???
             finalstr.append(c);
            }
return finalstr.ToString();
}

1

Posługiwać się:

s.erase(std::remove_if(s.begin(), s.end(), my_predicate), s.end());

bool my_predicate(char c)
{
 return !(isalpha(c) || c=='_' || c==' '); // depending on you definition of special characters
}

I dostaniesz czysty sznurek s.

erase()usunie go ze wszystkich znaków specjalnych i jest wysoce konfigurowalny dzięki tej my_predicate()funkcji.


1

HashSet to O (1)
Nie jestem pewien, czy jest szybszy niż istniejące porównanie

private static HashSet<char> ValidChars = new HashSet<char>() { 'a', 'b', 'c', 'A', 'B', 'C', '1', '2', '3', '_' };
public static string RemoveSpecialCharacters(string str)
{
    StringBuilder sb = new StringBuilder(str.Length / 2);
    foreach (char c in str)
    {
        if (ValidChars.Contains(c)) sb.Append(c);
    }
    return sb.ToString();
}

Testowałem i to nie szybciej niż zaakceptowana odpowiedź.
Zostawię to tak, jakbyś potrzebował konfigurowalnego zestawu znaków, byłoby to dobre rozwiązanie.


Jak myślisz, dlaczego porównanie to nie O (1)?
Guffa,

@Guffa Nie jestem pewien, że tak nie jest i usunąłem swój komentarz. I +1. Powinienem był zrobić więcej testów przed opublikowaniem komentarza.
paparazzo

1

Zastanawiam się, czy zastąpienie oparte na Regex (być może skompilowane) jest szybsze. Musiałbym sprawdzić, czy ktoś stwierdził, że jest to ~ 5 razy wolniej.

Poza tym należy zainicjować StringBuilder o oczekiwanej długości, aby łańcuch pośredni nie musiał być kopiowany podczas jego wzrostu.

Dobra liczba to długość oryginalnego ciągu lub coś nieco mniejszego (w zależności od charakteru danych wejściowych funkcji).

Na koniec możesz użyć tabeli odnośników (w zakresie 0..127), aby dowiedzieć się, czy znak ma zostać zaakceptowany.


Wyrażenie regularne zostało już przetestowane i jest około pięć razy wolniejsze. W przypadku tabeli odnośników z zakresu 0..127 nadal musisz sprawdzić zakres kodu znaków przed użyciem tabeli odnośników, ponieważ znaki są 16-bitowymi wartościami, a nie 7-bitowymi.
Guffa,

@Guffa Err ... tak? ;)
Christian Klauser

1

Poniższy kod ma następujące dane wyjściowe (wniosek jest taki, że możemy również zaoszczędzić niektóre zasoby pamięci przydzielając tablicę mniejszą wielkość):

lookup = new bool[123];

for (var c = '0'; c <= '9'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'A'; c <= 'Z'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'a'; c <= 'z'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

48: 0  
49: 1  
50: 2  
51: 3  
52: 4  
53: 5  
54: 6  
55: 7  
56: 8  
57: 9  
65: A  
66: B  
67: C  
68: D  
69: E  
70: F  
71: G  
72: H  
73: I  
74: J  
75: K  
76: L  
77: M  
78: N  
79: O  
80: P  
81: Q  
82: R  
83: S  
84: T  
85: U  
86: V  
87: W  
88: X  
89: Y  
90: Z  
97: a  
98: b  
99: c  
100: d  
101: e  
102: f  
103: g  
104: h  
105: i  
106: j  
107: k  
108: l  
109: m  
110: n  
111: o  
112: p  
113: q  
114: r  
115: s  
116: t  
117: u  
118: v  
119: w  
120: x  
121: y  
122: z  

Możesz także dodać następujące wiersze kodu w celu obsługi rosyjskich ustawień regionalnych (rozmiar tablicy to 1104):

for (var c = 'А'; c <= 'Я'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

for (var c = 'а'; c <= 'я'; c++)
{
    lookup[c] = true; System.Diagnostics.Debug.WriteLine((int)c + ": " + (char)c);
}

1

Nie jestem pewien, czy jest to najbardziej efektywny sposób, ale działa dla mnie

 Public Function RemoverTildes(stIn As String) As String
    Dim stFormD As String = stIn.Normalize(NormalizationForm.FormD)
    Dim sb As New StringBuilder()

    For ich As Integer = 0 To stFormD.Length - 1
        Dim uc As UnicodeCategory = CharUnicodeInfo.GetUnicodeCategory(stFormD(ich))
        If uc <> UnicodeCategory.NonSpacingMark Then
            sb.Append(stFormD(ich))
        End If
    Next
    Return (sb.ToString().Normalize(NormalizationForm.FormC))
End Function

Odpowiedź ma pracę, ale pytanie było dla C #. (PS: Wiem, że było to praktycznie pięć lat temu, ale nadal…) Użyłem konwertera VB na C # firmy Telerik (i odwrotnie), a kod działał dobrze - nie jestem jednak pewien, czy ktokolwiek inny. (Inna sprawa, converter.telerik.com )
Momoro

1

Istnieje wiele proponowanych rozwiązań, niektóre bardziej wydajne niż inne, ale być może niezbyt czytelne. Oto jeden, który może nie być najbardziej wydajny, ale z pewnością przydatny w większości sytuacji, i jest dość zwięzły i czytelny, wykorzystując Linq:

string stringToclean = "This is a test.  Do not try this at home; you might get hurt. Don't believe it?";

var validPunctuation = new HashSet<char>(". -");

var cleanedVersion = new String(stringToclean.Where(x => (x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());

var cleanedLowercaseVersion = new String(stringToclean.ToLower().Where(x => (x >= 'a' && x <= 'z') || validPunctuation.Contains(x)).ToArray());

-1
public static string RemoveSpecialCharacters(string str){
    return str.replaceAll("[^A-Za-z0-9_\\\\.]", "");
}

1
Obawiam się, że replaceAllnie jest to funkcja C # String, ale Java lub JavaScript
Csaba Toth

-1
public static string RemoveAllSpecialCharacters(this string text) {
  if (string.IsNullOrEmpty(text))
    return text;

  string result = Regex.Replace(text, "[:!@#$%^&*()}{|\":?><\\[\\]\\;'/.,~]", " ");
  return result;
}

Odpowiedź jest zła. Jeśli zamierzasz użyć wyrażenia regularnego, powinien on być włączający, a nie wyłączny, ponieważ teraz brakuje niektórych postaci. W rzeczywistości istnieje już odpowiedź z wyrażeniem regularnym. I aby być pełnym - wyrażenie regularne jest wolniejsze niż funkcja bezpośredniego porównywania znaków.
TPAKTOPA

-3

Jeśli martwisz się szybkością, użyj wskaźników, aby edytować istniejący ciąg. Możesz przypiąć ciąg i uzyskać do niego wskaźnik, a następnie uruchomić pętlę for nad każdym znakiem, zastępując każdy nieprawidłowy znak znakiem zastępującym. Byłby niezwykle wydajny i nie wymagałby przydzielania nowej pamięci ciągów. Będziesz także musiał skompilować moduł z opcją niebezpieczną i dodać „niebezpieczny” modyfikator do nagłówka metody, aby użyć wskaźników.

static void Main(string[] args)
{
    string str = "string!$%with^&*invalid!!characters";
    Console.WriteLine( str ); //print original string
    FixMyString( str, ' ' );
    Console.WriteLine( str ); //print string again to verify that it has been modified
    Console.ReadLine(); //pause to leave command prompt open
}


public static unsafe void FixMyString( string str, char replacement_char )
{
    fixed (char* p_str = str)
    {
        char* c = p_str; //temp pointer, since p_str is read-only
        for (int i = 0; i < str.Length; i++, c++) //loop through each character in string, advancing the character pointer as well
            if (!IsValidChar(*c)) //check whether the current character is invalid
                (*c) = replacement_char; //overwrite character in existing string with replacement character
    }
}

public static bool IsValidChar( char c )
{
    return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c == '.' || c == '_');
    //return char.IsLetterOrDigit( c ) || c == '.' || c == '_'; //this may work as well
}

14
Nieeeeeeee! Zmiana łańcucha w .NET to BAAAAAAAAAAAAD! Wszystko w ramach opiera się na zasadzie, że ciągi są niezmienne, i jeśli złamiesz, że można uzyskać bardzo zaskakujące efekty uboczne ...
Guffa
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.