Go'da boş bir dizeyi test etmenin en iyi yolu nedir?


262

Boş olmayan dizeleri (Go'da) test etmek için en iyi yöntem hangisidir?

if len(mystring) > 0 { }

Veya:

if mystring != "" { }

Veya başka bir şey?

Yanıtlar:


392

Her iki stil de Go'nun standart kitaplıklarında kullanılır.

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

bulunabilir strconvpaketinin: http://golang.org/src/pkg/strconv/atoi.go

if s != "" { ... }

bulunabilir encoding/jsonpaketinin: http://golang.org/src/pkg/encoding/json/encode.go

Her ikisi de deyimsel ve yeterince açık. Daha çok kişisel zevk ve netlik meselesidir.

Russ Cox bir golang-fındık parçasında yazıyor :

Kodu netleştiren.
Eleman x'e bakmak
üzereysem, x == 0 için bile genellikle len (s)> x yazıyorum , ama eğer
"bu belirli dize" umursam "s ==" yazma eğilimindeyim "".

Yetişkin bir derleyicinin
len (s) == 0 ve s == "" aynı, verimli koda derleyeceğini varsaymak mantıklıdır .
...

Kodu netleştirin.

İşaret edildiği gibi Timmmm cevabı , Go derleyici her iki durumda da aynı kod oluşturmak gelmez.


1
Bu yanıta katılmıyorum. Basitçe if mystring != "" { }iyi, tercih ve deyimsel yolu BUGÜN. Standart kütüphanenin başka türlü içermesinin nedeni, len(mystring) == 0optimizasyonun anlamlı olduğu 2010'dan önce yazılmış olmasıdır .
honzajde

13
@honzajde Sadece ifadenizi doğrulamaya çalıştım, ancak standart kütüphanede lenboş / boş olmayan dizeleri kontrol etmek için 1 yaşından küçük taahhütler bulundu . Brad Fitzpatrick'in bu taahhüdü gibi . Korkarım ki hala bir tat ve berraklık meselesi;)
ANisus

6
@honzajde Trol değil. Taahhütte 3 len anahtar kelime var. I atıfta len(v) > 0olarak h2_bundle.go (hat 2702). Golang.org/x/net/http2 adresinden üretildiği için otomatik olarak gösterilmiyor.
ANisus

2
Farkda noi ise yeni değil. Neden doğrudan bağlantı yayınlamıyorsunuz? Neyse. benim için yeterince dedektiflik işi ... Görmüyorum.
honzajde

6
@honzajde Endişelenme. Diğerlerinin h2_bundle.go dosyası için "Load diff" tıklamayı bileceğini varsayalım.
ANisus

30

Bu erken mikrooptimizasyon gibi görünüyor. Derleyici her iki durum için veya en azından bu ikisi için aynı kodu üretmekte serbesttir

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

ve

if s != "" { ... }

çünkü anlambilim açıkça eşittir.


1
Ancak, kabul edilen, gerçekten dizginin uygulanmasına bağlıdır ... Dizeler pascal gibi uygulanırsa len (s) o (1) 'de yürütülür ve eğer C gibi ise o (n)' dir. ya da her neyse, çünkü len () tamamlanmak üzere yürütmek zorundadır.
Richard

Derleyicinin bunu öngörüp öngörmediğini görmek için kod üretimine baktınız mı yoksa yalnızca bir derleyicinin bunu uygulayabileceğini mi öneriyorsunuz?
Michael Labbé

19

Uzunluğun kontrol edilmesi iyi bir yanıttır, ancak aynı zamanda yalnızca boşluk olan "boş" bir dizeyi de hesaba katabilirsiniz. "Teknik olarak" boş değil, ancak kontrol etmek isterseniz:

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

TrimSpaceorijinal dizeden yeni bir dize atar ve kopyalar, böylece bu yaklaşım ölçeğinde verimsizlikleri ortaya çıkarır.
Dai

@Dai kaynak koduna bakarak, ancak verilen sdize türünde s[0:i]yeni bir kopya döndürürse doğru olur . Go'da dizeler değiştirilemez, bu yüzden burada bir kopya oluşturması gerekiyor mu?
Michael Paesold

@MichaelPaesold Sağ - strings.TrimSpace( s )dize kesilmesi gerekmiyorsa yeni dize ayırmaya ve karakter kopyasına neden olmaz, ancak dize kesilmesi gerekiyorsa, fazladan kopya (boşluk karakteri olmadan) çağrılır.
Dai

1
"teknik olarak boş" sorudur.
Richard

gocriticLinter kullanarak önerir strings.TrimSpace(str) == ""yerine uzunluk çek.
y3sh

12

Boş alanların ve tüm önde gelen ve arkadaki beyaz alanların kaldırılması gerektiğini varsayarsak:

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

Çünkü :
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2


2
Neden bu varsayım var? Adam açıkça boş ipi anlatıyor. Bir dizedeki yalnızca ascii karakterleri istediğinizi varsayarak ve sonra da ascii olmayan tüm karakterleri kaldıran bir işlev eklediğinizi varsayalım.
Salvador Dali

1
Çünkü len (""), len ("") ve len ("") aynı şey değildir. Daha önce bunlardan birine başlattığı bir değişkenin gerçekten de "teknik olarak" boş olduğundan emin olmak istediğini varsayıyordum.
Edwinner

Aslında bu yazıdan tam olarak ihtiyacım olan şey bu. En az 1 boşluk olmayan karaktere sahip kullanıcı girişine ihtiyacım var ve bu tek satır net ve özlü. Tek yapmam gereken if koşulunu yapmak < 1+1
Shadoninja

7

Şu an itibariyle, Go derleyicisi her iki durumda da aynı kodu üretir, bu yüzden bir zevk meselesidir. GCCGo farklı kod üretir, ancak zar zor kimse kullanır, bu yüzden bu konuda endişe olmaz.

https://godbolt.org/z/fib1x1


1

Aşağıdaki gibi bir işlevi kullanmak daha temiz ve daha az hataya açık olacaktır:

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

0

Sadece daha eklemek için açıklama

Temelde performans testi nasıl yapılır.

Aşağıdaki kod ile test yaptım:

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

Ve sonuçlar:

% 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

Etkili varyantlar genellikle en hızlı süreye ulaşmaz ve varyant üst hızı arasında sadece minimum fark (yaklaşık 0.01ns / op) vardır.

Tam günlüğe bakarsam, denemeler arasındaki fark, karşılaştırma işlevleri arasındaki farktan daha büyüktür.

Ayrıca, BenchmarkStringCheckEq ve BenchmarkStringCheckNe veya BenchmarkStringCheckLen ve BenchmarkStringCheckLenGt arasında, ikinci varyantların 2 kez yerine 6 kat artması halinde bile ölçülebilir bir fark yok gibi görünüyor.

Değiştirilmiş test veya iç döngü ile testler ekleyerek eşit performans hakkında biraz güven kazanmayı deneyebilirsiniz. Bu daha hızlı:

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

Bu daha hızlı değil:

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

Her iki varyant genellikle ana testler arasındaki farktan daha hızlı veya daha yavaştır.

Ayrıca, ilgili dağıtıma sahip dize üreteci kullanarak test dizeleri (ss) oluşturmak da iyi olur. Değişken uzunlukları da var.

Bu nedenle, boş dizeyi test etmek için ana yöntemler arasındaki performans farkına güvenmiyorum.

Ve biraz güvenle söyleyebilirim, boş dizeyi hiç test etmemek, boş dizeyi test etmekten daha hızlıdır. Ayrıca boş karakter dizisini test etmek, 1 karakter dizisini (önek varyantı) test etmekten daha hızlıdır.


0

Resmi yönergelere göre ve performans açısından eşdeğer görünüyorlar ( ANisus cevabı ), s! = "" Sözdizimsel bir avantaj nedeniyle daha iyi olurdu. Değişken bir dize değilse s! = "" derleme zamanında başarısız olurken, len (s) == 0 diğer birkaç veri türü için de geçer.


CPU döngülerini saydım ve C derleyicisinin C ve Pascal dizelerinin yapısını derinlemesine anladığını ve derlediğini incelediğim bir zaman vardı ... dünyadaki tüm optimizasyonlarda bile len()bu kadar az iş gerektiriyor. Ancak, C'de yaptığımız bir şey, consts == "" ın C sözdiziminde kabul edilebilir olan s = "" olmasını önlemek için sol tarafı a ya da statik dizeyi operatörün sol tarafına koymaktı. .. ve muhtemelen golang da. (uzatılmışsa bakınız)
Richard

-1

Bu, tüm dizeyi kırpmaktan daha performanslı olacaktır, çünkü yalnızca en az bir tane boşluk olmayan karakteri kontrol etmeniz gerekir

// 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 olabilir, ancak "dizenin boş olup olmadığını golang kontrolü" veya benzeri şeyler için Google'da arama yapıldığında ortaya çıkan tek soru budur. Yığın Borsası
Brian Leishman

-1

Bence en iyi yol boş string ile karşılaştırmak

BenchmarkStringCheck1 boş dize ile kontrol ediyor

BenchmarkStringCheck2 sıfır ile kontrol ediyor

Boş ve boş olmayan dize denetimi ile kontrol ediyorum. Boş bir dize ile kontrol etmenin daha hızlı olduğunu görebilirsiniz.

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

kod

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
Bence bu kanıt hiçbir şey. Bilgisayarınız test ederken ve diğer şeyleri yaparken başka şeyler yapmak için küçük bir şey diğerinden daha hızlı olduğunu söylemek için küçüktür. Bu, her iki işlevin aynı çağrı için derlendiğini ima edebilir.
SR
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.