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

261

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

if len(mystring) > 0 { }

Или:

if mystring != "" { }

Или что-то другое?

Ричард
источник

Ответы:

390

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

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

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

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

Anisus
источник
1
Я не согласен с этим ответом. Просто if mystring != "" { }это лучший, предпочтительный и идиоматический способ СЕГОДНЯ. Причина, по которой стандартная библиотека содержит иное, заключается в том, что она была написана до 2010 года, когда len(mystring) == 0оптимизация имела смысл.
honzajde
12
@honzajde Только что попытался проверить ваше утверждение, но обнаружил коммиты в стандартной библиотеке менее 1 года, использующие lenдля проверки пустые / непустые строки. Как этот коммит Брэда Фицпатрика. Боюсь, это все еще вопрос вкуса и ясности;)
ANisus
6
@honzajde Не троллинг. В коммите 3 ключевых слова. Я имел в виду len(v) > 0в h2_bundle.go (строка 2702). Я полагаю, что он не отображается автоматически, поскольку он генерируется с golang.org/x/net/http2.
ANisus
2
Если это не так, то это не ново. Почему вы не публикуете прямую ссылку? В любом случае. достаточно детективной работы для меня ... я не вижу этого.
honzajde
6
@honzajde Не беспокойся. Я предполагаю, что другие будут знать, как нажать «Загрузить diff» для файла h2_bundle.go.
ANisus
30

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

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

и

if s != "" { ... }

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

ZZZZ
источник
1
согласен, однако, это действительно зависит от реализации строки ... Если строки реализованы как паскаль, то len (s) выполняется в o (1), а если как C, то это o (n). или что угодно, так как len () должен выполняться до завершения.
Ричард
Вы смотрели на генерацию кода, чтобы увидеть, ожидает ли это компилятор, или вы только предполагаете, что компилятор может это реализовать?
Майкл Лаббе
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, глядя на исходный код, это будет верно только в том случае, если данный sтип имеет тип string и s[0:i]возвращает новую копию. Строки являются неизменяемыми в Go, поэтому нужно ли здесь создавать копию?
Майкл Пезольд
@MichaelPaesold Right - strings.TrimSpace( s )не приведет к выделению новой строки и копированию символов, если строка не нуждается в обрезке, но если строка требует обрезки, будет вызвана дополнительная копия (без пробельных символов).
Дай
1
«технически пустой» вопрос.
Ричард
gocriticЛИНТЕР предлагает использовать strings.TrimSpace(str) == ""вместо проверки длины.
3
12

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

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

Так как :
len("") // is 0
len(" ") // one empty space is 1
len(" ") // two empty spaces is 2

Edwinner
источник
2
Почему у вас есть это предположение? Парень четко рассказывает о пустой строке. То же самое вы можете сказать, предполагая, что вы хотите, чтобы в строке были только символы ascii, а затем добавили функцию, которая удаляет все символы, не относящиеся к ascii.
Сальвадор Дали
1
Поскольку len (""), len ("") и len ("") - это не одно и то же. Я предполагал, что он хотел удостовериться, что переменная, которую он инициализировал для одной из этих ранних, действительно все еще "технически" пуста.
Edwinner
На самом деле это именно то, что мне нужно из этого поста. Мне нужно, чтобы пользовательский ввод имел как минимум 1 непробельный символ, и эта строка понятна и лаконична. Все, что мне нужно сделать, это выполнить условие if < 1+1
Shadoninja
7

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

https://godbolt.org/z/fib1x1

Timmmm
источник
1

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

func empty(s string) bool {
    return len(strings.TrimSpace(s)) == 0
}
Яннис Серметзиадис
источник
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,01 нс / оп) между максимальной скоростью варианта.

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

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

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

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

Маркус Линнала
источник
0

Согласно официальным правилам и с точки зрения производительности они выглядят эквивалентно ( ответ ANisus ), s! = "" Будет лучше из-за синтаксического преимущества. s! = "" потерпит неудачу во время компиляции, если переменная не является строкой, в то время как len (s) == 0 пройдет для нескольких других типов данных.

Янис Виксне
источник
Было время, когда я подсчитывал циклы ЦП и просматривал ассемблер, созданный компилятором C, и глубоко понимал структуру строк C и Pascal ... даже при всех оптимизациях в мире len()требуется лишь немного больше работы. ОДНАКО, одна вещь, которую мы делали в C, была приведена с левой стороны к a constили помещена статическая строка в левую часть оператора, чтобы s == "" не стал s = "", что в синтаксисе C приемлемо. .. и, вероятно, тоже Голанг. (см. расширенный вариант)
Ричард
-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
@ Ричард, что может быть, но когда Googling для «golang check, если строка пуста» или тому подобное, это единственный вопрос, который возникает, так что для тех людей это для них, что не является беспрецедентной вещью, которую нужно делать на Обмен стеками
Брайан Лейшман
-1

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

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

BenchmarkStringCheck2 проверяет с нулем len

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

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
Я думаю, что это доказательство ничего. Поскольку ваш компьютер делает другие вещи, когда тестирование отличается от малого, можно сказать, что одно быстрее другого. Это может указывать на то, что обе функции были скомпилированы для одного и того же вызова.
СР,