Який найкращий спосіб перевірити порожній рядок у програмі Go?


260

Який метод найкращий (більш ідентичний) для тестування не порожніх рядків (у програмі Go)?

if len(mystring) > 0 { }

Або:

if mystring != "" { }

Або щось інше?

Відповіді:


388

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

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

Доцільно припустити, що зрілий компілятор буде компілювати
len (s) == 0 і s == "" в той же ефективний код.
...

Зробити код зрозумілим.

Як зазначено у відповіді Тімммм , компілятор Go генерує ідентичний код в обох випадках.


1
Я не згоден з цією відповіддю. Просто if mystring != "" { }це найкращий, вподобаний та ідіоматичний спосіб сьогодні. Причина, що містить стандартна бібліотека, полягає в тому, що вона була написана до 2010 року, коли len(mystring) == 0оптимізація мала сенс.
honzajde

12
@honzajde Просто спробував перевірити свою заяву, але знайшов комісії в стандартній бібліотеці віком lenдо 1 року, щоб перевірити порожні / непусті рядки. Як і цей вчинок Бреда Фіцпатріка. Боюся, це все-таки питання смаку та ясності;)
Анісус

6
@honzajde Не тролінг. У коміті є 3 ключові слова. Я мав на увазі len(v) > 0в h2_bundle.go (рядок 2702). Він не відображається автоматично, оскільки він генерується з golang.org/x/net/http2, я вважаю.
Анісус

2
Якщо це noi в diff, то це не нове. Чому ви не опублікуєте пряме посилання? Все одно. достатньо детективної роботи для мене ... Я цього не бачу.
honzajde

6
@honzajde Не хвилюйся. Я припускаю, що інші знатимуть, як натиснути "Завантажити" для файлу h2_bundle.go.
Анісус

30

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

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

і

if s != "" { ... }

тому що семантика явно рівна.


1
погоджено, однак, це дійсно залежить від реалізації рядка ... Якщо рядки реалізовані як pascal, тоді 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рядок типу, s[0:i]поверне нову копію. Рядки незмінні в Go, тому чи потрібно тут створювати копію?
Майкл Пейсолд

@MichaelPaesold Right - strings.TrimSpace( s )не спричинить виділення нового рядка та копіювання символів, якщо рядку не потрібна обрізка, але якщо рядку потрібно обрізка, тоді буде запущена додаткова копія (без символів пробілу).
Дай

1
"технічно порожнє" - це питання.
Річард

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 ("") - це не одне і те ж саме. Я припускав, що він хотів переконатися, що змінна, яку він ініціалізував до однієї з них раніше, справді все ще залишається "технічно" порожньою.
Едвіннер

Це насправді саме те, що мені було потрібно з цієї посади. Мені потрібно, щоб користувальницький ввід мав щонайменше 1 непробільний символ, і ця однолінійка чітка та лаконічна. Все, що мені потрібно зробити, - це зробити умовою < 1+1
Шадоніня

7

На сьогодні компілятор Go генерує ідентичний код в обох випадках, тому це питання смаку. GCCGo генерує інший код, але ледве хто не використовує його, тому я б не турбувався про це.

https://godbolt.org/z/fib1x1


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, навіть якщо останні варіанти повинні включати 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 рядовий рядок (варіант префікса).


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
@ Richard, що може бути, але коли Googling для "golang перевіряє, чи рядок порожній" чи подібні речі, це єдине питання, яке виникає, тому для тих людей це для них, що не є безпрецедентною справою робити Обмін
стеками

-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
Я думаю, що цей доказ нічого. Оскільки ваш комп'ютер робить інші речі при тестуванні і різниця - це мало, щоб сказати, що один швидший від іншого. Це може натякати, що обидві функції були зібрані для одного виклику.
SR
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.