Goで空の文字列をテストする最良の方法は何ですか?


261

(Goで)空ではない文字列をテストするのに最適な方法(より理想的な方法)はどれですか。

if len(mystring) > 0 { }

または:

if mystring != "" { }

または、他の何か?

回答:


389

どちらのスタイルもGoの標準ライブラリ内で使用されます。

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

strconvパッケージにあります:http : //golang.org/src/pkg/strconv/atoi.go

if s != "" { ... }

encoding/jsonパッケージにあります:http : //golang.org/src/pkg/encoding/json/encode.go

どちらも慣用的であり、十分に明確です。それはより個人的な好みと明快さの問題です。

Russ Coxはgolang-nutsスレッドに書き込みます

コードを明確にするもの。
要素xを見ようとしている場合
、x == 0の場合でも、通常len(s)> x
と記述しますが、「この特定の文字列であるか」に関心がある場合、s == ""と記述しがちです。

成熟したコンパイラが
len(s)== 0とs == ""を同じ効率的なコードにコンパイルすると想定するのは妥当です。
...

コードを明確にします。

Timmmmの回答で指摘されているように、Goコンパイラはどちらの場合でも同じコードを生成します。


1
私はこの答えに同意しません。単にif mystring != "" { }今日、最もよく、好まれ、慣用的な方法です。それ以外に標準ライブラリが含まれている理由は、len(mystring) == 0最適化が理にかなっている2010年より前に作成されたためです。
honzajde

12
@honzajdeステートメントを検証しようとしましたが、len空の文字列/空でない文字列をチェックするために1年未満の標準ライブラリでコミットが見つかりました。Brad Fitzpatrickによるこのコミットのように。私はそれがまだ味と明快さの問題であると
思い

6
@honzajdeトローリングしないでください。コミットには3つのlenキーワードがあります。h2_bundle.go(行2702)len(v) > 0で参照していました。golang.org/x/net/http2から生成されているため、自動的には表示されません。
ANisus

2
それが差分のnoiであれば、それは新しいものではありません。直接リンクを貼ってみませんか?いずれかの方法。私には十分な探偵の仕事...私には見えません。
honzajde

6
@honzajdeご心配なく。他の人がh2_bundle.goファイルの「Load diff」をクリックする方法を知っていると思います。
ANisus

30

これは時期尚早のマイクロ最適化のようです。コンパイラーは、両方の場合、または少なくともこれら2つの場合に同じコードを自由に生成できます。

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

そして

if s != "" { ... }

セマンティクスが明らかに等しいためです。


1
ただし、これは実際には文字列の実装に依存します...文字列がpascalのように実装されている場合、len(s)はo(1)で実行され、Cの場合はo(n)です。またはlen()は最後まで実行する必要があるため。
リチャード

コード生成を見て、コンパイラーがこれを予期しているかどうか、またはコンパイラーがこれを実装できることを示唆しているだけですか?
MichaelLabbé18年

19

長さをチェックすることは良い答えですが、空白のみである「空の」文字列を考慮することもできます。「技術的に」空ではありませんが、確認したい場合:

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は、元の文字列から新しい文字列を割り当ててコピーするので、このアプローチは大規模な非効率をもたらします。
Dai

@Daiがソースコードを見ると、s文字列型s[0:i]が指定され、新しいコピーが返された場合にのみ当てはまります。Goでは文字列は不変なので、ここでコピーを作成する必要がありますか?
Michael Paesold

@MichaelPaesold Right- strings.TrimSpace( s )文字列をトリミングする必要がない場合、新しい文字列の割り当てと文字のコピーは行われませんが、文字列をトリミングする必要がある場合は、余分なコピー(空白文字なし)が呼び出されます。
Dai

1
「技術的に空」は問題です。
Richard

gocriticリンターは、使用することを提案しているstrings.TrimSpace(str) == ""長さのチェックを代わりに。
y3sh

12

空のスペースとすべての先頭と末尾の空白を削除する必要があると仮定します。

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

なぜなら:
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2


2
なぜこの仮定があるのですか?男は空の文字列についてはっきりと伝えます。文字列にASCII文字のみが必要であると想定して、ASCII以外の文字をすべて削除する関数を追加することで、同じことが言えます。
サルバドールダリ

1
len( "")、len( "")およびlen( "")は、実際には同じものではないためです。私は、彼が以前に初期化した変数が実際に「技術的に」空であることを確認したいと思っていました。
エドウィナー2017年

これは実際、私がこの投稿に必要なものです。ユーザー入力には少なくとも1つの非空白文字を含める必要があり、このワンライナーは明確で簡潔です。私がしなければならないのは、if条件を< 1+1にすることです
Shadoninja '10

7

現在のところ、Goコンパイラはどちらの場合も同じコードを生成するため、好みの問題です。GCCGoは異なるコードを生成しますが、ほとんど誰もがそれを使用しないので、私はそれについて心配しません。

https://godbolt.org/z/fib1x1



0

コメントに追加するだけです

主にパフォーマンステストの方法について。

私は次のコードでテストを行いました:

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

そして結果は:

% 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

実際、バリアントは通常、最速の時間に到達せず、バリアントの最高速度の差はごくわずかです(約0.01ns / op)。

そして、完全なログを見ると、試行間の差はベンチマーク関数間の差よりも大きくなります。

また、BenchmarkStringCheckEqとBenchmarkStringCheckNeまたはBenchmarkStringCheckLenとBenchmarkStringCheckLenGtの間には、後者のバリアントを2回ではなく6回含める必要があるとしても、測定可能な違いはないようです。

変更されたテストまたは内部ループを使用してテストを追加することにより、同等のパフォーマンスについてある程度の信頼を得ることができます。これはより高速です:

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

これは速くありません:

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

通常、両方のバリアントは、メインテスト間の違いよりも高速または低速です。

関連する分布を持つ文字列ジェネレータを使用してテスト文字列(ss)を生成することも良いでしょう。また、可変長もあります。

したがって、空の文字列をgoでテストするための主要なメソッド間のパフォーマンスの違いに自信はありません。

そして、私は自信を持って述べることができます。空の文字列をテストするよりも空の文字列をテストしない方が速いです。また、1文字の文字列(プレフィックスバリアント)をテストするよりも、空の文字列をテストする方が高速です。


0

公式ガイドラインに従い、パフォーマンスの観点から、それらは同等であるように見えます(ANisus回答。s!= ""の方が構文上の利点があるためより優れています。s!= ""は、変数が文字列でない場合、コンパイル時に失敗しますが、他のいくつかのデータ型ではlen(s)== 0が渡されます。


CPUサイクルをカウントして、Cコンパイラが生成したアセンブラを確認し、CおよびPascal文字列の構造を深く理解したときがありました...世界のすべての最適化でさえlen()、ほんの少しだけ余分な作業が必要です。ただし、Cで使用していたことの1つは、左側をaにキャストするconstか、演算子の左側に静的文字列を配置して、s == ""がs = ""になるのを防ぎ、C構文では許容されるようにしました。 ..そしておそらくgolangも。(拡張ifを参照)
Richard

-1

これは、少なくとも1つのスペース以外の文字が存在するかどうかを確認するだけでよいため、文字列全体をトリミングするよりもパフォーマンスが高くなります。

// 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かもしれませんが、「golang check if string is blank」または同様のものをグーグルすると、これが唯一出てくる質問なので、これらの人々にとってはこれが彼らにとってのものであり、前例のないことではありませんスタック交換
ブライアンリーシュマン

-1

最良の方法は空の文字列と比較することだと思います

BenchmarkStringCheck1は空の文字列でチェックしています

BenchmarkStringCheck2はlen zeroでチェックしています

空の文字列チェックと空でない文字列チェックを使用してチェックします。空の文字列を使用したチェックの方が高速であることがわかります。

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

コード

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 {

        }
    }
}

5
この証拠は何もないと思います。あなたのコンピュータはテストの際に他のことをするので、違いは小さいので、あるものは別のものより速いと言えます。これは、両方の関数が同じ呼び出しにコンパイルされたことを示唆している可能性があります。
SR
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.