Jak skutecznie łączyć łańcuchy w ruchu

727

W Go a stringjest prymitywnym typem, co oznacza, że ​​jest tylko do odczytu, a każda jego manipulacja utworzy nowy ciąg.

Więc jeśli chcę wielokrotnie łączyć łańcuchy bez znajomości długości łańcucha wynikowego, jaki jest najlepszy sposób na to?

Naiwnym sposobem byłoby:

s := ""
for i := 0; i < 1000; i++ {
    s += getShortStringFromSomewhere()
}
return s

ale to nie wydaje się bardzo skuteczne.

Randy Sugianto „Yuku”
źródło
7
Jeszcze jedna ławka
Ivan Black,
1
Uwaga: Wydaje się, że to pytanie i większość odpowiedzi zostało napisanych wcześniej append()w języku, co jest dobrym rozwiązaniem. Będzie działał szybko jak, copy()ale najpierw powiększy wycinek, nawet jeśli oznacza to przydzielenie nowej tablicy podkładowej, jeśli pojemność nie jest wystarczająca. bytes.Buffernadal ma sens, jeśli chcesz skorzystać z dodatkowych metod wygody lub jeśli oczekuje tego pakiet, którego używasz.
thomasrutter,
7
To nie tylko „wydaje się bardzo nieefektywne”; ma specyficzny problem, że każdy nowy zatrudnienie spoza firmy CS, z jakim kiedykolwiek mieliśmy do czynienia, występuje w ciągu pierwszych kilku tygodni pracy. Jest kwadratowy - O (n * n). Pomyśl o sekwencji liczb: 1 + 2 + 3 + 4 + .... Jest to n*(n+1)/2obszar trójkąta podstawy n. Przydzielasz rozmiar 1, następnie rozmiar 2, następnie rozmiar 3 itd., Gdy dołączasz niezmienne ciągi w pętli. Kwadratyczne zużycie zasobów przejawia się na wiele sposobów.
Rob

Odpowiedzi:

856

Nowy sposób:

Od wersji 1.10 istnieje strings.Buildertyp, spójrz na tę odpowiedź, aby uzyskać więcej szczegółów .

Stara droga:

Skorzystaj z bytespakietu. Ma Buffertyp, który implementuje io.Writer.

package main

import (
    "bytes"
    "fmt"
)

func main() {
    var buffer bytes.Buffer

    for i := 0; i < 1000; i++ {
        buffer.WriteString("a")
    }

    fmt.Println(buffer.String())
}

Robi to w czasie O (n).

marketingowiec
źródło
24
zamiast println (string (buffer.Bytes ())); use może po prostu zrobić println (buffer.String ())
FigmentEngine
26
Zamiast tego buffer := bytes.NewBufferString("")możesz zrobić var buffer bytes.Buffer. Nie potrzebujesz również żadnego z tych średników :).
crazy2be
66
Niezwykle szybki. Zrobiłem naiwny ciąg znaków „+” w moim programie z 3 minut do 1,3 sekundy .
Malcolm
10
+1 dla „czasu O (n)”; Myślę, że ważne jest, aby robić więcej takich uwag.
zaprzeczono
8
Go 1.10 dodaje strings.Builder , czyli jak bytes.Buffer ale szybciej, gdy końcowy cel jest ciągiem.
Josh Bleecher Snyder
272

Najbardziej efektywnym sposobem łączenia łańcuchów jest użycie wbudowanej funkcji copy. W moich testach takie podejście jest około 3 razy szybsze niż używanie bytes.Bufferi znacznie dużo szybsze (około 12 000 razy) niż używanie operatora+ . Ponadto zużywa mniej pamięci.

Stworzyłem przypadek testowy, aby to udowodnić, a oto wyniki:

BenchmarkConcat  1000000    64497 ns/op   502018 B/op   0 allocs/op
BenchmarkBuffer  100000000  15.5  ns/op   2 B/op        0 allocs/op
BenchmarkCopy    500000000  5.39  ns/op   0 B/op        0 allocs/op

Poniżej znajduje się kod do testowania:

package main

import (
    "bytes"
    "strings"
    "testing"
)

func BenchmarkConcat(b *testing.B) {
    var str string
    for n := 0; n < b.N; n++ {
        str += "x"
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); str != s {
        b.Errorf("unexpected result; got=%s, want=%s", str, s)
    }
}

func BenchmarkBuffer(b *testing.B) {
    var buffer bytes.Buffer
    for n := 0; n < b.N; n++ {
        buffer.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); buffer.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", buffer.String(), s)
    }
}

func BenchmarkCopy(b *testing.B) {
    bs := make([]byte, b.N)
    bl := 0

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        bl += copy(bs[bl:], "x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); string(bs) != s {
        b.Errorf("unexpected result; got=%s, want=%s", string(bs), s)
    }
}

// Go 1.10
func BenchmarkStringBuilder(b *testing.B) {
    var strBuilder strings.Builder

    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        strBuilder.WriteString("x")
    }
    b.StopTimer()

    if s := strings.Repeat("x", b.N); strBuilder.String() != s {
        b.Errorf("unexpected result; got=%s, want=%s", strBuilder.String(), s)
    }
}
cd1
źródło
6
Bajt.Bufor powinien robić to samo co kopia (chyba z dodatkowymi księgami), a szybkość nie jest inna. Więc użyłbym tego :). Różnica polega na tym, że bufor zaczyna się od 0 bajtów, więc musi zostać ponownie przydzielony (wydaje mi się, że wydaje się to nieco wolniejsze). Łatwiejszy w użyciu.
Aktau
5
buffer.Write(bajty) jest o 30% szybszy niż buffer.WriteString. [przydatne, jeśli możesz uzyskać dane jako []byte]
Dani-Br
34
Pamiętaj, że wyniki testu porównawczego są zniekształcone i nie są autentyczne. Różne funkcje testu będą wywoływane z różnymi wartościami b.N, więc nie porównujesz czasu wykonania tego samego zadania, które ma zostać wykonane (np. Jedna funkcja może dołączyć 1,000ciągi znaków, inna może dołączyć, 10,000co może mieć dużą różnicę w średniej na przykład czas 1 dołączenia BenchmarkConcat()). W każdym przypadku powinieneś użyć tej samej liczby dodatków (z pewnością nie b.N) i wykonać całą konkatenację w ciele forod b.N( do 2 forosadzonych pętli).
icza
18
Dodatkowo test porównawczy kopiowania jest wypaczony przez jawne ignorowanie czasu, jaki zajmuje alokacja, co jest uwzględnione w innych testach porównawczych.
gha.st
6
Ponadto test porównawczy kopiowania polega na znajomości długości wynikowego ciągu.
Skarllot,
227

Przejdź 1.10+ istnieje strings.Builder, tutaj .

Konstruktor służy do wydajnego budowania łańcucha przy użyciu metod zapisu. Minimalizuje kopiowanie pamięci. Wartość zerowa jest gotowa do użycia.


Przykład

Jest prawie tak samo z bytes.Buffer.

package main

import (
    "strings"
    "fmt"
)

func main() {
    // ZERO-VALUE:
    //
    // It's ready to use from the get-go.
    // You don't need to initialize it.
    var str strings.Builder

    for i := 0; i < 1000; i++ {
        str.WriteString("a")
    }

    fmt.Println(str.String())
}

Kliknij, aby zobaczyć to na placu zabaw .


Uwaga

  • Nie kopiuj wartości StringBuilder, ponieważ buforuje ona dane bazowe.
  • Jeśli chcesz udostępnić wartość StringBuilder, użyj do niej wskaźnika.

Obsługiwane interfejsy

Metody StringBuilder są wdrażane z myślą o istniejących interfejsach. Abyś mógł łatwo przejść do nowego typu Konstruktora w swoim kodzie.


Różnice w stosunku do bajtów Bufor

  • Może tylko rosnąć lub resetować.

  • Ma wbudowany mechanizm kopiowania, który zapobiega przypadkowemu skopiowaniu:

    func (b *Builder) copyCheck() { ... }

  • W bytes.Buffer, można uzyskać dostęp do bajtów leżących jak to: (*Buffer).Bytes().

    • strings.Builder zapobiega temu problemowi.
    • Czasami nie jest to jednak problem, a zamiast tego pożądany.
    • Na przykład: zachowanie zerkania, gdy bajty są przekazywane do io.Readeritp.

Sprawdź jego kod źródłowy, aby uzyskać więcej informacji, tutaj .

Inanc Gumus
źródło
5
Co rozumiesz przez „ucieczkę”? Czy masz na myśli znaki ucieczki w łańcuchu, czy po prostu to, że bajty leżące u ich podstaw mogą zostać odkryte?
makhdumi
1
@makhdumi Tak, 2. miejsce, ujawnienie bazowych bajtów.
Inanc Gumus
Warto zauważyć, że strings.Builderwdraża swoje metody za pomocą odbiornika wskaźnikowego, który rzucił mnie na chwilę. W rezultacie prawdopodobnie stworzyłbym taki przy użyciu new.
Duncan Jones
@DuncanJones Dodałem notatkę, ponieważ jest on używany głównie do buforowania danych, normalne jest używanie do niego wskaźnika podczas udostępniania go między funkcjami itp. W tej samej funkcji możesz używać go również jako wskaźnika.
Inanc Gumus
130

W pakiecie ciągów znajduje się funkcja biblioteki o nazwie Join: http://golang.org/pkg/strings/#Join

Spojrzenie na kod Joinpokazuje podobne podejście do funkcji Append, którą Kinopiko napisał: https://golang.org/src/strings/strings.go#L420

Stosowanie:

import (
    "fmt";
    "strings";
)

func main() {
    s := []string{"this", "is", "a", "joined", "string\n"};
    fmt.Printf(strings.Join(s, " "));
}

$ ./test.bin
this is a joined string
mbarkhau
źródło
21
Nie działa, gdy trzeba zapętlić coś, co nie jest ciągiem [].
Malcolm
42

Właśnie porównałem najlepsze odpowiedzi zamieszczone powyżej w moim własnym kodzie (rekursywny spacer po drzewie), a prosty operator konkat jest w rzeczywistości szybszy niż BufferString.

func (r *record) String() string {
    buffer := bytes.NewBufferString("");
    fmt.Fprint(buffer,"(",r.name,"[")
    for i := 0; i < len(r.subs); i++ {
        fmt.Fprint(buffer,"\t",r.subs[i])
    }
    fmt.Fprint(buffer,"]",r.size,")\n")
    return buffer.String()
}

Zajęło to 0,81 sekundy, podczas gdy następujący kod:

func (r *record) String() string {
    s := "(\"" + r.name + "\" ["
    for i := 0; i < len(r.subs); i++ {
        s += r.subs[i].String()
    }
    s += "] " + strconv.FormatInt(r.size,10) + ")\n"
    return s
} 

zajęło tylko 0,61 sekundy. Jest to prawdopodobnie spowodowane nakładem pracy związanym z tworzeniem nowegoBufferString .

Aktualizacja: Testowałem również tę joinfunkcję i działała ona w 0,54 sekundy.

func (r *record) String() string {
    var parts []string
    parts = append(parts, "(\"", r.name, "\" [" )
    for i := 0; i < len(r.subs); i++ {
        parts = append(parts, r.subs[i].String())
    }
    parts = append(parts, strconv.FormatInt(r.size,10), ")\n")
    return strings.Join(parts,"")
}
JasonMc
źródło
5
Uważam, że OP bardziej martwił się złożonością pamięci niż złożonością środowiska wykonawczego, biorąc pod uwagę fakt, że naiwne łączenie łańcuchów powoduje za każdym razem nowe przydzielanie pamięci.
galaktor
15
Niska prędkość może być związana z użyciem fmt.Fprint zamiast buffer.WriteString("\t"); buffer.WriteString(subs[i]);
Robert Jack Will
Cieszę się, że moja preferowana metoda (strings.Join)run jak najszybciej, gdy z tego mówiąc, że (bytes.Buffer)jest zwycięzcą!
Chetabahana
23

Możesz utworzyć duży kawałek bajtów i skopiować do niego bajty krótkich ciągów, używając plasterków ciągów. W „Effective Go” podano funkcję:

func Append(slice, data[]byte) []byte {
    l := len(slice);
    if l + len(data) > cap(slice) { // reallocate
        // Allocate double what's needed, for future growth.
        newSlice := make([]byte, (l+len(data))*2);
        // Copy data (could use bytes.Copy()).
        for i, c := range slice {
            newSlice[i] = c
        }
        slice = newSlice;
    }
    slice = slice[0:l+len(data)];
    for i, c := range data {
        slice[l+i] = c
    }
    return slice;
}

Następnie po zakończeniu operacji użyj string ( )dużego kawałka bajtów, aby ponownie przekonwertować go na ciąg znaków.


źródło
Interesujące jest to, że jest tak wiele sposobów na zrobienie tego w Go.
Icchak
11
W efekcie mówi również, że pomysł jest tak użyteczny, że został uchwycony we wbudowanym. append(slice, byte...)Wygląda na to, że możesz zastąpić swoją funkcję .
Aktau
23

Jest to najszybsze rozwiązanie, które nie wymaga wcześniejszej znajomości ani obliczenia całkowitego rozmiaru bufora:

var data []byte
for i := 0; i < 1000; i++ {
    data = append(data, getShortStringFromSomewhere()...)
}
return string(data)

Według mojego testu jest o 20% wolniejszy niż rozwiązanie do kopiowania (8,1ns na dołączenie zamiast 6,72ns), ale nadal 55% szybszy niż użycie bajtów. Bufor.

Rog
źródło
23
package main

import (
  "fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    out := fmt.Sprintf("%s %s ",str1, str2)
    fmt.Println(out)
}
Harold Ramos
źródło
2
Witamy w Stack Overflow! Poświęć chwilę, aby przeczytać pomoc dotyczącą edycji w centrum pomocy. Formatowanie w przypadku przepełnienia stosu jest inne niż w innych witrynach.
Rizier123
2
Ten fragment kodu może rozwiązać pytanie, ale wyjaśnienie naprawdę pomaga poprawić jakość posta. Pamiętaj, że w przyszłości odpowiadasz na pytanie czytelników, a ci ludzie mogą nie znać przyczyn Twojej sugestii kodu. Staraj się również nie tłoczyć kodu objaśniającymi komentarzami, co zmniejsza czytelność zarówno kodu, jak i objaśnień!
Rizier123
Proste rozwiązanie 👍
Finn
22

Uwaga dodana w 2018 r

Od wersji 1.10 istnieje strings.Buildertyp, spójrz na tę odpowiedź, aby uzyskać więcej szczegółów .

Odpowiedź sprzed 201x

Kod testu porównawczego @ cd1 i inne odpowiedzi są nieprawidłowe. b.Nnie powinien być ustawiony w funkcji testu porównawczego. Jest on ustawiany dynamicznie przez narzędzie testowe w celu ustalenia, czy czas wykonania testu jest stabilny.

Funkcja testu porównawczego powinna uruchamiać te same b.Nczasy testu, a test wewnątrz pętli powinien być taki sam dla każdej iteracji. Naprawiam to, dodając wewnętrzną pętlę. Dodam również testy porównawcze dla niektórych innych rozwiązań:

package main

import (
    "bytes"
    "strings"
    "testing"
)

const (
    sss = "xfoasneobfasieongasbg"
    cnt = 10000
)

var (
    bbb      = []byte(sss)
    expected = strings.Repeat(sss, cnt)
)

func BenchmarkCopyPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        bs := make([]byte, cnt*len(sss))
        bl := 0
        for i := 0; i < cnt; i++ {
            bl += copy(bs[bl:], sss)
        }
        result = string(bs)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppendPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, cnt*len(sss))
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferPreAllocate(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        buf := bytes.NewBuffer(make([]byte, 0, cnt*len(sss)))
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkCopy(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64) // same size as bootstrap array of bytes.Buffer
        for i := 0; i < cnt; i++ {
            off := len(data)
            if off+len(sss) > cap(data) {
                temp := make([]byte, 2*cap(data)+len(sss))
                copy(temp, data)
                data = temp
            }
            data = data[0 : off+len(sss)]
            copy(data[off:], sss)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkAppend(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        data := make([]byte, 0, 64)
        for i := 0; i < cnt; i++ {
            data = append(data, sss...)
        }
        result = string(data)
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWrite(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.Write(bbb)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkBufferWriteString(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var buf bytes.Buffer
        for i := 0; i < cnt; i++ {
            buf.WriteString(sss)
        }
        result = buf.String()
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

func BenchmarkConcat(b *testing.B) {
    var result string
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < cnt; i++ {
            str += sss
        }
        result = str
    }
    b.StopTimer()
    if result != expected {
        b.Errorf("unexpected result; got=%s, want=%s", string(result), expected)
    }
}

Środowisko to OS X 10.11.6, 2,2 GHz Intel Core i7

Wyniki testów:

BenchmarkCopyPreAllocate-8         20000             84208 ns/op          425984 B/op          2 allocs/op
BenchmarkAppendPreAllocate-8       10000            102859 ns/op          425984 B/op          2 allocs/op
BenchmarkBufferPreAllocate-8       10000            166407 ns/op          426096 B/op          3 allocs/op
BenchmarkCopy-8                    10000            160923 ns/op          933152 B/op         13 allocs/op
BenchmarkAppend-8                  10000            175508 ns/op         1332096 B/op         24 allocs/op
BenchmarkBufferWrite-8             10000            239886 ns/op          933266 B/op         14 allocs/op
BenchmarkBufferWriteString-8       10000            236432 ns/op          933266 B/op         14 allocs/op
BenchmarkConcat-8                     10         105603419 ns/op        1086685168 B/op    10000 allocs/op

Wniosek:

  1. CopyPreAllocatejest najszybszym sposobem; AppendPreAllocatejest dość blisko numeru 1, ale łatwiej jest napisać kod.
  2. Concatma naprawdę niską wydajność zarówno pod względem szybkości, jak i zużycia pamięci. Nie używaj tego.
  3. Buffer#Writei Buffer#WriteStringsą w zasadzie takie same pod względem prędkości, w przeciwieństwie do tego, co powiedział @ Dani-Br w komentarzu. Rozważanie stringjest rzeczywiście[]byte w Go, ma to sens.
  4. bajty.Bufor zasadniczo używa tego samego rozwiązania, co w Copyprzypadku dodatkowej księgowości i innych rzeczy.
  5. Copyi Appendużyj rozmiaru bootstrapu 64, takiego samego jak bytes.Buffer
  6. Appendzużywają więcej pamięci i alokacji, myślę, że jest to związane z algorytmem wzrostu, którego używa. Nie rośnie pamięć tak szybko jak bajty. Bufor

Sugestia:

  1. Do prostych zadań, takich jak to, czego chce OP, użyłbym Appendlub AppendPreAllocate. Jest wystarczająco szybki i łatwy w użyciu.
  2. Jeśli musisz jednocześnie czytać i zapisywać bufor, skorzystaj bytes.Bufferoczywiście. Do tego jest przeznaczony.
PickBoy
źródło
13

Moja oryginalna sugestia brzmiała

s12 := fmt.Sprint(s1,s2)

Ale powyżej odpowiedzi za pomocą bytes.Buffer - WriteString () jest najbardziej wydajnym sposobem.

Moja początkowa sugestia wykorzystuje odbicie i przełącznik typu. Zobacz (p *pp) doPrinti(p *pp) printArg
nie ma uniwersalnego interfejsu Stringer () dla podstawowych typów, jak naiwnie myślałem.

Przynajmniej Sprint () wewnętrznie używa bajtu. Bufor. A zatem

`s12 := fmt.Sprint(s1,s2,s3,s4,...,s1000)`

jest akceptowalny pod względem alokacji pamięci.

=> Łączenie Sprint () może być użyte do szybkiego wyjścia danych debugowania.
=> W przeciwnym razie użyj bytes.Buffer ... WriteString

Peter Buchmann
źródło
8
Nie jest wbudowany i nie jest wydajny.
peterSO
Importowanie pakietu (takiego jak fmt) oznacza, że ​​nie jest ono wbudowane. Jest w standardowej bibliotece.
Malcolm
Jest powolny tylko dlatego, że wykorzystuje refleksję nad argumentami. Jest skuteczny. W przeciwnym razie jest nie mniej wydajne niż łączenie za pomocą
łańcuchów. Dołącz
11

Rozwijanie odpowiedzi cd1: Możesz użyć append () zamiast copy (). append () wprowadza coraz większe rezerwy, kosztując nieco więcej pamięci, ale oszczędzając czas. Dodałem dwa kolejne testy na górze twojego. Uruchom lokalnie z

go test -bench=. -benchtime=100ms

Na moim thinkpadie T400s daje:

BenchmarkAppendEmpty    50000000         5.0 ns/op
BenchmarkAppendPrealloc 50000000         3.5 ns/op
BenchmarkCopy           20000000        10.2 ns/op
Peter Buchmann
źródło
4

To jest rzeczywista wersja testu porównawczego dostarczonego przez @ cd1 ( Go 1.8, linux x86_64) z poprawkami błędów wspomnianymi przez @icza i @PickBoy.

Bytes.Bufferjest tylko kilka 7razy szybszy niż bezpośrednie łączenie łańcucha przez +operatora.

package performance_test

import (
    "bytes"
    "fmt"
    "testing"
)

const (
    concatSteps = 100
)

func BenchmarkConcat(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var str string
        for i := 0; i < concatSteps; i++ {
            str += "x"
        }
    }
}

func BenchmarkBuffer(b *testing.B) {
    for n := 0; n < b.N; n++ {
        var buffer bytes.Buffer
        for i := 0; i < concatSteps; i++ {
            buffer.WriteString("x")
        }
    }
}

Czasy:

BenchmarkConcat-4                             300000          6869 ns/op
BenchmarkBuffer-4                            1000000          1186 ns/op
Witalij Isaev
źródło
Nie sądzę, aby ręczne ustawienie bN było właściwym sposobem korzystania z funkcji testu porównawczego pakietu testowego
PickBoy
@PickBoy, proszę uzasadnić swój punkt widzenia. Jak myślisz, dlaczego b.Njest to zmienna publiczna?
Vitaly Isaev
1
bN nie powinien być ustawiony w funkcji testu porównawczego. Jest on ustawiany dynamicznie przez narzędzie testowe. Funkcja testu porównawczego powinna uruchamiać ten sam test bN razy, ale w kodzie (jak również w kodzie @ cd1) każdy test w pętli jest innym testem (ponieważ długość łańcucha rośnie)
PickBoy
@ PickBoy, jeśli puścisz narzędzie testowe b.Ndynamicznie, skończysz z łańcuchami o różnej długości w różnych przypadkach testowych. Patrz komentarz
Witalij Isaev
Dlatego należy dodać wewnętrzną pętlę o stałej liczbie iteracji, np. 10000, wewnątrz pętli bN.
PickBoy
3

goutils.JoinBetween

 func JoinBetween(in []string, separator string, startIndex, endIndex int) string {
    if in == nil {
        return ""
    }

    noOfItems := endIndex - startIndex

    if noOfItems <= 0 {
        return EMPTY
    }

    var builder strings.Builder

    for i := startIndex; i < endIndex; i++ {
        if i > startIndex {
            builder.WriteString(separator)
        }
        builder.WriteString(in[i])
    }
    return builder.String()
}
Xian Shu
źródło
1

Robię to za pomocą:

package main

import (
    "fmt"
    "strings"
)

func main (){
    concatenation:= strings.Join([]string{"a","b","c"},"") //where second parameter is a separator. 
    fmt.Println(concatenation) //abc
}
Krish Bhanushali
źródło
Nie rozwiązuje to problemu OP polegającego na budowaniu łańcucha przez serię iteracji za pomocą pętli for.
codeforester
1
package main

import (
"fmt"
)

func main() {
    var str1 = "string1"
    var str2 = "string2"
    result := make([]byte, 0)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)
    result = append(result, []byte(str1)...)
    result = append(result, []byte(str2)...)

    fmt.Println(string(result))
}
rajni kant
źródło
3
Proszę nie wpisywać kodu tylko odpowiedzi. Proszę wyjaśnić, co robi ten kod i dlaczego jest rozwiązaniem.
Korashen
-1

wynik testu porównawczego ze statystykami alokacji pamięci. sprawdź kod testu na github .

użyj strings.Builder do optymalizacji wydajności.

go test -bench . -benchmem
goos: darwin
goarch: amd64
pkg: github.com/hechen0/goexp/exps
BenchmarkConcat-8                1000000             60213 ns/op          503992 B/op          1 allocs/op
BenchmarkBuffer-8               100000000               11.3 ns/op             2 B/op          0 allocs/op
BenchmarkCopy-8                 300000000                4.76 ns/op            0 B/op          0 allocs/op
BenchmarkStringBuilder-8        1000000000               4.14 ns/op            6 B/op          0 allocs/op
PASS
ok      github.com/hechen0/goexp/exps   70.071s
hechen0
źródło
prosimy o uznanie @ cd1 za oryginalne przypadki testowe, które budujesz tutaj.
colm.anseo
-2
s := fmt.Sprintf("%s%s", []byte(s1), []byte(s2))
użytkownik2288856
źródło
5
Jest to rozwiązanie bardzo wolne, ponieważ wykorzystuje odbicie, analizuje ciąg formatu i tworzy kopię danych do []byte(s1)konwersji. Porównując go z innymi opublikowanymi rozwiązaniami, czy możesz wymienić jedną zaletę swojego rozwiązania?
pkt
-5

strings.Join() z pakietu „strings”

Jeśli masz niedopasowanie typu (np. Jeśli próbujesz połączyć liczbę całkowitą i ciąg znaków), wykonujesz RANDOMTYPE (rzecz, którą chcesz zmienić)

DAWNY:

package main

import (
    "fmt"
    "strings"
)

var intEX = 0
var stringEX = "hello all you "
var stringEX2 = "people in here"


func main() {
    s := []string{stringEX, stringEX2}
    fmt.Println(strings.Join(s, ""))
}

Wynik :

hello all you people in here
liam
źródło
4
Ten kod nawet się nie kompiluje: strings.Join()pobiera tylko 2 parametry: plasterek i separator string.
icza
to nie może pomóc
Anshu
dodaj kilka zmian tutaj.
Anshu