Qual è il modo migliore per testare una stringa vuota in Go?


288

Qual è il metodo migliore (più idomatic) per testare stringhe non vuote (in Go)?

if len(mystring) > 0 { }

O:

if mystring != "" { }

O qualcos'altro?

Risposte:


436

Entrambi gli stili vengono utilizzati all'interno delle librerie standard di Go.

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

può essere trovato nel strconvpacchetto: http://golang.org/src/pkg/strconv/atoi.go

if s != "" { ... }

può essere trovato nel encoding/jsonpacchetto: http://golang.org/src/pkg/encoding/json/encode.go

Entrambi sono idiomatici e sono abbastanza chiari. È più una questione di gusto personale e di chiarezza.

Russ Cox scrive in un thread golang-nuts :

Quello che rende chiaro il codice.
Se sto per guardare l'elemento x di solito scrivo
len (s)> x, anche per x == 0, ma se mi interessa
"è questa stringa specifica" tendo a scrivere s == "".

È ragionevole presumere che un compilatore maturo compilerà
len (s) == 0 e s == "" nello stesso codice efficiente.
...

Rendi il codice chiaro.

Come sottolineato nella risposta di Timmmm , il compilatore Go genera codice identico in entrambi i casi.


1
Non sono d'accordo con questa risposta. Semplicemente if mystring != "" { }è il modo migliore, preferito e idiomatico OGGI. Il motivo per cui la libreria standard contiene il contrario è perché è stata scritta prima del 2010, quando l' len(mystring) == 0ottimizzazione aveva senso.
honzajde

15
@honzajde Ho appena provato a convalidare la tua dichiarazione, ma ho trovato commit nella libreria standard di meno di 1 anno che utilizzavano lenper controllare stringhe vuote / non vuote. Come questo commit di Brad Fitzpatrick. Temo che sia ancora una questione di gusto e chiarezza;)
ANisus

6
@honzajde Non trolling. Ci sono 3 parole chiave len nel commit. Mi riferivo a len(v) > 0in h2_bundle.go (riga 2702). Non viene mostrato automaticamente poiché viene generato da golang.org/x/net/http2, credo.
ANisus

2
Se è noi nel diff, allora non è nuovo. Perché non pubblichi un link diretto? Comunque. abbastanza lavoro investigativo per me ... non lo vedo.
honzajde

32

Questa sembra essere una microottimizzazione prematura. Il compilatore è libero di produrre lo stesso codice per entrambi i casi o almeno per questi due

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

e

if s != "" { ... }

perché la semantica è chiaramente uguale.


1
d'accordo, tuttavia, dipende davvero dall'implementazione di string ... Se le stringhe sono implementate come pascal allora len (s) viene eseguita in o (1) e se come C allora è o (n). o qualsiasi altra cosa, poiché len () deve essere eseguito fino al completamento.
Richard

Hai esaminato la generazione del codice per vedere se il compilatore lo prevede o stai solo suggerendo che un compilatore potrebbe implementarlo?
Michael Labbé

22

Controllare la lunghezza è una buona risposta, ma potresti anche tenere conto di una stringa "vuota" che è anche solo uno spazio bianco. Non "tecnicamente" vuoto, ma se vuoi controllare:

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!")
  }
}

TrimSpaceallocherà e copierà una nuova stringa dalla stringa originale, quindi questo approccio introdurrà inefficienze su larga scala.
Dai

@Dai guardando il codice sorgente, sarebbe vero solo se, dato sè di tipo stringa, s[0:i]restituisse una nuova copia. Le stringhe non sono modificabili in Go, quindi è necessario crearne una copia qui?
Michael Paesold

@MichaelPaesold Right - strings.TrimSpace( s )non causerà una nuova allocazione di stringhe e una copia di caratteri se la stringa non necessita di essere tagliata, ma se la stringa ha bisogno di essere tagliata, verrà richiamata la copia extra (senza caratteri di spazio).
Dai

1
"tecnicamente vuoto" è la domanda.
Richard

Il gocriticlinter suggerisce di utilizzare al strings.TrimSpace(str) == ""posto del controllo della lunghezza.
y3sh

14

Supponendo che gli spazi vuoti e tutti gli spazi bianchi iniziali e finali debbano essere rimossi:

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

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


2
Perché hai questa supposizione? Il ragazzo racconta chiaramente della stringa vuota. Allo stesso modo puoi dirlo, supponendo che tu voglia solo caratteri ASCII in una stringa e quindi aggiungere una funzione che rimuove tutti i caratteri non ASCII.
Salvador Dalì,

1
Perché len (""), len ("") e len ("") non sono la stessa cosa in go. Supponevo che volesse assicurarsi che una variabile che aveva inizializzato su una di quelle precedenti fosse effettivamente ancora "tecnicamente" vuota.
Edwinner

Questo è esattamente ciò di cui avevo bisogno da questo post. Ho bisogno che l'input dell'utente abbia almeno 1 carattere diverso da uno spazio bianco e questa riga è chiara e concisa. Tutto quello che devo fare è rendere la condizione if < 1+1
Shadoninja

11

A partire da ora, il compilatore Go genera codice identico in entrambi i casi, quindi è una questione di gusti. GCCGo genera codice diverso, ma quasi nessuno lo usa, quindi non me ne preoccuperei.

https://godbolt.org/z/fib1x1


2

Secondo le linee guida ufficiali e dal punto di vista delle prestazioni sembrano equivalenti ( risposta ANisus ), la s! = "" Sarebbe migliore per un vantaggio sintattico. s! = "" fallirà in fase di compilazione se la variabile non è una stringa, mentre len (s) == 0 passerà per molti altri tipi di dati.


C'è stato un tempo in cui contavo i cicli della CPU e recensivo l'assemblatore prodotto dal compilatore C e capivo a fondo la struttura delle stringhe C e Pascal ... anche con tutte le ottimizzazioni del mondo len()richiede solo quel po 'di lavoro in più. TUTTAVIA, una cosa che eravamo soliti fare in C era lanciare il lato sinistro in a consto mettere la stringa statica sul lato sinistro dell'operatore per evitare che s == "" diventasse s = "" che nella sintassi C è accettabile. .. e probabilmente anche golang. (vedi l'estensione if)
Richard

0

Sarebbe più pulito e meno soggetto a errori utilizzare una funzione come quella seguente:

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

0

Solo per aggiungere altro al commento

Principalmente su come eseguire i test delle prestazioni.

Ho fatto dei test con il seguente codice:

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)
    }
}

E i risultati sono stati:

% 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

Effettivamente le varianti di solito non raggiungono il tempo più veloce e c'è solo una differenza minima (circa 0,01 ns / op) tra la velocità massima delle varianti.

E se guardo il registro completo, la differenza tra i tentativi è maggiore della differenza tra le funzioni di benchmark.

Inoltre non sembra esserci alcuna differenza misurabile tra BenchmarkStringCheckEq e BenchmarkStringCheckNe o BenchmarkStringCheckLen e BenchmarkStringCheckLenGt anche se queste ultime varianti dovrebbero aumentare c 6 volte invece di 2 volte.

Puoi provare a ottenere un po 'di sicurezza sulla parità di prestazioni aggiungendo test con test modificato o loop interno. Questo è più veloce:

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)
    }
}

Questo non è più veloce:

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)
    }
}

Entrambe le varianti sono generalmente più veloci o più lente della differenza tra i test principali.

Sarebbe anche utile generare stringhe di test utilizzando un generatore di stringhe con distribuzione pertinente. E hanno anche lunghezze variabili.

Quindi non ho alcuna fiducia nella differenza di prestazioni tra i metodi principali per testare una stringa vuota in go.

E posso affermare con una certa sicurezza che è più veloce non testare affatto una stringa vuota che testare una stringa vuota. Inoltre è più veloce testare una stringa vuota che testare 1 stringa di caratteri (variante del prefisso).


-2

Questo sarebbe più performante rispetto al taglio dell'intera stringa, dal momento che devi solo controllare almeno un singolo carattere non spazio esistente

// 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
}

3
@Richard può essere, ma quando si cerca su Google "golang controlla se la stringa è vuota" o cose simili, questa è l'unica domanda che viene fuori, quindi per quelle persone questo è per loro, il che non è una cosa senza precedenti da fare su Stack Exchange
Brian Leishman

-2

Penso che il modo migliore sia confrontare con una stringa vuota

BenchmarkStringCheck1 sta verificando con una stringa vuota

BenchmarkStringCheck2 sta verificando con len zero

Controllo con il controllo delle stringhe vuote e non vuote. Puoi vedere che il controllo con una stringa vuota è più veloce.

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

Codice

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 {

        }
    }
}

6
Penso che questa prova nulla. Dal momento che il tuo computer fa altre cose durante i test e la differenza è troppo piccola per dire che uno è più veloce di un altro. Ciò potrebbe suggerire che entrambe le funzioni sono state compilate con la stessa chiamata.
SR
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.