Najlepszy sposób na usunięcie ostatniego znaku z łańcucha zbudowanego za pomocą narzędzia do tworzenia ciągów

92

Mam następujące

data.AppendFormat("{0},",dataToAppend);

Problem polega na tym, że używam go w pętli i pojawi się przecinek testowy. Jaki jest najlepszy sposób na usunięcie końcowego przecinka?

Czy muszę zmienić dane na ciąg będący podłańcuchem?

Wesley Skeen
źródło
10
string.Join(",", yourCollection)? Edycja: dodana jako odpowiedź.
Vlad
1
czy próbowałeś stackoverflow.com/questions/5701163/… ?
andreister
@Chris: w ten sposób w ogóle nie potrzebujesz StringBuildera.
Vlad
może możesz uniknąć dodawania przecinka zamiast później go usuwać. Zobacz: stackoverflow.com/questions/581448/… (odpowiedź Jona Skeeta)
Paolo Falabella
@Vlad Tak, przepraszam, źle to przeczytałem; Myślałem, że zaproponowałeś to jako sugestię zmiany ostatecznej zbudowanej struny, a nie jako całkowity zamiennik dla jego pętli. (Myślałem, że usunąłem swój komentarz na czas, chyba nie!)
Chris Sinclair

Odpowiedzi:

224

Najprostszym i najbardziej wydajnym sposobem jest wykonanie tego polecenia:

data.Length--;

w ten sposób przesuwasz wskaźnik (tj. ostatni indeks) wstecz o jeden znak, ale nie zmieniasz zmienności obiektu. W rzeczywistości, wyczyszczenie a StringBuildernajlepiej również wykonać Length(ale w rzeczywistości użyj Clear()metody dla przejrzystości, ponieważ tak wygląda jej implementacja):

data.Length = 0;

ponownie, ponieważ nie zmienia to tabeli alokacji. Pomyśl o tym jak o stwierdzeniu, że nie chcę już rozpoznawać tych bajtów. Teraz, nawet dzwoniąc ToString(), nie rozpozna niczego poza swoim… Lengthcóż, nie może. Jest to zmienny obiekt, który przydziela więcej miejsca niż to, co mu zapewniasz, jest po prostu zbudowany w ten sposób.

Mike Perrenoud
źródło
2
re data.Length = 0;: dokładnie to StringBuilder.Clearrobi, więc lepiej używać go StringBuilder.Cleardla jasności intencji.
Eren Ersönmez
@ ErenErsönmez, dość uczciwy przyjacielu, powinienem był wyraźniej stwierdzić, że to Clear()robi, ale zabawna rzecz. To jest pierwsza linia z Clear()metody. Ale czy wiesz, że interfejs faktycznie wyświetla plik return this;. To właśnie mnie zabija. Ustawiając Length = 0zmiany jako odniesienie, które już masz, po co wracać?
Mike Perrenoud
12
Myślę, że to po to, aby móc używać w „płynny” sposób. Appendwraca również.
Eren Ersönmez
43

Po prostu użyj

string.Join(",", yourCollection)

W ten sposób nie potrzebujesz StringBuilderpętli i.




Długi dodatek o przypadku asynchronicznym. Począwszy od 2019 roku, nie jest to rzadka konfiguracja, gdy dane przychodzą asynchronicznie.

W przypadku, gdy Twoje dane znajdują się w kolekcji asynchronicznej, nie występuje string.Joinprzeciążenie IAsyncEnumerable<T>. Ale łatwo jest utworzyć go ręcznie, hakując kod zstring.Join :

public static class StringEx
{
    public static async Task<string> JoinAsync<T>(string separator, IAsyncEnumerable<T> seq)
    {
        if (seq == null)
            throw new ArgumentNullException(nameof(seq));

        await using (var en = seq.GetAsyncEnumerator())
        {
            if (!await en.MoveNextAsync())
                return string.Empty;

            string firstString = en.Current?.ToString();

            if (!await en.MoveNextAsync())
                return firstString ?? string.Empty;

            // Null separator and values are handled by the StringBuilder
            var sb = new StringBuilder(256);
            sb.Append(firstString);

            do
            {
                var currentValue = en.Current;
                sb.Append(separator);
                if (currentValue != null)
                    sb.Append(currentValue);
            }
            while (await en.MoveNextAsync());
            return sb.ToString();
        }
    }
}

Jeśli dane przychodzą asynchronicznie, ale interfejs IAsyncEnumerable<T>nie jest obsługiwany (jak wspomniano w komentarzach SqlDataReader), stosunkowo łatwo jest opakować dane w IAsyncEnumerable<T>:

async IAsyncEnumerable<(object first, object second, object product)> ExtractData(
        SqlDataReader reader)
{
    while (await reader.ReadAsync())
        yield return (reader[0], reader[1], reader[2]);
}

i użyj go:

Task<string> Stringify(SqlDataReader reader) =>
    StringEx.JoinAsync(
        ", ",
        ExtractData(reader).Select(x => $"{x.first} * {x.second} = {x.product}"));

Aby użyć Select, musisz użyć pakietu nuget System.Interactive.Async. Tutaj znajdziesz przykład do kompilacji.

Vlad
źródło
12
Najlepsza odpowiedź to nie ta, która rozwiązuje problem, ale ta, która mu zapobiega.
LastTribunal
1
@Seabizkit: Oczywiście! Nawiasem mówiąc, całe pytanie dotyczy C #.
Vlad
1
@Vlad Rozumiem, sprawdzam tylko dwukrotnie, ponieważ robię prosty test, taki jak test surowy, a one nie dały tego samego. string.Join(",", yourCollection)nadal ma ,na końcu. więc powyższy ie string.Join(",", yourCollection)jest nieefektywny i sam go nie usuwa.
Seabizkit
2
@Seabizkit: Bardzo dziwne! Czy mógłbyś zamieścić przykład? W moim kodzie działa idealnie: ideone.com/aj8PWR
Vlad
2
Lata używania pętli do budowania konstruktora ciągów, a następnie usuwania końcowego przecinka i mogłem po prostu tego użyć. Dzięki za wskazówkę!
Caverman
11

Użyj następującego po pętli.

.TrimEnd(',')

lub po prostu zmień na

string commaSeparatedList = input.Aggregate((a, x) => a + ", " + x)
Sam Leach
źródło
4
Używa StringBuilder, a nie string. Co więcej, jest to dość nieskuteczne: najpierw konwersja na sznurek, a następnie przycinanie.
Piotr Stapp
lubstring.Join(",", input)
Tvde1,
11

Co powiesz na to..

string str = "The quick brown fox jumps over the lazy dog,";
StringBuilder sb = new StringBuilder(str);
sb.Remove(str.Length - 1, 1);
Pankaj
źródło
7

Wolę manipulować długością stringbuildera:

data.Length = data.Length - 1;
bastos.sergio
źródło
4
Dlaczego nie po prostu data.Length--lub --data.Length?
Gone Coding,
Zwykle używam data.Length - ale w jednym przypadku musiałem cofnąć się o 2 znaki z powodu pustej wartości po znaku, który chciałem usunąć. Trim również nie działał w tym przypadku, więc data.Length = data.Length - 2; pracował.
Caverman
Trim zwraca nowe wystąpienie ciągu, nie zmienia zawartości obiektu
stringbuilder
@GoneCoding Visual Basic .NET nie obsługuje --lub ++ możesz użyć data.Length -= 1, albo ta odpowiedź też będzie działać.
Jason S
3

Polecam zmienić algorytm pętli:

  • Dodaj przecinek nie ZA elementem, ale PRZED
  • Użyj zmiennej logicznej, która zaczyna się od fałszu, pomiń pierwszy przecinek
  • Po przetestowaniu tej zmiennej boolowskiej należy ustawić wartość true
Eugen Rieck
źródło
2
Jest to prawdopodobnie najmniej wydajna ze wszystkich sugestii (i wymaga więcej kodu).
Gone Coding,
1
Spójrz na odpowiedź @Vlad
Noctis
3

Należy użyć tej string.Joinmetody, aby zamienić kolekcję elementów w ciąg rozdzielany przecinkami. Zapewni to, że nie będzie żadnych przecinków na początku ani na końcu, a także zapewni wydajną konstrukcję ciągu (bez niepotrzebnych ciągów pośrednich).

Servy
źródło
2

Tak, zamień go na ciąg po zakończeniu pętli:

String str = data.ToString().TrimEnd(',');
DonBoitnott
źródło
3
jest to dość nieskuteczne: najpierw konwersja na ciąg, a następnie przycinanie.
Piotr Stapp
2
@Garath Gdybyś miał na myśli „nieefektywny”, nie zaprzeczyłbym. Ale to byłoby skuteczne.
DonBoitnott
2

Masz dwie możliwości. Pierwsza to bardzo łatwa Removemetoda użycia , która jest dość skuteczna. Drugi sposób to użycie ToStringz indeksem początkowym i końcowym ( dokumentacja MSDN )

Piotr Stapp
źródło
1

Najprostszym sposobem byłoby użycie metody Join ():

public static void Trail()
{
    var list = new List<string> { "lala", "lulu", "lele" };
    var data = string.Join(",", list);
}

Jeśli naprawdę potrzebujesz StringBuilder, przytnij końcowy przecinek po pętli:

data.ToString().TrimEnd(',');
studert
źródło
4
data.ToString().TrimEnd(',');jest nieefektywny
bastos.sergio
1
Możesz też nie chcieć konwertować obiektu StringBuilder na String, ponieważ może on mieć wiele wierszy kończących się na „,”
Fandango68
0

Mam cię !!

Większość odpowiedzi w tym wątku nie zadziała, jeśli użyjesz AppendLinejak poniżej:

var builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length--; // Won't work
Console.Write(builder.ToString());

builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length += -1; // Won't work
Console.Write(builder.ToString());

builder = new StringBuilder();
builder.AppendLine("One,");
Console.Write(builder.TrimEnd(',')); // Won't work

Fiddle Me

CZEMU??? @ (& ** (& @ !!

Problem jest prosty, ale zajęło mi trochę czasu, zanim to rozgryzłem: ponieważ na końcu są jeszcze 2 niewidoczne znaki CRi LF(powrót karetki i przesunięcie wiersza). Dlatego musisz usunąć 3 ostatnie znaki:

var builder = new StringBuilder();
builder.AppendLine("One,");
builder.Length -= 3; // This will work
Console.WriteLine(builder.ToString());

Na zakończenie

Użyj Length--lub, Length -= 1jeśli ostatnią wywołaną metodą była Append. Użyj, Length =- 3jeśli używasz ostatniej metody, którą wywołałeś AppendLine.

KodowanieYoshi
źródło