如何在Go中反转字符串?


Answers:


94

在Go1中,符文是内置类型。

func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

4
您不能在Go中使用len()来找出字符串/数组/切片等的长度...这是为什么?-Go中的len()表示输入大小(以字节为单位)。它与其长度不符。-并非所有utf8的符文都大小相同。它可以是1、2、4或8。-您应该使用unicode / ut8包的方法RuneCountInString来获取符文的长度。
Anvesh Checka 2014年

15
@AnveshChecka,那是不正确的。请参阅golang.org/pkg/builtin/#len-切片上的len()肯定会返回元素数,而不是字节数。一片符文是正确的方法。
chowey

3
@рытфолд这不适用于组合字符。请参阅play.golang.org/p/sBgZAV7gCb,合并字符不会与其基数互换。
chowey

53

在golang-nuts邮件列表上的Russ Cox建议

package main 
import "fmt"
func main() { 
        input := "The quick brown 狐 jumped over the lazy 犬" 
        // Get Unicode code points. 
        n := 0
        rune := make([]rune, len(input))
        for _, r := range input { 
                rune[n] = r
                n++
        } 
        rune = rune[0:n]
        // Reverse 
        for i := 0; i < n/2; i++ { 
                rune[i], rune[n-1-i] = rune[n-1-i], rune[i] 
        } 
        // Convert back to UTF-8. 
        output := string(rune)
        fmt.Println(output)
}

20
我喜欢他们如何迫使您考虑编码。
捷尔吉Andrasek

10
题外话:为什么是[golang-nuts]而不是[go-nuts]?
吉米

2
哇,换向时wtf用双重赋值了吗?有趣。现在,考虑一串具有不规则符文的字符串。中间的部分经过特殊处理,但最终结果正确。:)一个有趣的小优化,我不会马上想到的。
Kissaki

4
我不明白为什么要转换为符文,为什么不rune:=[]rune(input)
siritinga 2014年

1
您不需要第一个for range循环。输出:= [] rune(input); n:= len(输出)而且您不需要rune = rune [0:n]
dvallejo 2015年

29

这可以正常工作,而不会引起所有功能的困扰:

func Reverse(s string) (result string) {
  for _,v := range s {
    result = string(v) + result
  }
  return 
}

6
虽然它有效,但是由于字符串是不可变的,因此效率很低。我发布了一个更有效的解决方案。
peterSO 2011年

5
这是太容易理解了。使它变得更困难:-)(以及“加一”,以便继续学习)
Roboprog

2
这是最好的答案,除非颠倒字符串是您的瓶颈。
Banjocat

1
@dolmen-为什么这不能处理组合字符?字符串上的范围返回一个符文,即代码点。
Stan R.

1
@StanR。符文不是雕文。一个字形可以由多个代码点/符文组成。请参阅reedbeta.com/blog/programmers-intro-to-unicode/#combining-marks反向代码点会将组合标记附加到其他基本代码点。
支石墓

14

通过考虑以下两点,可以对unicode字符串起作用:

  • range通过枚举unicode字符对字符串起作用
  • 可以从int slices构造字符串,其中每个元素都是unicode字符。

因此,它去了:

func reverse(s string) string {
    o := make([]int, utf8.RuneCountInString(s));
    i := len(o);
    for _, c := range s {
        i--;
        o[i] = c;
    }
    return string(o);
}

我会分配i:=len(o)-1for,然后将for折叠成一行for _, c:=range s { o[i--]=c; }。老兄,我讨厌无括号的for-是否允许这样做:for(_, c:=range s) { o[i--]=c; }
Lawrence Dol

您能解释一下_的作用吗?
劳伦斯·多尔

6
@Software_Monkey:o [i--] = c在Go中是不允许的。-和++是语句,而不是表达式。_表示放弃(忽略)该变量。
Randy Sugianto'Yuku',2009年

1
使用go 1.1+时,它将在string([] int)行中返回错误,如果不是,则将[] rune类型用于所有工作
Otuk 2013年

1
@yuku:在s上仍然失败:=“ Les Mise \ u0301rables”
Stefan Steiger

13

Go示例项目:golang / example / stringutil / reverse.go,作者:Andrew Gerrand

/*
Copyright 2014 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// Reverse returns its argument string reversed rune-wise left to right.
func Reverse(s string) string {
    r := []rune(s)
    for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
        r[i], r[j] = r[j], r[i]
    }
    return string(r)
}

去操场换一个弦

反转字符串“bròwn”后,正确的结果应该是“nwòrb”,而不是“nẁorb”。
注意字母o上方的坟墓。


要保留将诸如“as⃝df̅”之类的字符与反向结果“f̅ds⃝a”结合在一起的Unicode,
请参考下面列出的另一个代码:

http://rosettacode.org/wiki/Reverse_a_string#Go


2
感谢您澄清与stackoverflow.com/a/10030772/3093387的区别-似乎这两种解决方案在处理“ brwn”之类的字符串方面存在差异。
josliber

感谢您提到处理组合字符的Rosettacode解决方案
dolmen

11

Simon发布他的解决方案时,我注意到了这个问题,因为字符串是不可变的,所以效率很低。提出的其他解决方案也存在缺陷。他们不起作用或效率低下。

这是一个有效的解决方案,除了当字符串无效的UTF-8或字符串包含组合字符时。

package main

import "fmt"

func Reverse(s string) string {
    n := len(s)
    runes := make([]rune, n)
    for _, rune := range s {
        n--
        runes[n] = rune
    }
    return string(runes[n:])
}

func main() {
    fmt.Println(Reverse(Reverse("Hello, 世界")))
    fmt.Println(Reverse(Reverse("The quick brown 狐 jumped over the lazy 犬")))
}

1
return string(runes)也可以。

3
@汤米:不,return string(runes)并非在所有情况下都适用。
peterSO

您能否再解释一下为什么呢?我做了一个简短的程序,它在那里工作,但是也许您所说的那些情况不是在那里触发的?play.golang.org/p/yk1sAwFjol

1
@Tommy:您的简短程序仅演示了NUL字符在发送到打印机或终端时是NOP。对于非ASCII UTF-8编码的字符串,您的Reverse2函数失败。我已经修改了您的简短程序,使其成为有效测试:play.golang.org/p/Ic5G5QEO93
peterSO 2013年

另一种错误的“解决方案”,不能正确组合字符。
支石墓

9

这里有太多答案。其中一些是明显的重复项。但是,即使从左起,也很难选择最佳解决方案。

因此,我仔细研究了答案,扔掉了不适用于unicode的答案,并删除了重复项。我对幸存者进行了基准测试,以找到最快的幸存者。因此,这是带归属的结果(如果您注意到我错过的答案,但值得添加,请随时修改基准):

Benchmark_rmuller-4   100000         19246 ns/op
Benchmark_peterSO-4    50000         28068 ns/op
Benchmark_russ-4       50000         30007 ns/op
Benchmark_ivan-4       50000         33694 ns/op
Benchmark_yazu-4       50000         33372 ns/op
Benchmark_yuku-4       50000         37556 ns/op
Benchmark_simon-4       3000        426201 ns/op

所以这是rmuller最快的方法

func Reverse(s string) string {
    size := len(s)
    buf := make([]byte, size)
    for start := 0; start < size; {
        r, n := utf8.DecodeRuneInString(s[start:])
        start += n
        utf8.EncodeRune(buf[size-start:], r)
    }
    return string(buf)
}

由于某种原因,我无法添加基准,因此您可以从中复制基准PlayGround(您无法在其中运行测试)。重命名并运行go test -bench=.


这些“解决方案”都无法正确处理组合标记
支石墓

6

我编写了以下Reverse尊重UTF8编码和组合字符的函数:

// Reverse reverses the input while respecting UTF8 encoding and combined characters
func Reverse(text string) string {
    textRunes := []rune(text)
    textRunesLength := len(textRunes)
    if textRunesLength <= 1 {
        return text
    }

    i, j := 0, 0
    for i < textRunesLength && j < textRunesLength {
        j = i + 1
        for j < textRunesLength && isMark(textRunes[j]) {
            j++
        }

        if isMark(textRunes[j-1]) {
            // Reverses Combined Characters
            reverse(textRunes[i:j], j-i)
        } 

        i = j
    }

    // Reverses the entire array
    reverse(textRunes, textRunesLength)

    return string(textRunes)
}

func reverse(runes []rune, length int) {
    for i, j := 0, length-1; i < length/2; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
}

// isMark determines whether the rune is a marker
func isMark(r rune) bool {
    return unicode.Is(unicode.Mn, r) || unicode.Is(unicode.Me, r) || unicode.Is(unicode.Mc, r)
}

我尽力使它尽可能高效和可读。这个想法很简单,遍历符文寻找组合字符,然后就地反转组合字符的符文。一旦我们全部覆盖了它们,就可以就地反转整个字符串的符文。

假设我们想反转此字符串bròwn。的ò由两个符,一个用于表示o,一个用于这个unicode \u0301a,表示“严重”。

为了简单起见,让我们代表这样的字符串bro'wn。我们要做的第一件事是寻找组合字符并反转它们。现在我们有了字符串br'own。最后,我们反转整个字符串并以结尾nwo'rb。这是作为返回给我们的nwòrb

如果您想使用它,可以在这里https://github.com/shomali11/util找到。

以下是一些测试案例,以展示几种不同的情况:

func TestReverse(t *testing.T) {
    assert.Equal(t, Reverse(""), "")
    assert.Equal(t, Reverse("X"), "X")
    assert.Equal(t, Reverse("b\u0301"), "b\u0301")
    assert.Equal(t, Reverse("😎⚽"), "⚽😎")
    assert.Equal(t, Reverse("Les Mise\u0301rables"), "selbare\u0301siM seL")
    assert.Equal(t, Reverse("ab\u0301cde"), "edcb\u0301a")
    assert.Equal(t, Reverse("This `\xc5` is an invalid UTF8 character"), "retcarahc 8FTU dilavni na si `�` sihT")
    assert.Equal(t, Reverse("The quick bròwn 狐 jumped over the lazy 犬"), "犬 yzal eht revo depmuj 狐 nwòrb kciuq ehT")
}

3

基于Stephan202的原始建议,似乎适用于unicode字符串:

import "strings";

func Reverse( orig string ) string {
    var c []string = strings.Split( orig, "", 0 );

    for i, j := 0, len(c)-1; i < j; i, j = i+1, j-1 {
        c[i], c[j] = c[j], c[i]
    }

    return strings.Join( c, "" );
}

备用,不使用字符串包,但不使用“ unicode安全”:

func Reverse( s string ) string {
    b := make([]byte, len(s));
    var j int = len(s) - 1;
    for i := 0; i <= j; i++ {
        b[j-i] = s[i]
    }

    return string ( b );
}

+1。这样可行。但是,我必须说,对于这样一个简单的任务,拆分和
合并

@马丁:对不起,我的编辑。我不小心粘贴我更新的答案在你的问题...... 我很惭愧
09年

@Stephan-没问题。我添加了一个基于字符串包Bytes函数的替代解决方案。
马丁克莱顿

@Nosradena:我在同一分钟内回滚了(我很惊讶地看到Martin用我刚写的相同的文字更新了他的答案……然后它
突然出现

@martin:如果您问我,第二个版本看起来更好:)
Stephan202

3
//Reverse reverses string using strings.Builder. It's about 3 times faster
//than the one with using a string concatenation
func Reverse(in string) string {
    var sb strings.Builder
    runes := []rune(in)
    for i := len(runes) - 1; 0 <= i; i-- {
        sb.WriteRune(runes[i])
    }
    return sb.String()
}


//Reverse reverses string using string
func Reverse(in string) (out string) {
    for _, r := range in {
        out = string(r) + out
    }
    return
}

BenchmarkReverseStringConcatenation-8   1000000 1571 ns/op  176 B/op    29 allocs/op
BenchmarkReverseStringsBuilder-8        3000000 499 ns/op   56 B/op 6 allocs/op

使用string.Builder大约比使用字符串连接快3倍


1
我想知道,尽管这个问题的答案是最准确的,但为什么这个问题没有得到解决?
Nilesh

3

这是完全不同的,我会说更多的功能性方法,未在其他答案中列出:

func reverse(s string) (ret string) {
    for _, v := range s {
        defer func(r rune) { ret += string(r) }(v)
    }
    return
}

我敢肯定这不是最快的解决方案,但是它显示了ret每个defer函数如何将return变量保留在闭包中以进行进一步处理。
弗拉基米尔·鲍尔

速度慢,无法正确组合字符。
支石墓

1
我不确定速度有多快,但它很漂亮。
donatJ

Go 1.14中可能会提高这一性能。至少发行说明声称延迟的零开销。
弗拉基米尔·鲍尔

2

这是最快的实施

func Reverse(s string) string {
    size := len(s)
    buf := make([]byte, size)
    for start := 0; start < size; {
        r, n := utf8.DecodeRuneInString(s[start:])
        start += n
        utf8.EncodeRune(buf[size-start:], r)
    }
    return string(buf)
}

const (
    s       = "The quick brown 狐 jumped over the lazy 犬"
    reverse = "犬 yzal eht revo depmuj 狐 nworb kciuq ehT"
)

func TestReverse(t *testing.T) {
    if Reverse(s) != reverse {
        t.Error(s)
    }
}

func BenchmarkReverse(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Reverse(s)
    }
}

您是否声称解决方案是最快的实施方案?
DenysSéguret2014年

是的,我做到了,这就是为什么存在BenchmarkReverse代码的原因:)。但是我没有结果了。
rmuller 2014年

快速的解决方案,但是仍然存在错误,因为它无法正确组合字符。
支石墓

就像@dolmen所说的那样,这不正确吗?这里有解决方案吗?
杰拉尔德斯16-10-13

2

此代码保留了完整组合字符的顺序,并且也应与无效的UTF-8输入一起使用。

package stringutil
import "code.google.com/p/go.text/unicode/norm"

func Reverse(s string) string {
    bound := make([]int, 0, len(s) + 1)

    var iter norm.Iter
    iter.InitString(norm.NFD, s)
    bound = append(bound, 0)
    for !iter.Done() {
        iter.Next()
        bound = append(bound, iter.Pos())
    }
    bound = append(bound, len(s))
    out := make([]byte, 0, len(s))
    for i := len(bound) - 2; i >= 0; i-- {
        out = append(out, s[bound[i]:bound[i+1]]...)
    }
    return string(out)
}

如果unicode / norm原语允许在不分配的情况下遍历字符串边界,则效率可能更高一些。另请参阅https://code.google.com/p/go/issues/detail?id=9055


字符串值中没有这样的“无效的UTF-8输入”:从转换[]bytestringGo 时,将用有效的codepoint替换“无效的UTF-8输入” \uFFFD
支石墓

我不明白以上评论。您是否在说出包含无效UTF-8的字符串时此代码的行为是错误的?
rog

不。我是说Go string中不存在无效的UTF-8 。但它可以存在于[]byte
支石墓

Go字符串可以包含与[] byte一样多的无效utf-8。例如:play.golang.org/p/PG0I4FJfEN
ROG

2

如果需要处理字素簇,请使用unicode或regexp模块。

package main

import (
  "unicode"
  "regexp"
)

func main() {
    str := "\u0308" + "a\u0308" + "o\u0308" + "u\u0308"
    println("u\u0308" + "o\u0308" + "a\u0308" + "\u0308" == ReverseGrapheme(str))
    println("u\u0308" + "o\u0308" + "a\u0308" + "\u0308" == ReverseGrapheme2(str))
}

func ReverseGrapheme(str string) string {

  buf := []rune("")
  checked := false
  index := 0
  ret := "" 

    for _, c := range str {

        if !unicode.Is(unicode.M, c) {

            if len(buf) > 0 {
                ret = string(buf) + ret
            }

            buf = buf[:0]
            buf = append(buf, c)

            if checked == false {
                checked = true
            }

        } else if checked == false {
            ret = string(append([]rune(""), c)) + ret
        } else {
            buf = append(buf, c)
        }

        index += 1
    }

    return string(buf) + ret
}

func ReverseGrapheme2(str string) string {
    re := regexp.MustCompile("\\PM\\pM*|.")
    slice := re.FindAllString(str, -1)
    length := len(slice)
    ret := ""

    for i := 0; i < length; i += 1 {
        ret += slice[length-1-i]
    }

    return ret
}

我想给你一千票。此页面上的所有其他实现都错误地反转了STRING(STRING不是字符序列)。
Stefan Steiger

这行不通。如果对字符串进行两次反向操作,则不会得到原始字符串。在此示例中使用的前导组合大写字母(\ u0308)与前面的字符结合使用,当反转时,会创建双变音符号'a'。如果str输出用引号引起来,它将修改引号!
Joshua Kolden '18

2

您还可以导入现有的实现:

import "4d63.com/strrev"

然后:

strrev.Reverse("abåd") // returns "dåba"

或反转包含unicode组合字符的字符串:

strrev.ReverseCombining("abc\u0301\u031dd") // returns "d\u0301\u031dcba"

这些实现支持对Unicode多字节进行正确排序,并在反转时支持梳理字符。

注意:许多编程语言中的内置字符串反向函数不能保留组合,并且标识组合字符需要大量的执行时间。


1

当然,这不是内存效率最高的解决方案,但是对于“简单的” UTF-8安全解决方案,以下将完成工作且不会破坏符文。

我认为它是页面上最易读和易懂的。

func reverseStr(str string) (out string) {
    for _, s := range str {
        out = string(s) + out
    }

    return
}

1

以下两种方法的运行速度比保留组合字符的最快解决方案要快,但这并不是说我在基准测试设置中遗漏了一些东西。

//input string s
bs := []byte(s)
var rs string
for len(bs) > 0 {
    r, size := utf8.DecodeLastRune(bs)
    rs += fmt.Sprintf("%c", r)
    bs = bs[:len(bs)-size]
} // rs has reversed string

方法二灵感

//input string s
bs := []byte(s)
cs := make([]byte, len(bs))
b1 := 0
for len(bs) > 0 {
    r, size := utf8.DecodeLastRune(bs)
    d := make([]byte, size)
    _ = utf8.EncodeRune(d, r)
    b1 += copy(cs[b1:], d)
    bs = bs[:len(bs) - size]
} // cs has reversed bytes

这是基准测试中缺少的内容:解决方案速度更快,因为它不保留组合字符。比较它们是不公平的。
支石墓

1

注意:此答案来自2009年,因此目前可能有更好的解决方案。


看起来有点“回旋”,可能不是很有效,但说明了Reader接口如何用于从字符串读取。当使用utf8字符串时,IntVectors似乎也非常适合作为缓冲区。

当省略“大小”部分并通过Insert插入向量时,它甚至会更短,但我想这样做会效率较低,因为每次添加新的符文时,整个向量都需要向后推一。

此解决方案绝对适用于utf8字符。

package main

import "container/vector";
import "fmt";
import "utf8";
import "bytes";
import "bufio";


func
main() {
    toReverse := "Smørrebrød";
    fmt.Println(toReverse);
    fmt.Println(reverse(toReverse));
}

func
reverse(str string) string {
    size := utf8.RuneCountInString(str);
    output := vector.NewIntVector(size);
    input := bufio.NewReader(bytes.NewBufferString(str));
    for i := 1; i <= size; i++ {
        rune, _, _ := input.ReadRune();
        output.Set(size - i, rune);
    }
    return string(output.Data());
}

为什么要添加所有这些尾随分号?
Morteza R 2015年

@ olivier-mason是时候在共享Go代码时学习gofmt了。
支石墓

1
这个答案来自八年前。
奥利弗·梅森

@OliverMason修复(或删除)不完善的解决方案永远不会太晚。
支石墓

0

我认为该版本适用于unicode。它基于utf8.Rune函数构建:

func Reverse(s string) string {
    b := make([]byte, len(s));
    for i, j := len(s)-1, 0; i >= 0; i-- {
        if utf8.RuneStart(s[i]) {
            rune, size := utf8.DecodeRuneInString(s[i:len(s)]);
            utf8.EncodeRune(rune, b[j:j+size]);
            j += size;
        }
    }
    return string(b);
}

0

符文是一种类型,所以使用它。而且,Go不使用分号。

func reverse(s string) string {
    l := len(s)
    m := make([]rune, l)

    for _, c := range s {
        l--
        m[l] = c
    }
    return string(m)
}

func main() {
    str := "the quick brown 狐 jumped over the lazy 犬"
    fmt.Printf("reverse(%s): [%s]\n", str, reverse(str))
}

当发布该问题时,它使用分号。
OneOfOne 2014年

另一种错误的“解决方案”,不能正确地组合字符。
支石墓



-1

这是另一个解决方案:

func ReverseStr(s string) string {
    chars := []rune(s)
    rev := make([]rune, 0, len(chars))
    for i := len(chars) - 1; i >= 0; i-- {
        rev = append(rev, chars[i])
    }
    return string(rev)
}

但是,由于yazu的解决方案更为优雅,因为他将[]rune切片切回原位。


-1

另一个解决方案(tm):

package main 
import "fmt"

type Runes []rune

func (s Runes) Reverse() (cp Runes) {
    l := len(s); cp = make(Runes, l)
    // i <= 1/2 otherwise it will mess up with odd length strings
    for i := 0; i <= l/2; i++ { 
        cp[i], cp[l-1-i] = s[l-1-i], s[i] 
    }
    return cp
}

func (s Runes) String() string {
    return string(s)
}

func main() { 
    input := "The quick brown 狐 jumped over the lazy 犬 +odd" 
    r := Runes(input)
    output := r.Reverse()
    valid := string(output.Reverse()) == input
    fmt.Println(len(r), len(output), r, output.Reverse(), valid)
}

-1
package reverseString

import "strings"

// ReverseString - output the reverse string of a given string s
func ReverseString(s string) string {

    strLen := len(s)

    // The reverse of a empty string is a empty string
    if strLen == 0 {
        return s
    }

    // Same above
    if strLen == 1 {
        return s
    }

    // Convert s into unicode points
    r := []rune(s)

    // Last index
    rLen := len(r) - 1

    // String new home
    rev := []string{}

    for i := rLen; i >= 0; i-- {
        rev = append(rev, string(r[i]))
    }

    return strings.Join(rev, "")
}

测试

package reverseString

import (
    "fmt"
    "strings"
    "testing"
)

func TestReverseString(t *testing.T) {

    s := "GO je úžasné!"
    r := ReverseString(s)

    fmt.Printf("Input: %s\nOutput: %s", s, r)

    revR := ReverseString(r)

    if strings.Compare(s, revR) != 0 {
        t.Errorf("Expecting: %s\n. Got: %s\n", s, revR)
    }
}

输出量

Input: GO je úžasné!
Output: nsažú ej OG
PASS
ok      github.com/alesr/reverse-string 0.098s

如果输入使用NFC,则此方法有效。但是像这里大多数其他错误的解决方案一样,它不能与字符组合一起使用。
支石墓

-1
    func reverseString(someString string) string {
        runeString := []rune(someString)
        var reverseString string
        for i := len(runeString)-1; i >= 0; i -- {
            reverseString += string(runeString[i])
        }
        return reverseString
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.