【问题标题】:Golang: find first character in a String that doesn't repeatGolang:在不重复的字符串中查找第一个字符
【发布时间】:2014-05-25 15:59:00
【问题描述】:

我正在尝试编写一个函数,该函数返回在不重复的字符串中找到的第一个字符,到目前为止我有这个:

package main

import (
    "fmt"
    "strings"
)

func check(s string) string {

    ss := strings.Split(s, "")
    smap := map[string]int{}

    for i := 0; i < len(ss); i++ {
        (smap[ss[i]])++

    }

    for k, v := range smap {

        if v == 1 {
            return k
        }
    }

    return ""
}

func main() {
    fmt.Println(check("nebuchadnezzer"))

}

不幸的是,在 Go 中,当您迭代地图时,无法保证顺序,所以每次运行代码时,我都会得到不同的值,是否有任何指针?

【问题讨论】:

  • 你没有定义你想要的顺序。输入顺序?代码点顺序?您是将源视为 8 位字节串还是 unicode 字符串?
  • @Krait,不将字符串视为 unicode 字符串真的有意义吗?如果他不关心我假设的 unicode,他将使用 []byte。
  • @OneOfOne 字符串在 Go 中传统上编码为 utf8,但它们也可以被视为任意字节字符串。没有社区强调 []byte 仅在已知“字符串”不是 utf8 时使用。

标签: go


【解决方案1】:

使用地图和 2 个循环: play

func check(s string) string {
    m := make(map[rune]uint, len(s)) //preallocate the map size
    for _, r := range s {
        m[r]++
    }

    for _, r := range s {
        if m[r] == 1 {
            return string(r)
        }
    }
    return ""
}

如果您使用strings.ContainsRunestrings.IndexRune,这样做的好处是只使用 2 个循环而不是多个循环(每个函数中都有内部循环)。

【讨论】:

  • uint16 的使用不正确。您的算法会在包含 (1 + 65536 的任意倍数) 任何符文重复的字符串上产生不正确的输出。使用 int 而不是 uint16,因为没有 Go 字符串的长度可以超过“max int”字节,这意味着它也不能超过那么多 rune。
  • 很好,切换到 uint。
  • +1。这似乎肯定比我的答案更优化。
  • OneofOne 我选择了您的答案,因为它似乎具有较少的活动部件,并消除了字符串函数中包含的内部循环。有没有办法优化功能并有一个循环?
  • 据我所知,不,您必须有某种第二个循环,或者使用strings.Contains 等隐藏更多的方式。
【解决方案2】:

获取所有或第一个唯一字节http://play.golang.org/p/ZGFepvEXFT的高效(时间和内存)算法:

func FirstUniqueByte(s string) (b byte, ok bool) {
    occur := [256]byte{}
    order := make([]byte, 0, 256)
    for i := 0; i < len(s); i++ {
        b = s[i]
        switch occur[b] {
        case 0:
            occur[b] = 1
            order = append(order, b)
        case 1:
            occur[b] = 2
        }
    }
    for _, b = range order {
        if occur[b] == 1 {
            return b, true
        }
    }
    return 0, false
}

作为奖励,上述函数永远不会产生任何垃圾。请注意,我将您的函数签名更改为一种更惯用的方式来表达您所描述的内容。如果您无论如何都需要func(string) string 签名,那么这一点就没有实际意义了。

【讨论】:

  • +1。比我的答案更优化,也更惯用。
  • 但是我的特别不会处理 unicode,所以如果需要 unicode,优化是没有意义的。我的 order 变量也是一个微优化,它不会真正帮助典型的输入,例如“kitten”,但会跳过查看,例如当输入为 5001 a 后跟单个 b 时,第二个循环中的 5000 字节。
  • @krait:-1。您只处理 ASCII 字符 (
  • @peterSO 不,我处理不透明字节(
【解决方案3】:

当然可以优化,但一种解决方案(不使用地图)是:
(playground example)

func check(s string) string {
    unique := ""
    for pos, c := range s {
        if strings.ContainsRune(unique, c) {
            unique = strings.Replace(unique, string(c), "", -1)
        } else if strings.IndexRune(s, c) == pos {
            unique = unique + string(c)
        }
    }
    fmt.Println("All unique characters found: ", unique)
    if len(unique) > 0 {
        _, size := utf8.DecodeRuneInString(unique)
        return unique[:size]
    }
    return ""
}

这是在问题“Find the first un-repeated character in a string”之后

krait 建议below 该函数应该:

返回一个包含第一个完整符文的字符串,而不仅仅是第一个符文的 utf8 编码的第一个字节。

【讨论】:

  • rune -&gt; stringbyte -&gt; string 转换是不受欢迎的,尽管由于 Go 1 保证它被困在语言中。此外,您实际上并没有生成包含第一个唯一符文的字符串;您正在生成第一个唯一 utf8 字节前缀的第一个 utf8 字节的字符串。
  • 按原样使用字符串连接,每个输入符文都会产生一块垃圾,算法是 O(n^2),而不是最优的 O(n)。此外,fmt.Println 会为您插入空格。
  • @krait 当然,我希望有更多知识的人来说明正确的算法。
  • 除了 unique[0] 部分之外,您的算法大多是正确的。剩下的只是优化。
  • @krait 很抱歉被无意识的编辑审阅机器人拒绝编辑 (stackoverflow.com/review/suggested-edits/4900866) ;) 我手动包含了您的编辑,并相应地修改了操场示例。
猜你喜欢
  • 2011-01-18
  • 2013-09-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多