Каков наилучший способ проверить пустую строку в Go?

Какой метод лучше (более идоматический) для тестирования непустых строк (в Go)?

if len(mystring) > 0 { }

Или:

if mystring != "" { }

Или что-то еще?

Ответ 1

Оба стиля используются в стандартных библиотеках 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

Оба идиоматичны и достаточно понятны. Это скорее вопрос личного вкуса и ясности.

Рассел Кокс пишет в теме о голангах:

Тот, который делает код понятным.
Если я собираюсь посмотреть на элемент x, я обычно пишу
len (s)> x, даже для x == 0, но если я забочусь о
msgstr "это конкретная строка" Я склонен писать s == "".

Разумно предположить, что зрелый компилятор будет компилировать
len (s) == 0 и s == "" в один и тот же эффективный код.
...

Сделайте код понятным.

Как указано в ответе Timmmm, компилятор Go генерирует идентичный код в обоих случаях.

Ответ 2

Это, по-видимому, преждевременная микрооптимизация. Компилятор может создавать один и тот же код для обоих случаев или, по крайней мере, для этих двух

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

и

if s != "" { ... }

потому что семантика явно равна.

Ответ 3

Проверка длины - хороший ответ, но вы также можете указать "пустую" строку, которая также является только пробелом. Не "технически" пуст, но если вы хотите проверить:

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

Ответ 4

Предполагая, что пустые места и все начальные и конечные пробелы должны быть удалены:

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

Потому что:
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2

Ответ 5

На данный момент компилятор Go генерирует идентичный код в обоих случаях, так что это дело вкуса. GCCGo генерирует другой код, но почти никто не использует его, поэтому я бы не беспокоился об этом.

https://godbolt.org/z/fib1x1

Ответ 6

Просто чтобы добавить больше к комментарию

В основном о том, как проводить тестирование производительности.

Я провел тестирование со следующим кодом:

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,01 нс/оп).

И если я посмотрю полный журнал, разница между попытками будет больше, чем разница между контрольными функциями.

Кроме того, не существует какой-либо ощутимой разницы между BenchmarkStringCheckEq и BenchmarkStringCheckNe или BenchmarkStringCheckLen и BenchmarkStringCheckLenGt даже если последние варианты должны быть выполнены c 6 раз вместо 2 раз.

Вы можете попытаться получить некоторую уверенность в равной производительности, добавив тесты с измененным тестом или внутренним циклом. Это быстрее:

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), используя генератор строк с соответствующим распределением. И иметь переменную длину тоже.

Так что я не уверен в разнице в производительности между основными методами для проверки пустой строки в го.

И я могу с уверенностью сказать, что быстрее вообще не тестировать пустую строку, чем тестировать пустую строку. А также быстрее протестировать пустую строку, чем 1 строку символов (префиксный вариант).

Ответ 7

Было бы чище и менее подвержено ошибкам использовать функцию, подобную приведенной ниже:

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

Ответ 8

Это будет более производительным, чем обрезка всей строки, поскольку вам нужно проверить только один существующий непробельный символ.

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

Ответ 9

Я думаю, что лучше всего сравнивать с пустой строкой

BenchmarkStringCheck1 проверяется с пустой строкой

BenchmarkStringCheck2 проверяет с нулевым длиной

Я проверяю с пустой и непустой строкой проверки. Вы можете видеть, что проверка с пустой строкой быстрее.

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 {

        }
    }
}