Qual è il metodo migliore (più idomatic) per testare stringhe non vuote (in Go)?
if len(mystring) > 0 { }
O:
if mystring != "" { }
O qualcos'altro?
Risposte:
Entrambi gli stili vengono utilizzati all'interno delle librerie standard di Go.
if len(s) > 0 { ... }
può essere trovato nel strconv
pacchetto: http://golang.org/src/pkg/strconv/atoi.go
if s != "" { ... }
può essere trovato nel encoding/json
pacchetto: 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.
len(v) > 0
in h2_bundle.go (riga 2702). Non viene mostrato automaticamente poiché viene generato da golang.org/x/net/http2, credo.
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.
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!")
}
}
TrimSpace
allocherà e copierà una nuova stringa dalla stringa originale, quindi questo approccio introdurrà inefficienze su larga scala.
s
è di tipo stringa, s[0:i]
restituisse una nuova copia. Le stringhe non sono modificabili in Go, quindi è necessario crearne una copia qui?
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).
gocritic
linter suggerisce di utilizzare al strings.TrimSpace(str) == ""
posto del controllo della lunghezza.
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
< 1
+1
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.
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.
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 const
o 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)
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
}
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).
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
}
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 {
}
}
}
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) == 0
ottimizzazione aveva senso.