如何从Go中的字节缓冲区中删除Unicode字符?

时间:2016-10-07 00:37:12

标签: go unicode buffer

我有一个bytes.Buffer类型变量,我填充了Unicode字符:

var mbuff bytes.Buffer
unicodeSource := 'کیا حال ھے؟'    
for i,r := range(unicodeSource) {
    mbuff.WriteRune(r)
}

注意:我在这里迭代了Unicode文字,但实际上源是用户输入字符的无限循环。

现在,我想从缓冲区mbuff中的任何位置删除Unicode字符。问题是字符可能是可变字节大小。所以我不能从mbuff.String()中选择 ith 字节,因为它可能是字符的开头,中间或结尾。这是我的琐碎(和可怕)的解决方案:

// removing Unicode character at position n
var tempString string
currChar := 0
for _, ch := range(mbuff.String()) { // iterate over Unicode chars
    if currChar != n {               // skip concatenating nth char
        tempString += ch
    }
    currChar++
}
mbuff.Reset()                        // empty buffer
mbuff.WriteString(tempString)        // write new string

这在许多方面都很糟糕。首先,我将缓冲区转换为字符串,删除 ith 元素,并将新字符串写回缓冲区。操作太多了。其次,我在循环中使用+=运算符将Unicode字符连接成一个新字符串。我首先使用缓冲区来避免使用+=进行连接,bytes.Buffer缓慢this answer points out

while (!in_stream.eof()) 中删除 ith Unicode字符的有效方法是什么?
i-1 Unicode字符之后(即在 ith 位置)插入Unicode字符的有效方法是什么?

3 个答案:

答案 0 :(得分:1)

要从一个字节切片中删除 ith 符文,请循环切片计数符文。找到第i个符文后,将符文后面的字节复制到 ith 符文的位置:

func removeAtBytes(p []byte, i int) []byte {
    j := 0
    k := 0
    for k < len(p) {
        _, n := utf8.DecodeRune(p[k:])
        if i == j {
            p = p[:k+copy(p[k:], p[k+n:])]
        }
        j++
        k += n
    }
    return p
}

此函数修改参数切片的后备数组,但不分配内存。

使用此功能从bytes.Buffer删除符文。

p := removeAtBytes(mbuf.Bytes(), i)
mbuf.Truncate(len(p)) // backing bytes were updated, adjust length

playground example

要从字符串中删除 ith 符文,请循环计算符号的字符串。当找到第i个符文时,通过将符文前的字符串段与符文后面的字符串段连接起来创建一个字符串。

func removeAt(s string, i int) string {
    j := 0  // count of runes
    k := 0  // index in string of current rune
   for k < len(s) {
        _, n := utf8.DecodeRuneInString(s[k:])
        if i == j {
            return s[:k] + s[k+n:]
        }
        j++
        k += n
    }
    return s
}

此函数分配一个字符串,即结果。 DecodeRuneInString是标准库unicode / utf8包中的一个函数。

答案 1 :(得分:0)

退一步,经常在ReaderWriter上工作,因此另一种解决方案是使用text/transform包。您创建Transformer,将其附加到Reader并使用新的Reader生成转换后的字符串。例如,这是一个船长:

func main() {
    src := strings.NewReader("کیا حال ھے؟")
    skipped := transform.NewReader(src, NewSkipper(5))
    var buf bytes.Buffer
    io.Copy(&buf, skipped)
    fmt.Println("RESULT:", buf.String())
}

这是实施:

package main

import (
    "bytes"
    "fmt"
    "io"
    "strings"
    "unicode/utf8"

    "golang.org/x/text/transform"
)

type skipper struct {
    pos int
    cnt int
}

// NewSkipper creates a text transformer which will remove the rune at pos
func NewSkipper(pos int) transform.Transformer {
    return &skipper{pos: pos}
}

func (s *skipper) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) {
    for utf8.FullRune(src) {
        _, sz := utf8.DecodeRune(src)
        // not enough space in the dst
        if len(dst) < sz {
            return nDst, nSrc, transform.ErrShortDst
        }
        if s.pos != s.cnt {
            copy(dst[:sz], src[:sz])
            // track that we stored in dst
            dst = dst[sz:]
            nDst += sz
        }
        // track that we read from src
        src = src[sz:]
        nSrc += sz
        // on to the next rune
        s.cnt++
    }
    if len(src) > 0 && !atEOF {
        return nDst, nSrc, transform.ErrShortSrc
    }
    return nDst, nSrc, nil
}

func (s *skipper) Reset() {
    s.cnt = 0
}

此代码可能存在错误,但希望您能看到这个想法。

这种方法的好处是它可以处理潜在无限量的数据,而无需将所有数据存储在内存中。例如,您可以通过这种方式转换文件。

答案 2 :(得分:0)

编辑:

删除缓冲区中的第i个符文:
答:将所有符文向左移动一个位置(此处A比B快),在The Go Playground上尝试:

func removeRuneAt(s string, runePosition int) string {
    if runePosition < 0 {
        return s
    }
    r := []rune(s)
    if runePosition >= len(r) {
        return s
    }
    copy(r[runePosition:], r[runePosition+1:])
    return string(r[:len(r)-1])
}

B:复制到新缓冲区,在The Go Playground

上试用
func removeRuneAt(s string, runePosition int) string {
    if runePosition < 0 {
        return s // avoid allocation
    }
    r := []rune(s)
    if runePosition >= len(r) {
        return s // avoid allocation
    }
    t := make([]rune, len(r)-1) // Apply replacements to buffer.
    w := copy(t, r[:runePosition])
    w += copy(t[w:], r[runePosition+1:])
    return string(t[:w])
}

C:在The Go Playground上试用:

package main

import (
    "bytes"
    "fmt"
)

func main() {
    str := "hello"
    fmt.Println(str)
    fmt.Println(removeRuneAt(str, 1))

    buf := bytes.NewBuffer([]byte(str))
    fmt.Println(buf.Bytes())

    buf = bytes.NewBuffer([]byte(removeRuneAt(buf.String(), 1)))
    fmt.Println(buf.Bytes())
}
func removeRuneAt(s string, runePosition int) string {
    if runePosition < 0 {
        return s // avoid allocation
    }
    r := []rune(s)
    if runePosition >= len(r) {
        return s // avoid allocation
    }

    t := make([]rune, len(r)-1) // Apply replacements to buffer.
    w := copy(t, r[0:runePosition])
    w += copy(t[w:], r[runePosition+1:])
    return string(t[0:w])
}

D:Benchmark
答:745.0426ms
B:1.0160581s
2000000次迭代

1-简答:替换角色的所有(n)个实例(甚至是string):

n := -1
newR := ""
old := "µ"
buf = bytes.NewBuffer([]byte(strings.Replace(buf.String(), old, newR, n)))

2-要替换缓冲区中string实例中的字符(ith),您可以使用:

buf = bytes.NewBuffer([]byte(Replace(buf.String(), oldString, newOrEmptyString, ith)))

请参阅:

// Replace returns a copy of the string s with the ith
// non-overlapping instance of old replaced by new.
func Replace(s, old, new string, ith int) string {
    if len(old) == 0 || old == new || ith < 0 {
        return s // avoid allocation
    }
    i, j := 0, 0
    for ; ith >= 0; ith-- {
        j = strings.Index(s[i:], old)
        if j < 0 {
            return s // avoid allocation
        }
        j += i
        i = j + len(old)
    }
    t := make([]byte, len(s)+(len(new)-len(old))) // Apply replacements to buffer.
    w := copy(t, s[0:j])
    w += copy(t[w:], new)
    w += copy(t[w:], s[j+len(old):])
    return string(t[0:w])
}

The Go Playground上试用:

package main

import (
    "bytes"
    "fmt"
    "strings"
)

func main() {
    str := `How are you?µ`
    fmt.Println(str)
    fmt.Println(Replace(str, "µ", "", 0))

    buf := bytes.NewBuffer([]byte(str))
    fmt.Println(buf.Bytes())

    buf = bytes.NewBuffer([]byte(Replace(buf.String(), "µ", "", 0)))

    fmt.Println(buf.Bytes())
}
func Replace(s, old, new string, ith int) string {
    if len(old) == 0 || old == new || ith < 0 {
        return s // avoid allocation
    }
    i, j := 0, 0
    for ; ith >= 0; ith-- {
        j = strings.Index(s[i:], old)
        if j < 0 {
            return s // avoid allocation
        }
        j += i
        i = j + len(old)
    }
    t := make([]byte, len(s)+(len(new)-len(old))) // Apply replacements to buffer.
    w := copy(t, s[0:j])
    w += copy(t[w:], new)
    w += copy(t[w:], s[j+len(old):])
    return string(t[0:w])
}

3-如果要从字符串中的任何位置删除所有Unicode字符实例(old字符串),可以使用:

strings.Replace(str, old, "", -1)

4-此外,这适用于从bytes.buffer中删除:

strings.Replace(buf.String(), old, newR, -1)

像这样:

buf = bytes.NewBuffer([]byte(strings.Replace(buf.String(), old, newR, -1)))

以下是完整的工作代码(在The Go Playground上试用):

package main

import (
    "bytes"
    "fmt"
    "strings"
)

func main() {
    str := `کیا حال ھے؟` //How are you?
    old := `ک`
    newR := ""
    fmt.Println(strings.Replace(str, old, newR, -1))

    buf := bytes.NewBuffer([]byte(str))
    //  for _, r := range str {
    //      buf.WriteRune(r)
    //  }
    fmt.Println(buf.Bytes())

    bs := []byte(strings.Replace(buf.String(), old, newR, -1))
    buf = bytes.NewBuffer(bs)

    fmt.Println("       ", buf.Bytes())
}

输出:

یا حال ھے؟
[218 169 219 140 216 167 32 216 173 216 167 217 132 32 218 190 219 146 216 159]
        [219 140 216 167 32 216 173 216 167 217 132 32 218 190 219 146 216 159]

5- strings.Replace非常有效,见内部:

// Replace returns a copy of the string s with the first n
// non-overlapping instances of old replaced by new.
// If old is empty, it matches at the beginning of the string
// and after each UTF-8 sequence, yielding up to k+1 replacements
// for a k-rune string.
// If n < 0, there is no limit on the number of replacements.
func Replace(s, old, new string, n int) string {
  if old == new || n == 0 {
      return s // avoid allocation
  }

  // Compute number of replacements.
  if m := Count(s, old); m == 0 {
      return s // avoid allocation
  } else if n < 0 || m < n {
      n = m
  }

  // Apply replacements to buffer.
  t := make([]byte, len(s)+n*(len(new)-len(old)))
  w := 0
  start := 0
  for i := 0; i < n; i++ {
      j := start
      if len(old) == 0 {
          if i > 0 {
              _, wid := utf8.DecodeRuneInString(s[start:])
              j += wid
          }
      } else {
          j += Index(s[start:], old)
      }
      w += copy(t[w:], s[start:j])
      w += copy(t[w:], new)
      start = j + len(old)
  }
  w += copy(t[w:], s[start:])
  return string(t[0:w])
}