วิธีที่ดีที่สุดในการทดสอบสตริงว่างใน 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 ฉันมักจะเขียน
len (s)> x, แม้สำหรับ x == 0 แต่ถ้าฉันสนใจ
"มันเป็นสตริงที่เฉพาะเจาะจงนี้" ฉันมักจะเขียน s == ""

มีเหตุผลที่จะสมมติว่าคอมไพเลอร์ที่เป็นผู้ใหญ่จะรวบรวม
len (s) == 0 และ s == "" ให้เป็นรหัสเดียวกันและมีประสิทธิภาพ
...

ทำให้รหัสชัดเจน

ดังที่ระบุไว้ในคำตอบของ Timmmmคอมไพเลอร์ Go สร้างโค้ดเหมือนกันในทั้งสองกรณี


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 ไม่ต้องกังวล ฉันคิดว่าคนอื่นจะรู้วิธีคลิก "โหลดความแตกต่าง" สำหรับไฟล์ h2_bundle.go
ANISUS

30

ดูเหมือนว่าจะเป็นการทำให้หมดเวลาก่อนกำหนดขนาดเล็ก คอมไพเลอร์มีอิสระในการผลิตรหัสเดียวกันสำหรับทั้งสองกรณีหรืออย่างน้อยสำหรับทั้งสองนี้

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

และ

if s != "" { ... }

เพราะความหมายมีความชัดเจนเท่าเทียมกัน


1
ตกลงอย่างไรก็ตามมันขึ้นอยู่กับการใช้งานของสตริง ... หากสตริงถูกนำมาใช้เช่นปาสกาลแล้ว len (s) จะถูกดำเนินการใน o (1) และถ้าชอบ C แล้วมันเป็น o (n) หรืออะไรก็ตามตั้งแต่ len () ต้องดำเนินการให้เสร็จสมบูรณ์
ริชาร์ด

คุณได้ดูการสร้างรหัสเพื่อดูว่าคอมไพเลอร์คาดการณ์หรือไม่
Michael Labbé

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 จึงจำเป็นต้องสร้างสำเนาที่นี่หรือไม่
Michael Paesold

@MichaelPaesold Right - strings.TrimSpace( s )จะไม่ทำให้เกิดการจัดสรรสตริงใหม่และการคัดลอกอักขระหากสตริงไม่จำเป็นต้องตัดแต่ง แต่ถ้าสตริงต้องตัดแต่งแล้วสำเนาพิเศษ (โดยไม่มีอักขระช่องว่าง) จะถูกเรียก
ได

1
"ว่างเปล่าทางเทคนิค" เป็นคำถาม
ริชาร์ด

gocriticlinter แนะนำให้ใช้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 ทั้งหมด
Salvador Dali

1
เนื่องจาก len (""), len ("") และ len ("") ไม่เหมือนกันในขณะเดินทาง ฉันคิดว่าเขาต้องการให้แน่ใจว่าตัวแปรที่เขาได้เริ่มต้นกับหนึ่งในนั้นก่อนหน้านี้ยังคงว่างเปล่า "เทคนิค"
Edwinner

นี่คือสิ่งที่ฉันต้องการจากโพสต์นี้ ฉันต้องการอินพุตของผู้ใช้ที่จะมีตัวละครที่ไม่ใช่ช่องว่างอย่างน้อย 1 ตัวและซับในตัวนี้มีความชัดเจนและรัดกุม สิ่งที่ฉันต้องทำคือทำให้เงื่อนไข< 1+1
Shadoninja

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.01ns / op) ระหว่างความเร็วสูงสุดของตัวแปร

และถ้าฉันดูบันทึกเต็มความแตกต่างระหว่างความพยายามมากกว่าความแตกต่างระหว่างฟังก์ชั่นมาตรฐาน

นอกจากนี้ยังไม่มีความแตกต่างที่สามารถวัดได้ระหว่าง BenchmarkStringCheckEq และ BenchmarkStringCheckNe หรือ BenchmarkStringCheckLen และ BenchmarkStringCheckLenGt แม้ว่าตัวแปรหลังควรรวม 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 จะส่งผ่านสำหรับชนิดข้อมูลอื่น ๆ


มีบางครั้งที่ฉันนับจำนวนรอบ CPU และตรวจสอบแอสเซมเบลอร์ว่าคอมไพเลอร์ C ผลิตและเข้าใจโครงสร้างของสตริง C และ Pascal อย่างลึกซึ้ง ... แม้จะมีการเพิ่มประสิทธิภาพทั้งหมดในโลกlen()ก็ต้องใช้งานเพิ่มอีกเล็กน้อย อย่างไรก็ตามสิ่งหนึ่งที่เราเคยทำใน C ถูกโยนด้านซ้ายไปที่ a constหรือใส่สตริงแบบคงที่ที่ด้านซ้ายของผู้ประกอบการเพื่อป้องกัน s == "" จากการกลายเป็น s = "" ซึ่งในไวยากรณ์ C เป็นที่ยอมรับ .. และอาจ golang ด้วย (ดูส่วนขยายหาก)
Richard

-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 ตรวจสอบว่าสตริงว่างเปล่า" หรือสิ่งที่คล้ายกันนี้เป็นคำถามเดียวที่เกิดขึ้นดังนั้นสำหรับคนเหล่านี้นี่คือพวกเขาซึ่งไม่ได้เป็นสิ่งที่ไม่เคยทำมา กองแลกเปลี่ยน
Brian Leishman

-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.