整合營銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          用 Go 如何實(shí)現(xiàn)精準(zhǔn)統(tǒng)計(jì)文章字?jǐn)?shù)

          用 Go 如何實(shí)現(xiàn)精準(zhǔn)統(tǒng)計(jì)文章字?jǐn)?shù)

          家好,我是站長 polarisxu。

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

          統(tǒng)計(jì)字?jǐn)?shù)是一個很常見的需求,很多人印象最深的應(yīng)該是微博早些時候限制 140 字,而且邊輸入會邊統(tǒng)計(jì)剩余字?jǐn)?shù)。現(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ì)是最簡單的。

          func TotalWords(s string) int {
           n := 0
           inWord := false
           for _, r := range s {
            wasInWord := inWord
            inWord = !unicode.IsSpace(r)
            if inWord && !wasInWord {
             n++
            }
           }
           return n
          }
          

          還有一種更簡單的方式:

          len(strings.Fields(s))
          

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

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

          s1 := "Hello,playground"
          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è)ノ⒉┥显嚵艘幌拢l(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ì)算方法。

          func RuneCountInString(s string) (n int)
          

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

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

          以上代碼輸出 4。

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

          func TotalWords(s string) int {
           wordCount := 0
            
           plainWords := strings.Fields(s)
           for _, word := range plainWords {
            runeCount := utf8.RuneCountInString(word)
            if len(word) == runeCount {
             wordCount++
            } else {
             wordCount += runeCount
            }
           }
          
           return wordCount
          }
          

          增加如下的測試用例:

          func TestTotalWords(t *testing.T) {
           tests := []struct {
            name  string
            input string
            want  int
           }{
            {"en1", "hello,playground", 2},
            {"en2", "hello, playground", 2},
            {"cn1", "你好世界", 4},
            {"encn1", "Hello你好世界", 5},
            {"encn2", "Hello 你好世界", 5},
           }
           for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
             if got := wordscount.TotalWords(tt.input); got != tt.want {
              t.Errorf("TotalWords() = %v, want %v", got, tt.want)
             }
            })
           }
          }
          

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

          // AutoSpace 自動給中英文之間加上空格
          func AutoSpace(str string) string {
           out := ""
          
           for _, r := range str {
            out = addSpaceAtBoundary(out, r)
           }
          
           return out
          }
          
          func addSpaceAtBoundary(prefix string, nextChar rune) string {
           if len(prefix) == 0 {
            return string(nextChar)
           }
          
           r, size := utf8.DecodeLastRuneInString(prefix)
           if isLatin(size) != isLatin(utf8.RuneLen(nextChar)) &&
            isAllowSpace(nextChar) && isAllowSpace(r) {
            return prefix + " " + string(nextChar)
           }
          
           return prefix + string(nextChar)
          }
          
          func isLatin(size int) bool {
           return size == 1
          }
          
          func isAllowSpace(r rune) bool {
           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):

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

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

          func (wc *Counter) Stat(str string) {
           wc.Links = len(rxStrict.FindAllString(str, -1))
           wc.Pics = len(imgReg.FindAllString(str, -1))
          
           // 剔除 HTML
           str = StripHTML(str)
          
           str = AutoSpace(str)
          
           // 普通的鏈接去除(非 HTML 標(biāo)簽鏈接)
           str = rxStrict.ReplaceAllString(str, " ")
           plainWords := strings.Fields(str)
          
           for _, plainWord := range plainWords {
            words := strings.FieldsFunc(plainWord, func(r rune) bool {
             if unicode.IsPunct(r) {
              wc.Puncts++
              return true
             }
             return false
            })
          
            for _, word := range words {
             runeCount := utf8.RuneCountInString(word)
             if len(word) == runeCount {
              wc.Words++
             } else {
              wc.Words += runeCount
             }
            }
           }
          
           wc.Total = wc.Words + wc.Puncts
          }
          
          var (
           rxStrict = xurls.Strict()
           imgReg   = regexp.MustCompile(`<img [^>]*>`)
           stripHTMLReplacer = strings.NewReplacer("\n", " ", "</p>", "\n", "<br>", "\n", "<br />", "\n")
          )
          
          // StripHTML accepts a string, strips out all HTML tags and returns it.
          func StripHTML(s string) string {
           // Shortcut strings with no tags in them
           if !strings.ContainsAny(s, "<>") {
            return s
           }
           s = stripHTMLReplacer.Replace(s)
          
           // Walk through the string removing all tags
           b := GetBuffer()
           defer PutBuffer(b)
           var inTag, isSpace, wasSpace bool
           for _, r := range s {
            if !inTag {
             isSpace = false
            }
          
            switch {
            case r == '<':
             inTag = true
            case r == '>':
             inTag = false
            case unicode.IsSpace(r):
             isSpace = true
             fallthrough
            default:
             if !inTag && (!isSpace || (isSpace && !wasSpace)) {
              b.WriteRune(r)
             }
            }
          
            wasSpace = isSpace
          
           }
           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。

          常在很多網(wǎng)站編輯文本回答或發(fā)博客時,經(jīng)常會遇到有最大或最小字?jǐn)?shù)限制的問題,字?jǐn)?shù)如果少了或多了,可能發(fā)不出去或沒有平臺獎勵,好不容易編輯好的回答,又要重新修改,如此反復(fù),很浪費(fèi)時間和精力,如果字?jǐn)?shù)少還好,可以自己數(shù),但是字?jǐn)?shù)多了,不僅費(fèi)時間,而且自己很容易數(shù)錯,唉,所以,達(dá)芬奇今天就同大家分享幾個可以快速得到字?jǐn)?shù)多少的方法,望能幫助到需要的朋友。

          方法一:通過登錄字?jǐn)?shù)計(jì)數(shù)網(wǎng)站進(jìn)行快速查詢

          打開網(wǎng)絡(luò)瀏覽器,然后在窗口網(wǎng)址輸入欄輸入以下任一網(wǎng)址,摁“enther”鍵進(jìn)入網(wǎng)站 ,然后復(fù)制你所編輯的網(wǎng)頁回答或博客文章,然后粘貼進(jìn)上述網(wǎng)址相應(yīng)位置(見下圖),即可得到文本字?jǐn)?shù)。

          網(wǎng)址一:文本計(jì)數(shù)網(wǎng)(word counter):https://wordcounter.net/

          如下圖所示,word counter是一個專業(yè)的文本計(jì)數(shù)網(wǎng)站,僅需把所復(fù)制文本,粘貼在下述框圖里,便自動計(jì)數(shù),且不僅包含文本的字?jǐn)?shù),包括行數(shù),段數(shù)以及相應(yīng)的關(guān)鍵詞,及關(guān)鍵詞使用次數(shù)都可得到,通過word counter不僅可得到文本的詳細(xì)信息,同時通過對關(guān)鍵詞及相應(yīng)的關(guān)鍵詞使用次數(shù),可使文本編輯者優(yōu)化及豐富自己所用“關(guān)鍵詞”,使文本遣詞造句更加合理豐富。

          網(wǎng)址2:javascriptkit

          http://www.javascriptkit.com/script/script2/countwords.shtml

          如下圖所示,javascriptkit提供的文本計(jì)數(shù)服務(wù),雖不如wordcounter那么豐富,但對于我們的計(jì)數(shù)需求完全可以勝任。

          方法二:借助軟件,進(jìn)行文本計(jì)數(shù)。

          1. 利用Notepad++(記事本升級版)軟件。

          下載地址:https://wwc.lanzouw.com/i7OcY0b1nude,密碼:7mj3

          使用方法:如下圖所示,安裝完成后,打開“notepad++”,然后粘貼復(fù)制的文本,接著點(diǎn)擊上方工具欄的“視圖”按鈕,然后下拉至“摘要”選項(xiàng),點(diǎn)擊后,即可顯示文本的“統(tǒng)計(jì)信息”,統(tǒng)計(jì)信息即包括字符數(shù),字?jǐn)?shù),行數(shù),文檔長度等。

          1. Word

          打開word,輸入文字后,如圖,在左下方即可顯示文本字?jǐn)?shù)多少。

          3, 如果你經(jīng)常性需要知道文本字?jǐn)?shù)大小,那么上述方式就顯得麻煩了,要么需要輸入網(wǎng)址或打開軟件,給大家推薦一個文本計(jì)數(shù)軟件(dragking),解壓后雙擊.ahk文件,可在軟件起動后,如果復(fù)制文本可自動顯示文本字?jǐn)?shù)大小行數(shù)列數(shù)等,快捷方便。

          下載地址:https://wwc.lanzouw.com/iXxGV0b1nuub,密碼:asuy

          同時如下圖所示,此軟件還可進(jìn)行多種設(shè)置,包括是否開機(jī)起動,取詞的快捷鍵,以及展示內(nèi)容和位置進(jìn)行設(shè)置。

          好啦,今天達(dá)芬奇的分享就到這里啦,希望上述文本計(jì)數(shù)方式能幫到大家,同時如果你有使用過程中的問題也歡迎大家多多留言討論,點(diǎn)贊關(guān)注。

          時候需要控制下文字?jǐn)?shù),不然就會溢出,頁面就會變樣不美觀。這時我們就可以用css控制字?jǐn)?shù),超出部分顯示省略號。可以不換行,超出部分顯示省略號,也可以可以換行,多行,超出部分顯示省略號。

          1.不換行,超出部分顯示省略號

          <!Doctype html>
          <html>
          <head>
          <meta http-equiv="Content-Type" content="text/html; charset=GBK"/>
          <title>用css控制字?jǐn)?shù),超出部分顯示省略號</title>
          <style type="text/css">
          *{margin:0;padding:0;}
          body{width:1000px;margin:100px auto;}
          .box{
          width:260px;
          /*超出部分就隱藏*/
          overflow:hidden;
          /*不換行設(shè)定*/
          white-space:nowrap;
          /*超出部分的文字顯示省略號*/
          text-overflow:ellipsis;
          }
          </style>
          </head>
          <body>
          <div class="box">用css控制字?jǐn)?shù),超出部分顯示省略號用css控制字?jǐn)?shù),超出部分顯示省略號</div>
          </body>
          </html>
          

          效果圖如下:

          2.可以換行,多行,超出部分顯示省略號

          <!Doctype html>
          <html>
          <head>
          <meta http-equiv="Content-Type" content="text/html; charset=GBK"/>
          <title>可以換行,多行,超出部分顯示省略號</title>
          <style type="text/css">
          *{margin:0;padding:0;}
          body{width:1000px;margin:100px auto;}
          .box{
          width:260px;
          display: -webkit-box;
          -webkit-box-orient: vertical;
          /*2行*/
          -webkit-line-clamp: 2;
          overflow: hidden;
          }
          </style>
          </head>
          <body>
          <div class="box">1.用css控制字?jǐn)?shù),超出部分顯示省略號用css控制字?jǐn)?shù),超出部分顯示省略號</div>
          <div class="box">2.用css控制字?jǐn)?shù),超出部分顯示省略號用css控制字?jǐn)?shù),超出部分顯示省略號</div>
          </body>
          </html>
          

          效果圖如下:

          注:此方法適用于WebKit瀏覽器及移動端。

          除注明外的文章,均為來源:湯久生博客,轉(zhuǎn)載請保留本文地址!

          原文地址:http://tangjiusheng.com/divcss/169.html


          主站蜘蛛池模板: 亚洲AV无码一区东京热| 亚洲综合激情五月色一区| 成人区精品一区二区不卡亚洲| 欧美日韩国产免费一区二区三区 | 亚洲av无码成人影院一区| 国产成人综合亚洲一区| 美女视频黄a视频全免费网站一区| 国产日韩一区二区三区在线观看| 波多野结衣一区二区三区88| 日本一区二三区好的精华液| 成人午夜视频精品一区| 在线观看日韩一区| 人妻精品无码一区二区三区| 88国产精品视频一区二区三区| 国产精品成人国产乱一区| 日韩动漫av在线播放一区| 午夜视频在线观看一区| 成人无码精品一区二区三区| 日韩内射美女人妻一区二区三区 | 国产伦精品一区二区三区在线观看| 97久久精品一区二区三区| 色窝窝无码一区二区三区 | 日本中文字幕在线视频一区| 久久无码人妻一区二区三区| 免费高清av一区二区三区| 亚洲国产精品一区二区久久hs| 日韩视频一区二区| 国产亚洲综合一区二区三区 | 精品国产日韩亚洲一区| 秋霞电影网一区二区三区| 色欲AV无码一区二区三区| 一区二区三区电影在线观看| 成人精品一区二区三区不卡免费看| 一区二区三区免费看| 精品一区二区三区在线视频| 国产一区二区内射最近更新| 欧洲无码一区二区三区在线观看| 国产日本亚洲一区二区三区| 精品国产一区二区三区| 国产成人无码aa精品一区| 国产伦精品一区二区三区视频猫咪|