Go语言源码中Replacer查找部份的笔记

用过strings.NewReplacer,replacer.Replace(),它按对传入参数后,能依优先级替换,并能处理中文字符串参数.

觉得功能强大,特别好用.对它的查找和优先级怎么处理有点兴趣,花时间研究了下源码,在这记录一下个人理解.

package main 

//author:xcl
//2014-1-20 记录

import (
	"fmt"
    "strings"
)

func main(){

   patterns := []string{
            "y","25",
            "中","国",
            "中工","家伙",
        }  

   /*
    patterns := make([]string,270 * 2)
    for i :=0;i< 270 *2;i++{
        patterns[i] = fmt.Sprintf("%d",i)
    }
    */
    replacer := strings.NewReplacer(patterns...)

    format := "中(国)--中工(家伙)"
    strfmt := replacer.Replace(format)
    NewReplacer(patterns...);
    fmt.Println("\nmain() replacer.Replace old=",format)
    fmt.Println("main() replacer.Replace new=",strfmt)
}

func NewReplacer(oldnew ...string){

   r :=  makeGenericReplacer(oldnew)

   val,keylen,found := r.lookup("中",true)
   fmt.Println("\nNewReplacer() 中   val:",val," keylen:",keylen," found:",found)

   val,keylen,found = r.lookup("中工",true)
   fmt.Println("NewReplacer() 中工 val:",val," keylen:",keylen," found:",found)

   val,keylen,found = r.lookup("y",false)
   fmt.Println("NewReplacer() y    val:",val," keylen:",keylen," found:",found)

   /*
   val,keylen,found := r.lookup("2",true)
   fmt.Println("\nNewReplacer() 2   val:",val," keylen:",keylen," found:",found)

   val,keylen,found = r.lookup("3",true)
   fmt.Println("\nNewReplacer() 3   val:",val," keylen:",keylen," found:",found)
*/
}

type genericReplacer struct {
    root trieNode  //一个字典树
    // tableSize is the size of a trie node's lookup table. It is the number
    // of unique key bytes.
    tableSize int
    // mapping maps from key bytes to a dense index for trieNode.table.
    mapping [256]byte
}

func makeGenericReplacer(oldnew []string) *genericReplacer {
    r := new(genericReplacer)
    // Find each byte used, then assign them each an index.
    for i := 0; i < len(oldnew); i += 2 { //步长2. 第一个为pattern
        key := oldnew[i]
        fmt.Println("\nmakeGenericReplacer() for key=",key)

        //key[j]=utf8存储汉字的三个编码位置中的一个如228,则将其对应位置设置为1
        //即 r.mapping[228] = 1
        for j := 0; j < len(key); j++ {
            r.mapping[key[j]] = 1
            fmt.Println("makeGenericReplacer() key[",j,"]=",key[j])
        }
    }

    for _, b := range r.mapping {
        r.tableSize += int(b)
    }
    fmt.Println("makeGenericReplacer()  r.tableSize=",r.tableSize)

    var index byte
    for i, b := range r.mapping {
        if b == 0 {
            r.mapping[i] = byte(r.tableSize)
        } else {
            //依数组字符编码位置,建立索引
            r.mapping[i] = index
            fmt.Println("makeGenericReplacer()  r.mapping[",i,"] =",r.mapping[i] )
            index++
        }
    }
    // Ensure root node uses a lookup table (for performance).
    r.root.table = make([]*trieNode, r.tableSize) 

    //将key,val放入字典树,注意priority=len(oldnew)-i,即越数组前面的,值越大.级别越高
    for i := 0; i < len(oldnew); i += 2 {
        r.root.add(oldnew[i], oldnew[i+1], len(oldnew)-i, r)
    }
    return r
}

type trieNode struct {
    value string
    priority int

    prefix string
    next   *trieNode
    table []*trieNode
}

func (t *trieNode) add(key, val string, priority int, r *genericReplacer) {
     fmt.Println("trieNode->add() val=",val," key=",key)
     if key == "" {
        if t.priority == 0 {
            t.value = val
            t.priority = priority
            fmt.Println("trieNode->add() t.priority==",priority)
        }
        return
    }

    if t.prefix != "" { //处理已有前缀的node
        // Need to split the prefix among multiple nodes.
        var n int // length of the longest common prefix
        for ; n < len(t.prefix) && n < len(key); n++ { //prefix与key的比较
            if t.prefix[n] != key[n] {
                break
            }
        }
        if n == len(t.prefix) {  //相同,继续放下面
            t.next.add(key[n:], val, priority, r)
        } else if n == 0 { //没一个相同
            // First byte differs, start a new lookup table here. Looking up
            // what is currently t.prefix[0] will lead to prefixNode, and
            // looking up key[0] will lead to keyNode.
            var prefixNode *trieNode
            if len(t.prefix) == 1 {  //如果prefix只是一个字节的字符编码,则挂在节点下面
                prefixNode = t.next
            } else {                    //如果不是,将余下的新建一个trie树
                prefixNode = &trieNode{
                    prefix: t.prefix[1:],
                    next:   t.next,
                }
            }
            keyNode := new(trieNode)
            t.table = make([]*trieNode, r.tableSize) //lookup()中的if node.table != nil 

            t.table[r.mapping[t.prefix[0]]] = prefixNode
            t.table[r.mapping[key[0]]] = keyNode
            t.prefix = ""
            t.next = nil
            keyNode.add(key[1:], val, priority, r)
        } else {
            // Insert new node after the common section of the prefix.
            next := &trieNode{
                prefix: t.prefix[n:],
                next:   t.next,
            }
            t.prefix = t.prefix[:n]
            t.next = next
            next.add(key[n:], val, priority, r)
        }
    } else if t.table != nil {
        // Insert into existing table.
        m := r.mapping[key[0]]
        if t.table[m] == nil {
            t.table[m] = new(trieNode)
        }
        t.table[m].add(key[1:], val, priority, r) //构建树
    } else {
        t.prefix = key
        t.next = new(trieNode)
        t.next.add("", val, priority, r)
    }

}

func (r *genericReplacer) lookup(s string, ignoreRoot bool) (val string, keylen int,found bool) {
    // Iterate down the trie to the end, and grab the value and keylen with
    // the highest priority.
    bestPriority := 0
    node := &r.root
    n := 0

    for node != nil {
         if node.priority > bestPriority && !(ignoreRoot && node == &r.root) {
            bestPriority = node.priority
            val = node.value
            keylen = n
            found = true
        }

        if s == "" {
            break
        }

        if node.table != nil {
            index := r.mapping[s[0]]
            if int(index) == r.tableSize { //字符编码第一个字节就没在table中,中断查找
                break
            }
            node = node.table[index]
            s = s[1:]
            n++
        } else if node.prefix != "" && HasPrefix(s, node.prefix) {
            //字符编码非第一个字节的节点会保留key在prefix中,所以通过分析prefix来继续找其它字节
            n += len(node.prefix)
            s = s[len(node.prefix):]
            node = node.next //继续找相同prefix以外其它字符
        } else {
            break
        }
    }
    return
}

// HasPrefix tests whether the string s begins with prefix.
func HasPrefix(s, prefix string) bool {
    return len(s) >= len(prefix) && s[0:len(prefix)] == prefix
}

记录:

ascii范围内的只占一个字节,如y(121)

utf8中每个汉字占三个字节.如中(228,184,173)

构建树:

如果是新的第一个单词或词组

先进  } else if t.table != nil {

然后再进 else,这中间会把 t.prefix = key,把key值存放在prefix,将""传给下一个node

最后执行 if key == "" && t.priority == 0 { ,将 t.value = val

即key的字符编码(第一个字节)对应的root.table位置开始,依次指向另外的字符编译node,中间node的prefix存下key值.

最末一个node,存下对应的val及priority.

如果是后传入的单词或词组,先从key字符编码首个字节对应的root.table位置开始,依次查找,

} else if t.table != nil {

如果已有前缀的,进行比较 if t.prefix != "" {

1, 如目前prefix与key完全一致,则继续构建树子节点

2. 如prefix与key完全不同,则另起炉灶,构建一条新的tree

prefixNode 承上,keyNode 启下

至于为什么t.table = make([]*trieNode, r.tableSize),是为了预留映射空间.

所以它是这么弄的,而不是t.table[0],t.table[1].

t.table[r.mapping[t.prefix[0]]] = prefixNode

t.table[r.mapping[key[0]]] = keyNode

3.有部份相同, 直接跳到t.prefix[n:],然后从key[n:]开始继续构建树子节点

priority:

在这的定义是数字越大,优先级别越高

if key == "" { //字符编码中间的字节

if t.priority == 0 { //如果有定义过priority的就略过,新加的,把现有的级别加上

//对应{中,中工}这种,虽然后面有"中工",但"中",的priority要高,所以"中工"对应的值虽找到但不会返回.

if node.priority > bestPriority { bestPriority = node.priority}

例如:中工(priority=4),中(priority=2)

patterns:

"中工","家伙",

"中","国",

则:

lookup() bestPriority: 0  node.priority: 0  value:   prefix:

lookup() bestPriority: 0  node.priority: 0  value:   prefix: ??

lookup() bestPriority: 0  node.priority: 2  value: 国  prefix: 工

NewReplacer() 中 val: 国  keylen: 3  found: true

lookup() bestPriority: 0  node.priority: 0  value:   prefix:

lookup() bestPriority: 0  node.priority: 0  value:   prefix: ??

lookup() bestPriority: 0  node.priority: 2  value: 国  prefix: 工

lookup() bestPriority: 2  node.priority: 4  value: 家伙  prefix:

NewReplacer() 中工 val: 家伙  keylen: 6  found: true

main() replacer.Replace old= 中(国)--中工(家伙)

main() replacer.Replace new= 国(国)--家伙(家伙)

如果调整下顺序,把中->国提前,则会发现,下面的结果:

patterns:

"中","国",

"中工","家伙",

则:

lookup() bestPriority: 0  node.priority: 0  value:   prefix:

lookup() bestPriority: 0  node.priority: 0  value:   prefix: ??

lookup() bestPriority: 0  node.priority: 4  value: 国  prefix: 工

NewReplacer() 中 val: 国  keylen: 3  found: true

lookup() bestPriority: 0  node.priority: 0  value:   prefix:

lookup() bestPriority: 0  node.priority: 0  value:   prefix: ??

lookup() bestPriority: 0  node.priority: 4  value: 国  prefix: 工

lookup() bestPriority: 4  node.priority: 2  value: 家伙  prefix:

NewReplacer() 中工 val: 国  keylen: 3  found: true

main() replacer.Replace old= 中(国)--中工(家伙)

main() replacer.Replace new= 国(国)--国工(家伙)

还有,刚发现 lookup(s string, ignoreRoot bool) (val string, keylen int,found bool) {}中

定义在返回值中的变量,原来可以直接在函数中使用,

至于返回,直接return就行了,都不用写全返回值的,好省事.

MAIL: [email protected]

BLOG:http://blog.csdn.net/xcl168

时间: 2024-11-08 02:04:48

Go语言源码中Replacer查找部份的笔记的相关文章

Go语言源码中的Rabin-Karp算法

strings.go包实现了一个Rabin-Karp算法.有点意思. 关于这个算法: 图灵社区的有一篇: 图说Rabin-Karp字符串查找算法 关于Go源码实现: 网友GoLove已写一个篇非常详细的说明了. http://www.cnblogs.com/golove/p/3234673.html GoLove那个已经分析的非常清楚了,只是前面那一串说明太长了.我把他的说明替换成代码形式. 直接跑起来,这样更能看得清楚些. package main import ( "fmt" &q

zepto.1.1.6.js源码中的each方法学习笔记

each方法接受要遍历的对象和对应的回调函数作为参数,它的作用是: 1.如果要遍历的对象是类似数组的形式(以该对象的length属性值的类型是否为number类型来判断),那么就把以要遍历的对象为执行环境,将回调函数放到该执行环境中去循环执行length次: 2.如果要遍历的对象不类似数组,那么用for key in obj 的方法循环执行回调函数key次,同样以要遍历的对象为执行环境,将回调函数放到该执行环境中去循环执行. function each(elements, callback){

在Android源码中查找Java代码中native函数对应的C++实现

Android源码中很多关键代码都是C++实现的,java通过jni来调用,经常会看到java中这样的代码: static native Thread currentThread(); 如何根据方法名找到其对应的C++实现,有两个方法. 先来个java代码的示例VMThread.java: package java.lang; class VMThread { Thread thread; int vmData; VMThread(Thread t) { thread = t; } native

访何红辉:谈谈Android源码中的设计模式

最近Android 6.0版本的源代码开放下载,刚好分析Android源码的技术书籍<Android源码设计模式解析与实战>上市,我们邀请到它的作者何红辉,来谈谈Android源码中的设计模式,以及近期Android开发中的一些热点话题. 受访嘉宾介绍: 何红辉(@MrSimp1e),前友盟Android工程师,活跃于国内各大技术社区,热爱开源,热爱技术,热爱分享.Android开源库 AndroidEventBus . Colorful 作者, 开发技术前线 站长,<Android源码

Redis源码中的CRC校验码(crc16、crc64)原理浅析

在阅读Redis源码的时候,看到了两个文件:crc16.c.crc64.c.下面我抛砖引玉,简析一下原理. CRC即循环冗余校验码,是信息系统中一种常见的检错码.大学课程中的"计算机网络"."计算机组成"等课程中都有提及.我们可能都了解它的数学原理,在试卷上手工计算一个CRC校验码,并不是难事.但是计算机不是人,现实世界中的数学原理需要转化为计算机算法才能实现目的.实际上作为计算机专业背景人并不会经常使用或接触到CRC的计算机算法实现的原理,通常是电子学科背景的人士

SpringSecurity的Filter执行顺序在源码中的体现

在网上看各种SpringSecurity教程时,都讲到了SpringSecurity的Filter顺序.但是一直不知道这个顺序在源码中是如何体现的.今天一步一步的查找,最终找到顺序是在FilterComparator中定义的. 先看一下代码: /** * An internal use only {@link Comparator} that sorts the Security {@link Filter} * instances to ensure they are in the corre

从express源码中探析其路由机制

引言 在web开发中,一个简化的处理流程就是:客户端发起请求,然后服务端进行处理,最后返回相关数据.不管对于哪种语言哪种框架,除去细节的处理,简化后的模型都是一样的.客户端要发起请求,首先需要一个标识,通常情况下是URL,通过这个标识将请求发送给服务端的某个具体处理程序,在这个过程中,请求可能会经历一系列全局处理,比如验证.授权.URL解析等,然后定位到某个处理程序进行业务处理,最后将生成的数据返回客户端,客户端将数据结合视图模版呈现出合适的样式.这个过程涉及到的模块比较多,本文只探讨前半部分,

Android 源码中的设计模式

最近看了一些android的源码,发现设计模式无处不在啊!感觉有点乱,于是决定要把设计模式好好梳理一下,于是有了这篇文章. 面向对象的六大原则 单一职责原则 所谓职责是指类变化的原因.如果一个类有多于一个的动机被改变,那么这个类就具有多于一个的职责.而单一职责原则就是指一个类或者模块应该有且只有一个改变的原因.通俗的说,即一个类只负责一项职责,将一组相关性很高的函数.数据封装到一个类中. 开闭原则 对于扩展是开放的,这意味着模块的行为是可以扩展的.当应用的需求改变时,我们可以对模块进行扩展,使其

Android 网络框架之Retrofit2使用详解及从源码中解析原理

就目前来说Retrofit2使用的已相当的广泛,那么我们先来了解下两个问题: 1 . 什么是Retrofit? Retrofit是针对于Android/Java的.基于okHttp的.一种轻量级且安全的.并使用注解方式的网络请求框架. 2 . 我们为什么要使用Retrofit,它有哪些优势? 首先,Retrofit使用注解方式,大大简化了我们的URL拼写形式,而且注解含义一目了然,简单易懂: 其次,Retrofit使用简单,结构层次分明,每一步都能清晰的表达出之所以要使用的寓意: 再者,Retr