以下内容有效,但我将如何对其进行优化?我想遍历数组会变得越来越昂贵。 我可以创建原始数组的映射表来存储每个值的出现次数,然后在另一个循环中检查这些值的+ /-/ 0,但这甚至更糟。
package main
import (
"fmt"
)
func main() {
arr := []int{2, 5, 6, 7, 8, 2, 4, 1, 1, 1, 2, -2, -2, 2, 2, 3, -1, 0, 0, 0, 0, 2, 5, 4, 9, 8, 7, 2, -3, -7}
var p, n, z int = 0, 0, 0
for _, v := range arr {
if v > 0 {
p++
} else if v < 0 {
n++
} else if v == 0 {
z++
}
}
fmt.Println(p, n, z)
}
答案 0 :(得分:1)
如果您的输入结构是未排序的数组,那么O(n)是您可以做的最好的事情,也就是说,遍历该数组,对每个元素进行一次比较。
如果可以选择,则可以使用两个数组和一个整数,一个数组用于负数,一个数组用于正数,以及一个整数以计算零的数量。然后,不再需要计数,您只需获取数组的长度即可。
答案 1 :(得分:0)
您几乎可以找到最佳解决方案。我实现了@bserdar的建议“首先进行排序”并针对它进行了基准测试。
注意:这是一个非常粗糙的实现。将其与一磅盐一起食用。
为了便于阅读,省略了打包和导入。
var slice = []int{2, 5, 6, 7, 8, 2, 4, 1, 1, 1, 2, -2, -2, 2, 2, 3, -1, 0, 0, 0, 0, 2, 5, 4, 9, 8, 7, 2, -3, -7}
func orig(s []int) (negative, zero, positive int) {
for _, v := range s {
if v > 0 {
positive++
} else if v < 0 {
negative++
} else if v == 0 {
zero++
}
}
return
}
func sorted(s []int) (negative, zero, positive int) {
// We do not want to modify the input slice,
// so we need to create a copy of it
sortedSlice := make([]int, len(s))
copy(sortedSlice, s)
sort.Ints(sortedSlice)
return preSorted(sortedSlice)
}
func preSorted(s []int) (int, int, int) {
var z, p int
var zfound bool
for i := 0; i < len(s); i++ {
if s[i] < 0 {
continue
} else if !zfound && s[i] == 0 {
zfound = true
z = i
} else if s[i] > 0 {
p = i
break
}
}
return z, p - z, len(s) - p
}
测试代码:
func BenchmarkOrig(b *testing.B) {
for i := 0; i < b.N; i++ {
orig(slice)
}
}
func BenchmarkLongOrig(b *testing.B) {
var slice = make([]int, 10000000)
for i := 0; i < 10000000; i++ {
slice[i] = rand.Intn(10)
if rand.Intn(2) == 0 {
slice[i] = slice[i] * -1
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
orig(slice)
}
}
func BenchmarkSorted(b *testing.B) {
for i := 0; i < b.N; i++ {
sorted(slice)
}
}
func BenchmarkLongSorted(b *testing.B) {
var slice = make([]int, 10000000)
for i := 0; i < 10000000; i++ {
slice[i] = rand.Intn(10)
if rand.Intn(2) == 0 {
slice[i] = slice[i] * -1
}
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
sorted(slice)
}
}
func BenchmarkPresorted(b *testing.B) {
cp := make([]int, len(slice))
copy(cp, slice)
sort.Ints(cp)
b.ResetTimer()
for i := 0; i < b.N; i++ {
preSorted(cp)
}
}
func BenchmarkLongPresorted(b *testing.B) {
var slice = make([]int, 10000000)
for i := 0; i < 10000000; i++ {
slice[i] = rand.Intn(10)
if rand.Intn(2) == 0 {
slice[i] = slice[i] * -1
}
}
sort.Ints(slice)
b.ResetTimer()
for i := 0; i < b.N; i++ {
sorted(slice)
}
}
根据基准:
goos: darwin
goarch: amd64
BenchmarkOrig-4 27271665 38.4 ns/op 0 B/op 0 allocs/op
BenchmarkLongOrig-4 21 50343196 ns/op 0 B/op 0 allocs/op
BenchmarkSorted-4 1405150 852 ns/op 272 B/op 2 allocs/op
BenchmarkLongSorted-4 2 536973066 ns/op 80003104 B/op 2 allocs/op
BenchmarkPresorted-4 100000000 10.9 ns/op 0 B/op 0 allocs/op
BenchmarkLongPresorted-4 5 248698010 ns/op 80003104 B/op 2 allocs/op
编辑发现了一种更有效的返回计数的方法。我们无需创建新的切片,而是计算每个子切片的长度。当切片较小时,这使得预排序非常有效。但是在10M时,简单计数似乎是最有效的。
已确认
答案 2 :(得分:0)
最快的方法是:
a)确保阵列/切片正在使用最小的数据类型(以减少RAM的数量和触及的缓存行的数量;将更多的值打包到单个SIMD寄存器中,并减少移位量I稍后再建议)-例如对于您在问题中显示的值,您可以/应该使用int8
(而不是int
)。
b)在末尾添加零,以将数组/切片填充到CPU使用SIMD可以一次完成的许多元素的倍数(例如,如果在80x86 CPU上使用int8
,则可以使用32个元素)支持AVX2)。当您接近阵列/切片的末尾时,这基本上只是避免了麻烦的麻烦。
c)循环使用SIMD:
d)之后(在循环之外):
通过对“负数计数器组”进行“水平加法”来计算负数的计数
通过对“非零数字的计数器组”进行“水平加法”,然后减去负数的计数来计算正数的计数
通过执行“ zeros = all_numbers-negative_numbers-positive_numbers-padding_zeros”来计算零的计数
当然,要想做好任何事情,您都需要内联汇编,这意味着您需要使用https://godoc.org/github.com/slimsag/rand/simd之类的东西(以一种很好的可移植方式为您进行内联汇编)。
注1:对于大型阵列/切片(但不是小型阵列/切片),您还希望并行使用多个CPU(例如,如果有N个CPU,则具有N个线程/ goroutines,然后将阵列/切片分成N个每个线程/ goroutine做一件事情,然后在执行“步骤d)”之前添加每件事情的计数。
注2:用于大量数据;我的算法是“ O(n)”,并且因为您的原始算法只有“ O(n)”,所以我希望我的算法在现代硬件上的速度快100倍。但是,对于少量数据,由于“ O(n)”不是线性的,所以我希望您的算法比我的算法更快。