有没有办法使这个golang代码更短?

1
2
3
4
5
6
7
8
9
10
func MergeSlices(s1 []float32, s2 []int32) []int {
    var slice []int
    for i := range s1 {
        slice = append(slice, int(s1[i]))
    }
    for i := range s2 {
        slice = append(slice, int(s2[i]))
    }
    return slice
}
  • 不,那里没有。
  • 除非需要使用特定大小的整数,否则避免使用int32。 使用int代替。 int在32位系统中将占用32位,在64位系统中将占用64位。 https://tour.golang.org/basics/11

您无法消除将每个元素分别转换为int的循环,因为您不能转换不同元素类型的整个切片。有关说明,请参见以下问题:在go中对接口的切片进行类型转换

您最多可以使用命名结果类型和带有两个迭代值的for range,您可以在其中将第一个(索引)分配给空白标识符,从而省略第一个(索引),而第二个将是值:

1
2
3
4
5
6
7
8
9
func MergeSlices(s1 []float32, s2 []int32) (s []int) {
    for _, v := range s1 {
        s = append(s, int(v))
    }
    for _, v := range s2 {
        s = append(s, int(v))
    }
    return
}

但是请知道,您的代码保持原样。我的代码不是总是要遵循的东西,而是要回答您的问题:如何使代码更短。如果要改进代码,可以先查看其性能,甚至重构代码以最终不必合并不同类型的切片。

  • 我绝对不建议使用命名的return保存一行。
  • @WilliamPoussier也许不保存1行,但是它有其用途(例如记录),有时是必需的(例如在发生紧急情况之前在返回之前更改返回值)。无论如何,即使是初始代码也不需要缩短。
  • 我同意那些特定的用例,使用命名的返回值是合法的。我想指出的是,向任何新手提及此事时也应附带其良好的做法。
  • 我是golang的新手,所以谢谢大家,它真的很有帮助。
  • @icza:您的用例似乎不适用于您的示例。请参阅转到代码查看注释,命名结果参数。
  • @peterSO我同意。我所展示的并不是遵循的内容,而是要回答这个问题:如何使代码更短。
  • 从技术上讲,在32位系统上,可以使用unsafe程序包将int32 slice重新解释为int slice。我认为。但是绝对不是浮动的。

您的代码应正确,可维护,可读且合理有效。请注意,代码短缺不是重要目标之一。有充分的理由,Stack Exchange还有另一个有关Code Golf问题的站点:Programming Puzzles&Code Golf。

您的代码可以改进;效率低下。例如,合并两个len(256)个切片,

1
BenchmarkMergeSlices      200000      8350 ns/op    8184 B/op     10 allocs/op

这是一个更高效(和更长)的版本:

1
BenchmarkMergeSlices      300000      4420 ns/op    4096 B/op      1 allocs/op

1
2
3
4
5
6
7
8
9
10
func MergeSlices(s1 []float32, s2 []int32) []int {
    slice := make([]int, 0, len(s1)+len(s2))
    for i := range s1 {
        slice = append(slice, int(s1[i]))
    }
    for i := range s2 {
        slice = append(slice, int(s2[i]))
    }
    return slice
}

将"转到代码查看注释"用于命名结果参数。例如:"不要为避免在函数内部声明var而命名结果参数;这样做会牺牲一些实现的简短性,但会浪费不必要的API冗长。文档的清晰性始终比在您的代码中节省一两行更为重要。功能。"

1
2
3
4
var s1 []int
var s2 []int

newSlice = append(s1, s2...)

代码不能再短一些,但这是一个值得怀疑的目标。它不是太冗长。但是,您可以通过消除中间分配来提高性能。每次调用append时,如果目标切片没有足够的空间,它将对其进行扩展,并猜测所需的大小,因为您没有告诉它需要多少空间。

最简单的方法就是预先确定目标切片的大小(用slice := make([]int, 0, len(s1) + len(s2))替换var slice []int;这样,附加就不必扩展它了。将第二个参数设置为0很重要,它将长度设置为零,将容量设置为所需的总大小,以便您的附件可以按预期工作。

不过,一旦调整好大小,就可以完全摆脱附加条件,直接设置每个索引:

1
2
3
4
5
6
7
8
9
10
func MergeSlices(s1 []float32, s2 []int32) []int {
    slice := make([]int, len(s1) + len(s2))
    for i,v := range s1 {
        slice[i] = int(v)
    }
    for i,v := range s2 {
        slice[i+len(s1)] = int(v)
    }
    return slice
}

游乐场链接

  • 他没有填写索引。用索引填充的是slice = append(slice, int(i)),而不是slice = append(slice, int(s1[i]))。请注意,它会将s1中的值附加到索引i处,而不是索引i本身。而且,切片分配相当便宜。 Go使用指数切片分配,因此摊销到O(1)。还值得注意的是,当两个条带均为0时,您的版本会更改输出,返回长度为0的条带而不是nil条带。无论哪种方式,我仍然会使用append()版本来避免第二个循环中奇怪的slice[i+len(s1)]索引。
  • 是的,我忽略了这一点。我会相应地纠正我的答案。而且虽然分片分配便宜,但是一次执行总是比重复执行便宜,并且一次执行到正确的大小将避免过度分配,而没有预分配的追加很有可能会这样做(指数大小调整的机会得到了最终分配位置的尺寸很小)。
  • 更新了关于分配的评论,因为它确实引入了一种附加的语义:即使两个输入列表均为零,它也会分配基础数组,从而导致返回的切片为非nil但长度为零,而不是它将为nil的切片在OPs版本中。
  • 同样适用,但值得商which。就个人而言,如果我放入两个空片,我希望返回一个空片,而不是nil;但是我不能说nil或空切片在客观上是"更好"的。
  • 我看不到空片会更好的情况。两者之间唯一的机械区别是,零切片不涉及基础数组的分配。因此,使用零长度切片会导致此函数在不需要时会触发附加分配。
  • 考虑到您最初的说法是"切片分配相当便宜",这是一个奇怪的抱怨,但是可以。我认为两者之间的差异可以忽略不计,而且ID倾向于保留空白部分以保持一致性,但ID再次说,这完全取决于个人喜好。考虑到两个空输入的情况可能相当少见,我认为中间分配的减少将远远超过偶然的,单个的,小的,不必要的分配。您可以添加一个初始空支票&return nil,如果它使您感到困扰。
  • 哦,可以肯定,而且我通常会在代码中放入这样的检查(当然,我只是更喜欢nil)。 在这种情况下,我的主要观点实际上是首选append(尽管我也经常将您的版本与索引一起使用),因为双重循环和将第二个循环的第一个列表的长度添加到索引的怪异要求。 在这两者中都使用append不太可能引起问题,例如,如果您扩展功能以在两者之间获取第三个切片,或者在切片的开头或其他地方添加了一些初始静态值。