日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
用Go如何實(shí)現(xiàn)精準(zhǔn)統(tǒng)計(jì)文章字?jǐn)?shù)

大家好,我是站長 polarisxu。

公司主營業(yè)務(wù):成都網(wǎng)站設(shè)計(jì)、網(wǎng)站制作、移動網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。成都創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會用頭腦與智慧不斷的給客戶帶來驚喜。成都創(chuàng)新互聯(lián)公司推出西安免費(fèi)做網(wǎng)站回饋大家。

今天要聊的內(nèi)容應(yīng)該可以當(dāng)做一道面試題,你可以先想想該怎么實(shí)現(xiàn)。

統(tǒng)計(jì)字?jǐn)?shù)是一個很常見的需求,很多人印象最深的應(yīng)該是微博早些時候限制 140 字,而且邊輸入會邊統(tǒng)計(jì)剩余字?jǐn)?shù)?,F(xiàn)在很多社區(qū)文章也會有字?jǐn)?shù)統(tǒng)計(jì)的功能,而且可以依據(jù)字?jǐn)?shù)來預(yù)估閱讀時間。比如 Go語言中文網(wǎng)就有這樣的功能。

01 需求分析

下手之前先分析下這個需求。從我個人經(jīng)驗(yàn)看,在實(shí)際面試中,針對一個面試題,你的分析過程,循序漸進(jìn)的解決方案,可以很好的展示你的思考過程。正所謂分析問題、解決問題。這會給你加分的。

我們采用類似詞法分析的思路分析這個需求。

一篇文章通常包含如下元素,我們也稱之為 token:

  • 普通文字
  • 標(biāo)點(diǎn)符號
  • 圖片
  • 鏈接(包含各種協(xié)議的鏈接)
  • 代碼

其中普通文字通常會分為歐美和中日韓(CJK),因?yàn)?CJK 屬于表意文字,和歐美字母的文字差異很大。同時這里還涉及到編碼的問題。本文假設(shè)使用 UTF-8 編碼。

對于標(biāo)點(diǎn)符號,中文標(biāo)點(diǎn)和英文標(biāo)點(diǎn)也會很不一樣。

此外還有全角和半角的問題。

根據(jù)以上分析,對于該需求作如下假定:

  • 空格(包括換行)不算字?jǐn)?shù);
  • HTML 標(biāo)簽需要剔除;
  • 編碼方式:假定為 UTF-8 編碼;
  • 標(biāo)點(diǎn)符號算不算做字?jǐn)?shù)。如果算,像括號這樣的按 2 個字算;
  • 鏈接怎么算?一個鏈接約定為 1 個字可能更合適,大概閱讀時只是把它當(dāng)鏈接,而不太會關(guān)心鏈接由什么字母組成;
  • 圖片不算做字?jǐn)?shù),但如果計(jì)算閱讀時間,可能需要適當(dāng)考慮圖片的影響;
  • 對于技術(shù)文章,代碼是最麻煩的。統(tǒng)計(jì)代碼字?jǐn)?shù)感覺是沒多大意義的。統(tǒng)計(jì)代碼行數(shù)可能更有意義;

本文的解決方案針對以上的假定進(jìn)行。

02 Go 語言實(shí)現(xiàn)

先看最簡單的。

純英文

根據(jù)以上分析,如果文章只包含普通文本且是英文,也就是說,每個字(單詞)根據(jù)空格分隔,統(tǒng)計(jì)是最簡單的。

 
 
 
 
  1. func TotalWords(s string) int { 
  2.  n := 0 
  3.  inWord := false 
  4.  for _, r := range s { 
  5.   wasInWord := inWord 
  6.   inWord = !unicode.IsSpace(r) 
  7.   if inWord && !wasInWord { 
  8.    n++ 
  9.   } 
  10.  } 
  11.  return n 

還有一種更簡單的方式:

 
 
 
 
  1. len(strings.Fields(s)) 

不過看 strings.Fields 的實(shí)現(xiàn),性能會不如第一種方式。

回顧上面的需求分析,會發(fā)現(xiàn)這個實(shí)現(xiàn)是有 Bug 的。比如下面的例子:

 
 
 
 
  1. s1 := "Hello,playground" 
  2. s2 := "Hello, playground" 

用上面的實(shí)現(xiàn),s1 的字?jǐn)?shù)是 1,s2 的字?jǐn)?shù)是 2。它們都忽略了標(biāo)點(diǎn)符號。而且因?yàn)閷懛ǖ亩鄻有?不規(guī)范統(tǒng)一),導(dǎo)致計(jì)算字?jǐn)?shù)會有誤差。所以我們需要對寫法進(jìn)行規(guī)范。

規(guī)范排版

其實(shí)和寫代碼要有規(guī)范一樣,文章也是有規(guī)范的。比如出版社對于一本書的排版會有明確的規(guī)定。為了讓我們的文章看起來更舒服,也應(yīng)該遵循一定的規(guī)范。

這里推薦一個 GitHub 上的排版指南:《中文文案排版指北》,它的宗旨,統(tǒng)一中文文案、排版的相關(guān)用法,降低團(tuán)隊(duì)成員之間的溝通成本,增強(qiáng)網(wǎng)站氣質(zhì)。這個規(guī)范開頭關(guān)于空格的一段話很有意思:

有研究顯示,打字的時候不喜歡在中文和英文之間加空格的人,感情路都走得很辛苦,有七成的比例會在 34 歲的時候跟自己不愛的人結(jié)婚,而其余三成的人最后只能把遺產(chǎn)留給自己的貓。畢竟愛情跟書寫都需要適時地留白。

建議大家可以看看這個指北,一些知名的網(wǎng)站就是按照這個做的。

因?yàn)?GCTT 的排版在這個規(guī)范做,但人為約束不是最好的方法,所以我開發(fā)了一個 Go 工具:https://github.com/studygolang/autocorrect,用于自動給中英文之間加入合理的空格并糾正專用名詞大小寫。

所以為了讓字?jǐn)?shù)統(tǒng)計(jì)更準(zhǔn)確,我們假定文章是按一定的規(guī)范書寫的。比如上面的例子,規(guī)范的寫法是 s2 := "Hello, playground"。不過這里標(biāo)點(diǎn)不算作字?jǐn)?shù)。

剛?cè)ノ⒉┥显嚵艘幌?,發(fā)現(xiàn)微博的字?jǐn)?shù)計(jì)算方式有點(diǎn)詭異,竟然是 9 個字。

測試一下發(fā)現(xiàn),它直接把兩個英文字母算作一個字(兩個字節(jié)算一個字)。而漢字是正常的。大家可以想想微博是怎么實(shí)現(xiàn)的。

中英文混合

中文不像英文,單詞之間沒有空格分隔,因此開始的那兩種方式不適合。

如果是純中文,我們怎么計(jì)算字?jǐn)?shù)呢?

在 Go 語言中,字符串使用 UTF-8 編碼,一個字符用 rune 表示。因此在標(biāo)準(zhǔn)庫中查找相關(guān)計(jì)算方法。

 
 
 
 
  1. func RuneCountInString(s string) (n int) 

這個方法能計(jì)算字符串包含的 rune(字符)數(shù),對于純中文,就是漢字?jǐn)?shù)。

 
 
 
 
  1. str := "你好世界" 
  2. fmt.Println(utf8.RuneCountInString(str)) 

以上代碼輸出 4。

然而,因?yàn)楹芏鄷r候文章會中英文混合,因此我們先采用上面的純英文的處理方式,即:strings.Fields(),將文章用空格分隔,然后處理每一部分。

 
 
 
 
  1. func TotalWords(s string) int { 
  2.  wordCount := 0 
  3.    
  4.  plainWords := strings.Fields(s) 
  5.  for _, word := range plainWords { 
  6.   runeCount := utf8.RuneCountInString(word) 
  7.   if len(word) == runeCount { 
  8.    wordCount++ 
  9.   } else { 
  10.    wordCount += runeCount 
  11.   } 
  12.  } 
  13.  
  14.  return wordCount 

增加如下的測試用例:

 
 
 
 
  1. func TestTotalWords(t *testing.T) { 
  2.  tests := []struct { 
  3.   name  string 
  4.   input string 
  5.   want  int 
  6.  }{ 
  7.   {"en1", "hello,playground", 2}, 
  8.   {"en2", "hello, playground", 2}, 
  9.   {"cn1", "你好世界", 4}, 
  10.   {"encn1", "Hello你好世界", 5}, 
  11.   {"encn2", "Hello 你好世界", 5}, 
  12.  } 
  13.  for _, tt := range tests { 
  14.   t.Run(tt.name, func(t *testing.T) { 
  15.    if got := wordscount.TotalWords(tt.input); got != tt.want { 
  16.     t.Errorf("TotalWords() = %v, want %v", got, tt.want) 
  17.    } 
  18.   }) 
  19.  } 

發(fā)現(xiàn) en1 和 encn1 測試不通過,因?yàn)闆]有按照上面說的規(guī)范書寫。因此我們通過程序增加必要的空格。

 
 
 
 
  1. // AutoSpace 自動給中英文之間加上空格 
  2. func AutoSpace(str string) string { 
  3.  out := "" 
  4.  
  5.  for _, r := range str { 
  6.   out = addSpaceAtBoundary(out, r) 
  7.  } 
  8.  
  9.  return out 
  10.  
  11. func addSpaceAtBoundary(prefix string, nextChar rune) string { 
  12.  if len(prefix) == 0 { 
  13.   return string(nextChar) 
  14.  } 
  15.  
  16.  r, size := utf8.DecodeLastRuneInString(prefix) 
  17.  if isLatin(size) != isLatin(utf8.RuneLen(nextChar)) && 
  18.   isAllowSpace(nextChar) && isAllowSpace(r) { 
  19.   return prefix + " " + string(nextChar) 
  20.  } 
  21.  
  22.  return prefix + string(nextChar) 
  23.  
  24. func isLatin(size int) bool { 
  25.  return size == 1 
  26.  
  27. func isAllowSpace(r rune) bool { 
  28.  return !unicode.IsSpace(r) && !unicode.IsPunct(r) 

這樣可以在 TotalWords 函數(shù)開頭增加 AutoSpace 進(jìn)行規(guī)范化。這時結(jié)果就正常了。

處理標(biāo)點(diǎn)和其他類型

以上例子標(biāo)點(diǎn)沒計(jì)算在內(nèi),而且如果英文和中文標(biāo)點(diǎn)混合在一起,情況又復(fù)雜了。

為了更好地實(shí)現(xiàn)開始的需求分析,重構(gòu)以上代碼,設(shè)計(jì)如下的結(jié)構(gòu):

 
 
 
 
  1. type Counter struct { 
  2.  Total     int // 總字?jǐn)?shù) = Words + Puncts 
  3.  Words     int // 只包含字符數(shù) 
  4.  Puncts    int // 標(biāo)點(diǎn)數(shù) 
  5.  Links     int // 鏈接數(shù) 
  6.  Pics      int // 圖片數(shù) 
  7.  CodeLines int // 代碼行數(shù) 

同時將 TotalWords 重構(gòu)為 Counter 的 Stat 方法,同時記錄標(biāo)點(diǎn)數(shù):

 
 
 
 
  1. func (wc *Counter) Stat(str string) { 
  2.  wc.Links = len(rxStrict.FindAllString(str, -1)) 
  3.  wc.Pics = len(imgReg.FindAllString(str, -1)) 
  4.  
  5.  // 剔除 HTML 
  6.  str = StripHTML(str) 
  7.  
  8.  str = AutoSpace(str) 
  9.  
  10.  // 普通的鏈接去除(非 HTML 標(biāo)簽鏈接) 
  11.  str = rxStrict.ReplaceAllString(str, " ") 
  12.  plainWords := strings.Fields(str) 
  13.  
  14.  for _, plainWord := range plainWords { 
  15.   words := strings.FieldsFunc(plainWord, func(r rune) bool { 
  16.    if unicode.IsPunct(r) { 
  17.     wc.Puncts++ 
  18.     return true 
  19.    } 
  20.    return false 
  21.   }) 
  22.  
  23.   for _, word := range words { 
  24.    runeCount := utf8.RuneCountInString(word) 
  25.    if len(word) == runeCount { 
  26.     wc.Words++ 
  27.    } else { 
  28.     wc.Words += runeCount 
  29.    } 
  30.   } 
  31.  } 
  32.  
  33.  wc.Total = wc.Words + wc.Puncts 
  34.  
  35. var ( 
  36.  rxStrict = xurls.Strict() 
  37.  imgReg   = regexp.MustCompile(`]*>`) 
  38.  stripHTMLReplacer = strings.NewReplacer("\n", " ", "

    ", "\n", "
    ", "\n", "", "\n") 
  39.  
  40. // StripHTML accepts a string, strips out all HTML tags and returns it. 
  41. func StripHTML(s string) string { 
  42.  // Shortcut strings with no tags in them 
  43.  if !strings.ContainsAny(s, "<>") { 
  44.   return s 
  45.  } 
  46.  s = stripHTMLReplacer.Replace(s) 
  47.  
  48.  // Walk through the string removing all tags 
  49.  b := GetBuffer() 
  50.  defer PutBuffer(b) 
  51.  var inTag, isSpace, wasSpace bool 
  52.  for _, r := range s { 
  53.   if !inTag { 
  54.    isSpace = false 
  55.   } 
  56.  
  57.   switch { 
  58.   case r == '<': 
  59.    inTag = true 
  60.   case r == '>': 
  61.    inTag = false 
  62.   case unicode.IsSpace(r): 
  63.    isSpace = true 
  64.    fallthrough 
  65.   default: 
  66.    if !inTag && (!isSpace || (isSpace && !wasSpace)) { 
  67.     b.WriteRune(r) 
  68.    } 
  69.   } 
  70.  
  71.   wasSpace = isSpace 
  72.  
  73.  } 
  74.  return b.String() 

代碼過多的細(xì)節(jié)不討論。此外,關(guān)于文章內(nèi)的代碼行數(shù)統(tǒng)計(jì)未實(shí)現(xiàn)(目前沒有想到特別好的方法,如果你有,歡迎交流)。

03 總結(jié)

通過本文的分析發(fā)現(xiàn),精準(zhǔn)統(tǒng)計(jì)字?jǐn)?shù)沒那么容易,這里涉及到很多的細(xì)節(jié)。

當(dāng)然,實(shí)際應(yīng)用中,字?jǐn)?shù)不需要那么特別精準(zhǔn),而且對于非正常文字(比如鏈接、代碼)怎么處理,會有不同的約定。

本文涉及到的完整代碼放在 GitHub:https://github.com/polaris1119/wordscount。

本文轉(zhuǎn)載自微信公眾號「polarisxu」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系polarisxu公眾號。


分享名稱:用Go如何實(shí)現(xiàn)精準(zhǔn)統(tǒng)計(jì)文章字?jǐn)?shù)
分享網(wǎng)址:http://www.5511xx.com/article/djohdji.html