Was ist der beste Weg, um in Go auf eine leere Zeichenfolge zu testen?

261

Welche Methode eignet sich am besten (idomatischer) zum Testen nicht leerer Zeichenfolgen (in Go)?

if len(mystring) > 0 { }

Oder:

if mystring != "" { }

Oder etwas anderes?

Richard
quelle

Antworten:

389

Beide Stile werden in den Standardbibliotheken von Go verwendet.

if len(s) > 0 { ... }

finden Sie im strconvPaket: http://golang.org/src/pkg/strconv/atoi.go

if s != "" { ... }

finden Sie im encoding/jsonPaket: http://golang.org/src/pkg/encoding/json/encode.go

Beide sind idiomatisch und klar genug. Es geht eher um den persönlichen Geschmack und um Klarheit.

Russ Cox schreibt in einem Golang-Nuss-Thread :

Derjenige, der den Code klar macht.
Wenn ich mir Element x anschaue, schreibe ich normalerweise
len (s)> x, sogar für x == 0, aber wenn mir
"ist es diese spezielle Zeichenfolge" wichtig ist, schreibe ich eher s == "".

Es ist anzunehmen, dass ein ausgereifter Compiler
len (s) == 0 und s == "" in denselben effizienten Code kompiliert.
...

Machen Sie den Code klar.

Wie in der Antwort von Timmmm ausgeführt , generiert der Go-Compiler in beiden Fällen identischen Code.

ANisus
quelle
1
Ich stimme dieser Antwort nicht zu. Einfach if mystring != "" { }ist der beste, bevorzugte und idiomatische Weg HEUTE. Der Grund, warum die Standardbibliothek etwas anderes enthält, ist, dass sie vor 2010 geschrieben wurde, als die len(mystring) == 0Optimierung sinnvoll war.
Honzajde
12
@honzajde Ich habe gerade versucht, Ihre Anweisung zu validieren, habe jedoch Commits in der Standardbibliothek gefunden, die jünger als 1 Jahr sind len, um leere / nicht leere Zeichenfolgen zu überprüfen. So ein Commit von Brad Fitzpatrick. Ich fürchte, es ist immer noch eine Frage des Geschmacks und der Klarheit;)
ANisus
6
@honzajde Nicht trolling. Das Commit enthält 3 Schlüsselwörter. Ich bezog mich auf len(v) > 0in h2_bundle.go (Zeile 2702). Es wird nicht automatisch angezeigt, da es aus golang.org/x/net/http2 generiert wird, glaube ich.
ANisus
2
Wenn es noi im Diff ist, dann ist es nicht neu. Warum postest du keinen direkten Link? Sowieso. genug Detektivarbeit für mich ... ich sehe es nicht.
Honzajde
6
@honzajde Keine Sorge. Ich gehe davon aus, dass andere wissen, wie man für die Datei h2_bundle.go auf "Diff laden" klickt.
ANisus
30

Dies scheint eine vorzeitige Mikrooptimierung zu sein. Dem Compiler steht es frei, für beide Fälle oder zumindest für diese beiden Fälle denselben Code zu erstellen

if len(s) != 0 { ... }

und

if s != "" { ... }

weil die Semantik eindeutig gleich ist.

zzzz
quelle
1
vereinbart, aber es hängt wirklich von der Implementierung des Strings ab ... Wenn Strings wie pascal implementiert werden, werden len (s) in o (1) ausgeführt und wenn wie C, dann ist es o (n). oder was auch immer, da len () vollständig ausgeführt werden muss.
Richard
Haben Sie sich die Codegenerierung angesehen, um festzustellen, ob der Compiler dies vorwegnimmt, oder schlagen Sie nur vor, dass ein Compiler dies implementieren könnte?
Michael Labbé
19

Das Überprüfen der Länge ist eine gute Antwort, aber Sie können auch eine "leere" Zeichenfolge berücksichtigen, die ebenfalls nur aus Leerzeichen besteht. Nicht "technisch" leer, aber wenn Sie überprüfen möchten:

package main

import (
  "fmt"
  "strings"
)

func main() {
  stringOne := "merpflakes"
  stringTwo := "   "
  stringThree := ""

  if len(strings.TrimSpace(stringOne)) == 0 {
    fmt.Println("String is empty!")
  }

  if len(strings.TrimSpace(stringTwo)) == 0 {
    fmt.Println("String two is empty!")
  }

  if len(stringTwo) == 0 {
    fmt.Println("String two is still empty!")
  }

  if len(strings.TrimSpace(stringThree)) == 0 {
    fmt.Println("String three is empty!")
  }
}
Wilhelm Murdoch
quelle
TrimSpacewird eine neue Zeichenfolge aus der ursprünglichen Zeichenfolge zuweisen und kopieren, sodass dieser Ansatz zu Ineffizienzen bei der Skalierung führt.
Dai
@Dai beim Betrachten des Quellcodes wäre dies nur dann der Fall, wenn der angegebene sTyp s[0:i]eine neue Kopie zurückgibt. Strings sind in Go unveränderlich. Muss hier also eine Kopie erstellt werden?
Michael Paesold
@MichaelPaesold Right - bewirkt strings.TrimSpace( s )keine neue Zeichenfolgenzuweisung und Zeichenkopie, wenn die Zeichenfolge nicht gekürzt werden muss. Wenn die Zeichenfolge jedoch gekürzt werden muss, wird die zusätzliche Kopie (ohne Leerzeichen) aufgerufen.
Dai
1
"technisch leer" ist die Frage.
Richard
Der gocriticLinter schlägt vor, strings.TrimSpace(str) == ""anstelle der Längenprüfung zu verwenden.
y3sh
12

Angenommen, leere Leerzeichen und alle führenden und nachfolgenden Leerzeichen sollten entfernt werden:

import "strings"
if len(strings.TrimSpace(s)) == 0 { ... }

Weil :
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2

Edwinner
quelle
2
Warum haben Sie diese Annahme? Der Typ erzählt deutlich von der leeren Schnur. Auf die gleiche Weise können Sie feststellen, dass Sie nur ASCII-Zeichen in einer Zeichenfolge möchten und dann eine Funktion hinzufügen, mit der alle Nicht-ASCII-Zeichen entfernt werden.
Salvador Dali
1
Weil len (""), len ("") und len ("") nicht dasselbe sind. Ich ging davon aus, dass er sicherstellen wollte, dass eine Variable, die er mit einer dieser Variablen initialisiert hatte, tatsächlich noch "technisch" leer ist.
Edwinner
Dies ist eigentlich genau das, was ich von diesem Beitrag brauchte. Ich brauche die Benutzereingabe, um mindestens 1 Nicht-Leerzeichen zu haben, und dieser Einzeiler ist klar und präzise. Alles was ich tun muss ist die if Bedingung < 1+1
Shadoninja
7

Ab sofort generiert der Go-Compiler in beiden Fällen identischen Code, daher ist es Geschmackssache. GCCGo generiert zwar anderen Code, aber kaum jemand verwendet ihn, sodass ich mir darüber keine Sorgen machen würde.

https://godbolt.org/z/fib1x1

Timmmm
quelle
1

Es wäre sauberer und weniger fehleranfällig, eine Funktion wie die folgende zu verwenden:

func empty(s string) bool {
    return len(strings.TrimSpace(s)) == 0
}
Yannis Sermetziadis
quelle
0

Nur um mehr zum Kommentar hinzuzufügen

Hauptsächlich darüber, wie Leistungstests durchgeführt werden.

Ich habe mit folgendem Code getestet:

import (
    "testing"
)

var ss = []string{"Hello", "", "bar", " ", "baz", "ewrqlosakdjhf12934c r39yfashk fjkashkfashds fsdakjh-", "", "123"}

func BenchmarkStringCheckEq(b *testing.B) {
    c := 0
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
            for _, s := range ss {
                    if s == "" {
                            c++
                    }
            }
    } 
    t := 2 * b.N
    if c != t {
            b.Fatalf("did not catch empty strings: %d != %d", c, t)
    }
}
func BenchmarkStringCheckLen(b *testing.B) {
    c := 0
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
            for _, s := range ss { 
                    if len(s) == 0 {
                            c++
                    }
            }
    } 
    t := 2 * b.N
    if c != t {
            b.Fatalf("did not catch empty strings: %d != %d", c, t)
    }
}
func BenchmarkStringCheckLenGt(b *testing.B) {
    c := 0
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
            for _, s := range ss {
                    if len(s) > 0 {
                            c++
                    }
            }
    } 
    t := 6 * b.N
    if c != t {
            b.Fatalf("did not catch empty strings: %d != %d", c, t)
    }
}
func BenchmarkStringCheckNe(b *testing.B) {
    c := 0
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
            for _, s := range ss {
                    if s != "" {
                            c++
                    }
            }
    } 
    t := 6 * b.N
    if c != t {
            b.Fatalf("did not catch empty strings: %d != %d", c, t)
    }
}

Und die Ergebnisse waren:

% for a in $(seq 50);do go test -run=^$ -bench=. --benchtime=1s ./...|grep Bench;done | tee -a log
% sort -k 3n log | head -10

BenchmarkStringCheckEq-4        150149937            8.06 ns/op
BenchmarkStringCheckLenGt-4     147926752            8.06 ns/op
BenchmarkStringCheckLenGt-4     148045771            8.06 ns/op
BenchmarkStringCheckNe-4        145506912            8.06 ns/op
BenchmarkStringCheckLen-4       145942450            8.07 ns/op
BenchmarkStringCheckEq-4        146990384            8.08 ns/op
BenchmarkStringCheckLenGt-4     149351529            8.08 ns/op
BenchmarkStringCheckNe-4        148212032            8.08 ns/op
BenchmarkStringCheckEq-4        145122193            8.09 ns/op
BenchmarkStringCheckEq-4        146277885            8.09 ns/op

Effektiv erreichen Varianten normalerweise nicht die schnellste Zeit und es gibt nur einen minimalen Unterschied (ca. 0,01 ns / op) zwischen der Höchstgeschwindigkeit der Variante.

Und wenn ich das vollständige Protokoll betrachte, ist der Unterschied zwischen Versuchen größer als der Unterschied zwischen Benchmark-Funktionen.

Es scheint auch keinen messbaren Unterschied zwischen BenchmarkStringCheckEq und BenchmarkStringCheckNe oder BenchmarkStringCheckLen und BenchmarkStringCheckLenGt zu geben, selbst wenn letztere Varianten 6-mal statt 2-mal enthalten sollten.

Sie können versuchen, ein gewisses Vertrauen in die gleiche Leistung zu gewinnen, indem Sie Tests mit modifiziertem Test oder innerer Schleife hinzufügen. Das geht schneller:

func BenchmarkStringCheckNone4(b *testing.B) {
    c := 0
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
            for _, _ = range ss {
                    c++
            }
    }
    t := len(ss) * b.N
    if c != t {
            b.Fatalf("did not catch empty strings: %d != %d", c, t)
    }
}

Das geht nicht schneller:

func BenchmarkStringCheckEq3(b *testing.B) {
    ss2 := make([]string, len(ss))
    prefix := "a"
    for i, _ := range ss {
            ss2[i] = prefix + ss[i]
    }
    c := 0
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
            for _, s := range ss2 {
                    if s == prefix {
                            c++
                    }
            }
    }
    t := 2 * b.N
    if c != t {
            b.Fatalf("did not catch empty strings: %d != %d", c, t)
    }
}

Beide Varianten sind normalerweise schneller oder langsamer als der Unterschied zwischen den Haupttests.

Es wäre auch gut, Teststrings (ss) mit einem Stringgenerator mit relevanter Verteilung zu generieren. Und haben auch variable Längen.

Ich habe also kein Vertrauen in den Leistungsunterschied zwischen den Hauptmethoden zum Testen leerer Zeichenfolgen in go.

Und ich kann mit einiger Sicherheit sagen, dass es schneller ist, leere Zeichenfolgen überhaupt nicht zu testen, als leere Zeichenfolgen zu testen. Außerdem ist es schneller, leere Zeichenfolgen zu testen, als 1 Zeichenfolge (Präfixvariante) zu testen.

Markus Linnala
quelle
0

Gemäß den offiziellen Richtlinien und aus Sicht der Leistung erscheinen sie gleichwertig ( ANisus-Antwort ), das s! = "" Wäre aufgrund eines syntaktischen Vorteils besser. s! = "" schlägt beim Kompilieren fehl, wenn die Variable keine Zeichenfolge ist, während len (s) == 0 für mehrere andere Datentypen übergeben wird.

Janis Viksne
quelle
Es gab eine Zeit, in der ich CPU-Zyklen gezählt und den Assembler überprüft habe, den der C-Compiler erstellt und die Struktur von C- und Pascal-Strings len()genau verstanden hat ... trotz aller Optimierungen auf der Welt ist nur ein wenig mehr Arbeit erforderlich. In C haben wir jedoch die linke Seite in a umgewandelt constoder die statische Zeichenfolge auf die linke Seite des Operators gesetzt, um zu verhindern, dass s == "" zu s = "" wird, was in der C-Syntax akzeptabel ist. .. und wahrscheinlich auch Golang. (siehe das erweiterte if)
Richard
-1

Dies wäre leistungsfähiger als das Trimmen der gesamten Zeichenfolge, da Sie nur nach mindestens einem einzelnen Nicht-Leerzeichen suchen müssen

// Strempty checks whether string contains only whitespace or not
func Strempty(s string) bool {
    if len(s) == 0 {
        return true
    }

    r := []rune(s)
    l := len(r)

    for l > 0 {
        l--
        if !unicode.IsSpace(r[l]) {
            return false
        }
    }

    return true
}
Brian Leishman
quelle
3
@Richard mag das sein, aber wenn Sie nach "Golang-Check, ob der String leer ist" oder ähnlichen Dingen googeln, ist dies die einzige Frage, die auftaucht. Für diese Leute ist dies also für sie, was keine beispiellose Sache ist Stapelaustausch
Brian Leishman
-1

Ich denke, der beste Weg ist, mit leeren Zeichenfolgen zu vergleichen

BenchmarkStringCheck1 prüft mit einer leeren Zeichenfolge

BenchmarkStringCheck2 prüft mit len ​​zero

Ich überprüfe mit der leeren und nicht leeren Zeichenkettenprüfung. Sie können sehen, dass das Überprüfen mit einer leeren Zeichenfolge schneller ist.

BenchmarkStringCheck1-4     2000000000           0.29 ns/op        0 B/op          0 allocs/op
BenchmarkStringCheck1-4     2000000000           0.30 ns/op        0 B/op          0 allocs/op


BenchmarkStringCheck2-4     2000000000           0.30 ns/op        0 B/op          0 allocs/op
BenchmarkStringCheck2-4     2000000000           0.31 ns/op        0 B/op          0 allocs/op

Code

func BenchmarkStringCheck1(b *testing.B) {
    s := "Hello"
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        if s == "" {

        }
    }
}

func BenchmarkStringCheck2(b *testing.B) {
    s := "Hello"
    b.ResetTimer()
    for n := 0; n < b.N; n++ {
        if len(s) == 0 {

        }
    }
}
Ketan Parmar
quelle
5
Ich denke dieser Beweis nichts. Da Ihr Computer beim Testen andere Dinge tut und der Unterschied zu gering ist, um zu sagen, dass einer schneller ist als der andere. Dies könnte darauf hinweisen, dass beide Funktionen für denselben Aufruf kompiliert wurden.
SR