網(wǎng)站首頁(yè) 編程語(yǔ)言 正文
Golang?統(tǒng)計(jì)字符串中數(shù)字字母數(shù)量的實(shí)現(xiàn)方法_Golang
作者:戀喵大鯉魚(yú) ? 更新時(shí)間: 2022-07-29 編程語(yǔ)言1.需求說(shuō)明
記錄一下項(xiàng)目對(duì)用戶 UGC 文本進(jìn)行字?jǐn)?shù)限制的具體實(shí)現(xiàn)。
不同的產(chǎn)品,出于種種原因,一般都會(huì)對(duì)用戶輸入的文本內(nèi)容做字?jǐn)?shù)限制。
- 出于產(chǎn)品定位,比如 140 字符限制的 Twitter,讓內(nèi)容保持簡(jiǎn)潔凝練,易于閱讀;
- 出于用戶的閱讀體驗(yàn),過(guò)多的文字會(huì)造成閱讀疲勞,合適的字?jǐn)?shù)能夠提高閱讀舒適度;
- 出于技術(shù)與成本的考慮,不設(shè)上限的 UGC 內(nèi)容會(huì)引發(fā)一些潛在的問(wèn)題,比如增加存儲(chǔ)的成本,降低檢索效率等。
回到自己的項(xiàng)目,是一個(gè)用戶發(fā)帖的業(yè)務(wù)場(chǎng)景。產(chǎn)品同學(xué)給到的要求是:
- 帖子名稱,限制在 25 個(gè)字;
- 帖子正文,限制在 1500 字;
- 關(guān)于字的說(shuō)明:1 個(gè)漢字為一個(gè)字,一個(gè) Emoji 表情相當(dāng)于 1 個(gè)字,2 個(gè)數(shù)字/英文字母相當(dāng)于 1 個(gè)字。
正常情況下,漢字,Emoji 字符,數(shù)字與英文字母都是單獨(dú)的字符。這里 2 個(gè)數(shù)字/英文算作 1 個(gè)字,所以在計(jì)算字符串長(zhǎng)度時(shí),不能夠使用 []rune 強(qiáng)轉(zhuǎn)后來(lái)獲取其長(zhǎng)度,而是需要統(tǒng)計(jì)出數(shù)字與英文字母的數(shù)量,再加上其他字符數(shù)量,作為其長(zhǎng)度。所以,要想實(shí)現(xiàn)產(chǎn)品同學(xué)的要求,關(guān)鍵是需要統(tǒng)計(jì)出用戶輸入文本中的數(shù)字與英文字母的數(shù)量。
2.實(shí)現(xiàn)
在 Golang,一般有兩種方法。
2.1 ASCII 碼值法
數(shù)字和英文字母的 ASCII 碼值我們是知道的,通過(guò)對(duì)原字符串遍歷,便可統(tǒng)計(jì)出數(shù)字/英文字母的數(shù)量。
// GetAlphanumericNumByASCII 根據(jù) ASCII 碼值獲取字母數(shù)字?jǐn)?shù)量。 func GetAlphanumericNumByASCII(s string) int { num := int(0) for i := 0; i < len(s); i++ { switch { case 48 <= s[i] && s[i] <= 57: // 數(shù)字 fallthrough case 65 <= s[i] && s[i] <= 90: // 大寫(xiě)字母 fallthrough case 97 <= s[i] && s[i] <= 122: // 小寫(xiě)字母 num++ default: } } return num } // 或者 // GetAlphanumericNumByASCIIV2 根據(jù) ASCII 碼值獲取字母數(shù)字?jǐn)?shù)量。 func GetAlphanumericNumByASCIIV2(s string) int { num := int(0) for _, c := range s { switch { case '0' <= c && c <= '9': fallthrough case 'a' <= c && c <= 'z': fallthrough case 'A' <= c && c <= 'Z': num++ default: } } return num }
2.2 正則表達(dá)式
我們可以利用 Golang 標(biāo)準(zhǔn)庫(kù)包 regexp 獲取指定表達(dá)式的字串?dāng)?shù)量。
// GetAlphanumericNumByRegExp 根據(jù)正則表達(dá)式獲取字母數(shù)字?jǐn)?shù)量。 func GetAlphanumericNumByRegExp(s string) int { rNum := regexp.MustCompile(`\d`) rLetter := regexp.MustCompile("[a-zA-Z]") return len(rNum.FindAllString(s, -1)) + len(rLetter.FindAllString(s, -1)) }
我們可以寫(xiě)個(gè)單測(cè)來(lái)驗(yàn)證下上面三個(gè)函數(shù)的正確性。
package string import "testing" func TestGetAlphanumericNumByASCII(t *testing.T) { type args struct { s string } tests := []struct { name string args args want int }{ { name: "包含數(shù)字", args: args{"108條梁山好漢"}, want: 3, }, { name: "包含字母", args: args{"一百條梁山man"}, want: 3, }, { name: "包含數(shù)字與字母", args: args{"108條梁山man"}, want: 6, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := GetAlphanumericNumByASCII(tt.args.s); got != tt.want { t.Errorf("GetAlphanumericNumByASCII() = %v, want %v", got, tt.want) } }) } } func TestGetAlphanumericNumByASCIIV2(t *testing.T) { type args struct { s string } tests := []struct { name string args args want int }{ { name: "包含數(shù)字", args: args{"108條梁山好漢"}, want: 3, }, { name: "包含字母", args: args{"一百條梁山man"}, want: 3, }, { name: "包含數(shù)字與字母", args: args{"108條梁山man"}, want: 6, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := GetAlphanumericNumByASCIIV2(tt.args.s); got != tt.want { t.Errorf("GetAlphanumericNumByASCII() = %v, want %v", got, tt.want) } }) } } func TestGetAlphanumericNumByRegExp(t *testing.T) { type args struct { s string } tests := []struct { name string args args want int }{ { name: "包含數(shù)字", args: args{"108條梁山好漢"}, want: 3, }, { name: "包含字母", args: args{"一百條梁山man"}, want: 3, }, { name: "包含數(shù)字與字母", args: args{"108條梁山man"}, want: 6, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := GetAlphanumericNumByRegExp(tt.args.s); got != tt.want { t.Errorf("GetAlphanumericNumByRegExp() = %v, want %v", got, tt.want) } }) } }
運(yùn)行go test main/string
命令,其中 main/string 為單元測(cè)試所在包的路徑。輸出如下:
ok ? ? ?main/string ? ? 0.355s
驗(yàn)證無(wú)誤。
3.性能對(duì)比
上面提到的兩種方法都可以用來(lái)獲取字符串中數(shù)字與英文字母的數(shù)量,那么我們應(yīng)該采用哪一種方法呢?
功能上沒(méi)有差別,那么我們來(lái)看下性能對(duì)比吧。
func BenchmarkGetAlphanumericNumByASCII(b *testing.B) { for n := 0; n < b.N; n++ { GetAlphanumericNumByASCII("108條梁山man") } } func BenchmarkGetAlphanumericNumByASCIIV2(b *testing.B) { for n := 0; n < b.N; n++ { GetAlphanumericNumByASCIIV2("108條梁山man") } } func BenchmarkGetAlphanumericNumByRegExp(b *testing.B) { for n := 0; n < b.N; n++ { GetAlphanumericNumByRegExp("108條梁山man") } }
運(yùn)行上面的基準(zhǔn)測(cè)試,輸出如下:
go test -bench=. -benchmem main/string
goos: windows
goarch: amd64
pkg: main/string
cpu: Intel(R) Core(TM) i7-9700 CPU @ 3.00GHz
BenchmarkGetAlphanumericNumByASCII-8 ? ? ? ? ? ?89540210 ? ? ? ? ? ? ? ?12.67 ns/op ? ? ? ? ? ?0 B/op ? ? ? ? ?0 allocs/op
BenchmarkGetAlphanumericNumByASCIIV2-8 ? ? ? ? ?63227778 ? ? ? ? ? ? ? ?19.11 ns/op ? ? ? ? ? ?0 B/op ? ? ? ? ?0 allocs/op
BenchmarkGetAlphanumericNumByRegExp-8 ? ? ? ? ? ? 465954 ? ? ? ? ? ? ?2430 ns/op ? ? ? ? ? ?1907 B/op ? ? ? ? 27 allocs/op
PASS
ok ? ? ?main/string ? ? 3.965s
不測(cè)不知道,一測(cè)嚇一跳。通過(guò)正則表達(dá)式的實(shí)現(xiàn)方式,代碼雖然簡(jiǎn)潔,但是涉及多次內(nèi)存配分,性能與 ASCII 碼值法相比,差距非常之大,是 ASCII 碼值法的 200 倍左右。所以從性能的考慮,推薦使用 ASCII 碼值的方式獲取數(shù)字字母數(shù)量。
ASCII 碼值法有兩種遍歷方式,一種是按照字節(jié)遍歷,一種是按照 rune 字符遍歷。因?yàn)楹笳呱婕?rune 字符的判斷,所以性能會(huì)差一些。推薦使用按照字節(jié)遍歷。
4.小結(jié)
本文給出了兩種從字符串獲取數(shù)字與字母數(shù)量的方法:
- ASCII 碼值。
- 正則表達(dá)式。
出于性能的考慮,推薦使用 ASCII 碼值法,并使用字節(jié)遍歷的方式。
此外,本文給出的兩種方法,三種實(shí)現(xiàn)方式,相關(guān)源碼已放置開(kāi)源庫(kù) go-huge-util,可 import 直接使用。
package main import ( "fmt" huge "github.com/dablelv/go-huge-util" ) func main() { fmt.Println(huge.GetAlphanumericNumByASCII("108條梁山man")) // 6 fmt.Println(huge.GetAlphanumericNumByASCIIV2("108條梁山man")) // 6 fmt.Println(huge.GetAlphanumericNumByRegExp("108條梁山man")) // 6 }
參考文獻(xiàn)
golang統(tǒng)計(jì)出其中英文字母、空格、數(shù)字和其它字符的個(gè)數(shù)
原文鏈接:https://blog.csdn.net/K346K346/article/details/124936878
相關(guān)推薦
- 2022-05-11 mac zsh: command not found: vim
- 2023-10-10 Object.defineProperty和Proxy分別實(shí)現(xiàn)響應(yīng)式原理的簡(jiǎn)單示例
- 2022-09-30 Flutter?日歷組件簡(jiǎn)單實(shí)現(xiàn)_Android
- 2022-11-14 Swift?指針底層探索分析_Swift
- 2022-12-21 使用redis如何生成自增序列號(hào)碼_Redis
- 2022-03-19 Docker匿名掛載和具名掛載的具體使用_docker
- 2022-08-14 Python?Selenium彈窗處理操作指南_python
- 2022-04-02 Python字符串與正則表達(dá)式詳細(xì)介紹_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過(guò)濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支