ฉันจะรับจำนวนอักขระของสตริงใน Go ได้อย่างไร?
ตัวอย่างเช่นถ้าฉันมีสตริงวิธีการที่ควรจะกลับ"hello"
5
ฉันเห็นว่าlen(str)
คืนค่าจำนวนไบต์ไม่ใช่จำนวนตัวอักษรดังนั้นlen("£")
ส่งคืน 2 แทน 1 เนื่องจาก£ถูกเข้ารหัสด้วยสองไบต์ใน UTF-8
ฉันจะรับจำนวนอักขระของสตริงใน Go ได้อย่างไร?
ตัวอย่างเช่นถ้าฉันมีสตริงวิธีการที่ควรจะกลับ"hello"
5
ฉันเห็นว่าlen(str)
คืนค่าจำนวนไบต์ไม่ใช่จำนวนตัวอักษรดังนั้นlen("£")
ส่งคืน 2 แทน 1 เนื่องจาก£ถูกเข้ารหัสด้วยสองไบต์ใน UTF-8
คำตอบ:
คุณสามารถลองRuneCountInString
จากแพ็คเกจ utf8
ส่งคืนจำนวนอักษรรูนใน p
ดังที่แสดงในบทนี้ : ความยาวของ "โลก" อาจเป็น 6 (เมื่อเขียนเป็นภาษาจีน: "世界") แต่จำนวนรูนของมันคือ 2:
package main
import "fmt"
import "unicode/utf8"
func main() {
fmt.Println("Hello, 世界", len("世界"), utf8.RuneCountInString("世界"))
}
Phrozenเพิ่มความคิดเห็น :
ที่จริงคุณสามารถทำได้len()
ผ่านรูโดยเพียงแค่พิมพ์แคสติ้ง จะพิมพ์
len([]rune("世界"))
2
ที่ leats ใน Go 1.3
และด้วยCL 108985 (พฤษภาคม 2018 สำหรับ Go 1.11) ได้len([]rune(string))
รับการปรับปรุง (แก้ไขปัญหา 24923 )
คอมไพเลอร์ตรวจพบlen([]rune(string))
รูปแบบโดยอัตโนมัติและแทนที่ด้วยสำหรับการเรียก r: = range
เพิ่มฟังก์ชั่นรันไทม์ใหม่เพื่อนับอักษรรูนในสตริง แก้ไขคอมไพเลอร์เพื่อตรวจหารูปแบบ
len([]rune(string))
และแทนที่ด้วยฟังก์ชันรันไทม์นับใหม่ของ rune
RuneCount/lenruneslice/ASCII 27.8ns ± 2% 14.5ns ± 3% -47.70% (p=0.000 n=10+10)
RuneCount/lenruneslice/Japanese 126ns ± 2% 60ns ± 2% -52.03% (p=0.000 n=10+10)
RuneCount/lenruneslice/MixedLength 104ns ± 2% 50ns ± 1% -51.71% (p=0.000 n=10+9)
Stefan Steigerชี้ไปที่โพสต์บล็อก "การทำให้เป็นข้อความปกติใน Go "
ตัวละครคืออะไร?
ในฐานะที่ได้รับการกล่าวถึงในการโพสต์บล็อกสตริง , ตัวละครที่สามารถขยายรูนหลาย
ตัวอย่างเช่น 'e
' และ '◌́◌́' (เฉียบพลัน "\ u0301") สามารถรวมกันเป็นรูปแบบ 'é' ("e\u0301
" ใน NFD) รูทั้งสองนี้เข้าด้วยกันเป็นตัวละครเดียว
คำจำกัดความของตัวละครอาจแตกต่างกันไปขึ้นอยู่กับแอพพลิเคชั่น
สำหรับการทำให้เป็นมาตรฐานเราจะนิยามมันเป็น:
- ลำดับของอักษรรูนที่ขึ้นต้นด้วย Starter
อัลกอริทึมการทำให้เป็นมาตรฐานจะประมวลผลอักขระหนึ่งตัวในเวลาเดียวกัน
เมื่อใช้แพ็คเกจและIter
ชนิดของมันจำนวน "อักขระ" ที่แท้จริงจะเป็น:
package main
import "fmt"
import "golang.org/x/text/unicode/norm"
func main() {
var ia norm.Iter
ia.InitString(norm.NFKD, "école")
nc := 0
for !ia.Done() {
nc = nc + 1
ia.Next()
}
fmt.Printf("Number of chars: %d\n", nc)
}
ที่นี่ใช้รูปแบบ Unicode Normalization NFKD "Decomposition ความเข้ากันได้"
คำตอบของOliverชี้ไปที่การแบ่งส่วนข้อความ UNICODEเป็นวิธีเดียวที่จะกำหนดขอบเขตเริ่มต้นที่เชื่อถือได้ระหว่างองค์ประกอบข้อความที่สำคัญบางอย่าง: อักขระที่ผู้ใช้รับรู้คำและประโยค
เพื่อที่คุณจะต้องมีห้องสมุดภายนอกเช่นRivo / unisegซึ่งจะแบ่งส่วน Unicode Text
ซึ่งจะนับเป็น " grapheme cluster " โดยที่หลาย ๆ รหัสคะแนนอาจรวมกันเป็นอักขระที่ผู้ใช้รับรู้
package uniseg
import (
"fmt"
"github.com/rivo/uniseg"
)
func main() {
gr := uniseg.NewGraphemes("👍🏼!")
for gr.Next() {
fmt.Printf("%x ", gr.Runes())
}
// Output: [1f44d 1f3fc] [21]
}
สองรูปแบบแม้ว่าจะมีอักษรรูนสาม (คะแนนรหัส Unicode)
คุณสามารถดูตัวอย่างอื่น ๆ ใน " วิธีจัดการสตริงใน GO เพื่อย้อนกลับได้อย่างไร "
👩🏾🦰 เพียงอย่างเดียวคือหนึ่งกราฟ แต่จากยูนิโคดถึงโค้ดตัวแปลงคะแนนรูน 4:
มีวิธีรับรูนที่ไม่มีแพ็คเกจโดยแปลงสตริงเป็น [] rune เป็นlen([]rune(YOUR_STRING))
:
package main
import "fmt"
func main() {
russian := "Спутник и погром"
english := "Sputnik & pogrom"
fmt.Println("count of bytes:",
len(russian),
len(english))
fmt.Println("count of runes:",
len([]rune(russian)),
len([]rune(english)))
}
จำนวนไบต์ 30 16
นับรูน 16 16
ขึ้นอยู่กับคำจำกัดความของคุณว่า "ตัวละคร" คืออะไร หาก "rune เท่ากับตัวอักษร" ก็โอเคสำหรับงานของคุณ (โดยทั่วไปไม่ใช่) คำตอบของ VonC นั้นเหมาะสำหรับคุณ ไม่เช่นนั้นควรสังเกตว่ามีบางสถานการณ์ที่จำนวนอักษรรูนในสตริง Unicode เป็นค่าที่น่าสนใจ และแม้ในสถานการณ์เหล่านั้นมันจะดีกว่าถ้าเป็นไปได้สรุปจำนวนในขณะที่ "traversing" สตริงขณะที่รูนถูกประมวลผลเพื่อหลีกเลี่ยงการเพิ่มความพยายามถอดรหัส UTF-8 เป็นสองเท่า
String
's .length()
วิธีไม่กลับจำนวนตัวอักษรอย่างใดอย่างหนึ่ง ไม่ไม่โกโก้NSString
's -length
วิธี เพียงแค่คืนค่าจำนวน UTF-16 แต่จำนวน codepoints ที่แท้จริงนั้นไม่ค่อยได้ใช้เพราะต้องใช้เวลาเชิงเส้นในการนับ
หากคุณจำเป็นต้องคำนึงถึงกลุ่มของกราฟให้ใช้ regexp หรือ unicode module การนับจำนวนจุดโค้ด (รูน) หรือไบต์ก็เป็นสิ่งจำเป็นสำหรับ validaiton เนื่องจากความยาวของกลุ่มกราฟจะไม่ จำกัด หากคุณต้องการกำจัดลำดับที่ยาวมาก ๆ ให้ตรวจสอบว่าลำดับนั้นสอดคล้องกับรูปแบบข้อความที่ปลอดภัยหรือไม่
package main
import (
"regexp"
"unicode"
"strings"
)
func main() {
str := "\u0308" + "a\u0308" + "o\u0308" + "u\u0308"
str2 := "a" + strings.Repeat("\u0308", 1000)
println(4 == GraphemeCountInString(str))
println(4 == GraphemeCountInString2(str))
println(1 == GraphemeCountInString(str2))
println(1 == GraphemeCountInString2(str2))
println(true == IsStreamSafeString(str))
println(false == IsStreamSafeString(str2))
}
func GraphemeCountInString(str string) int {
re := regexp.MustCompile("\\PM\\pM*|.")
return len(re.FindAllString(str, -1))
}
func GraphemeCountInString2(str string) int {
length := 0
checked := false
index := 0
for _, c := range str {
if !unicode.Is(unicode.M, c) {
length++
if checked == false {
checked = true
}
} else if checked == false {
length++
}
index++
}
return length
}
func IsStreamSafeString(str string) bool {
re := regexp.MustCompile("\\PM\\pM{30,}")
return !re.MatchString(str)
}
var
ฟังก์ชั่นนอก
มีหลายวิธีในการรับความยาวสตริง:
package main
import (
"bytes"
"fmt"
"strings"
"unicode/utf8"
)
func main() {
b := "这是个测试"
len1 := len([]rune(b))
len2 := bytes.Count([]byte(b), nil) -1
len3 := strings.Count(b, "") - 1
len4 := utf8.RuneCountInString(b)
fmt.Println(len1)
fmt.Println(len2)
fmt.Println(len3)
fmt.Println(len4)
}
ฉันควรจะชี้ให้เห็นว่าไม่มีคำตอบใดที่ให้จนถึงจำนวนตัวอักษรตามที่คุณคาดหวังโดยเฉพาะอย่างยิ่งเมื่อคุณจัดการกับอิโมจิ (แต่ยังมีบางภาษาเช่นไทยเกาหลีหรืออาหรับ) คำแนะนำของ VonCจะแสดงผลดังนี้:
fmt.Println(utf8.RuneCountInString("🏳️🌈🇩🇪")) // Outputs "6".
fmt.Println(len([]rune("🏳️🌈🇩🇪"))) // Outputs "6".
นั่นเป็นเพราะวิธีการเหล่านี้นับคะแนนรหัส Unicode เท่านั้น มีอักขระจำนวนมากซึ่งสามารถประกอบด้วยจุดรหัสหลายจุด
เหมือนกับการใช้แพ็คเกจการทำให้เป็นมาตรฐาน :
var ia norm.Iter
ia.InitString(norm.NFKD, "🏳️🌈🇩🇪")
nc := 0
for !ia.Done() {
nc = nc + 1
ia.Next()
}
fmt.Println(nc) // Outputs "6".
การทำให้เป็นมาตรฐานไม่เหมือนกับการนับตัวอักษรจริง ๆ และตัวอักษรจำนวนมากไม่สามารถทำให้เป็นมาตรฐานได้เทียบเท่ากับการใช้รหัสเดียว
คำตอบของ masakielastic เข้ามาใกล้ แต่จัดการกับการปรับเปลี่ยนเท่านั้น (ธงรุ้งมีตัวปรับแต่งซึ่งไม่นับเป็นจุดรหัสของตัวเอง):
fmt.Println(GraphemeCountInString("🏳️🌈🇩🇪")) // Outputs "5".
fmt.Println(GraphemeCountInString2("🏳️🌈🇩🇪")) // Outputs "5".
วิธีที่ถูกต้องในการแยกสตริง Unicode เข้า (ผู้ใช้รับรู้) ตัวละครกลุ่มคืออักษรจะถูกกำหนดไว้ในมาตรฐาน Unicode ภาคผนวก # กฎระเบียบที่สามารถพบได้ในมาตรา 3.1.1 github.com/rivo/unisegแพคเกจการดำเนินการตามกฎเหล่านี้เพื่อให้คุณสามารถตรวจสอบหมายเลขที่ถูกต้องของตัวละครในสตริง:
fmt.Println(uniseg.GraphemeClusterCount("🏳️🌈🇩🇪")) // Outputs "2".
ฉันพยายามทำให้การฟื้นฟูเป็นปกติเร็วขึ้นเล็กน้อย:
en, _ = glyphSmart(data)
func glyphSmart(text string) (int, int) {
gc := 0
dummy := 0
for ind, _ := range text {
gc++
dummy = ind
}
dummy = 0
return gc, dummy
}