Skip to content

Commit

Permalink
sliceop{,/f64s}: introduce generics sliceop
Browse files Browse the repository at this point in the history
Move and generalize code from sliceop/f64s into sliceop.

name                old time/op    new time/op    delta
Take/Len=2-8          2.69ns ± 1%    2.71ns ± 2%   +0.75%  (p=0.043 n=18+18)
Take/Len=4-8          3.18ns ± 1%    3.23ns ± 3%   +1.27%  (p=0.005 n=19+20)
Take/Len=8-8          3.83ns ± 1%    3.73ns ± 1%   -2.69%  (p=0.000 n=20+19)
Take/Len=128-8        44.1ns ± 4%    35.9ns ± 2%  -18.61%  (p=0.000 n=20+20)
Take/Len=1024-8        337ns ± 2%     280ns ± 6%  -16.73%  (p=0.000 n=19+20)
Take/Len=1048576-8    1.37ms ± 3%    1.31ms ± 5%   -3.97%  (p=0.000 n=19+20)

name                old alloc/op   new alloc/op   delta
Take/Len=2-8           0.00B          0.00B          ~     (all equal)
Take/Len=4-8           0.00B          0.00B          ~     (all equal)
Take/Len=8-8           0.00B          0.00B          ~     (all equal)
Take/Len=128-8         0.00B          0.00B          ~     (all equal)
Take/Len=1024-8        0.00B          0.00B          ~     (all equal)
Take/Len=1048576-8     0.00B          0.00B          ~     (all equal)

name                old allocs/op  new allocs/op  delta
Take/Len=2-8            0.00           0.00          ~     (all equal)
Take/Len=4-8            0.00           0.00          ~     (all equal)
Take/Len=8-8            0.00           0.00          ~     (all equal)
Take/Len=128-8          0.00           0.00          ~     (all equal)
Take/Len=1024-8         0.00           0.00          ~     (all equal)
Take/Len=1048576-8      0.00           0.00          ~     (all equal)

Updates #868.
  • Loading branch information
sbinet committed Apr 4, 2022
1 parent 582dce6 commit 2b4b393
Show file tree
Hide file tree
Showing 5 changed files with 322 additions and 81 deletions.
86 changes: 5 additions & 81 deletions sliceop/f64s/f64s.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,72 +5,28 @@
// Package f64s provides common operations on float64 slices.
package f64s

import (
"fmt"
)

var (
errLength = fmt.Errorf("f64s: length mismatch")
errSortedIndices = fmt.Errorf("f64s: indices not sorted")
errDuplicateIndices = fmt.Errorf("f64s: duplicate indices")
)
import "go-hep.org/x/hep/sliceop"

// Filter creates a slice with all the elements x_i of src for which f(x_i) is true.
// Filter uses dst as work buffer, storing elements at the start of the slice.
// Filter clears dst if a slice is passed, and allocates a new slice if dst is nil.
func Filter(dst, src []float64, f func(v float64) bool) []float64 {

if dst == nil {
dst = make([]float64, 0, len(src))
}

dst = dst[:0]
for _, x := range src {
if f(x) {
dst = append(dst, x)
}
}

return dst
return sliceop.Filter(dst, src, f)
}

// Map creates a slice with all the elements f(x_i) where x_i are elements from src.
// Map uses dst as work buffer, storing elements at the start of the slice.
// Map allocates a new slice if dst is nil.
// Map will panic if the lengths of src and dst differ.
func Map(dst, src []float64, f func(v float64) float64) []float64 {

if dst == nil {
dst = make([]float64, len(src))
}

if len(src) != len(dst) {
panic(errLength)
}

for i, x := range src {
dst[i] = f(x)
}
return dst
return sliceop.Map(dst, src, f)
}

// Find creates a slice with all indices corresponding to elements for which f(x) is true.
// Find uses dst as work buffer, storing indices at the start of the slice.
// Find clears dst if a slice is passed, and allocates a new slice if dst is nil.
func Find(dst []int, src []float64, f func(v float64) bool) []int {

if dst == nil {
dst = make([]int, 0, len(src))
}

dst = dst[:0]
for i, x := range src {
if f(x) {
dst = append(dst, i)
}
}

return dst
return sliceop.Find(dst, src, f)
}

// Take creates a sub-slice of src with all elements indiced by the provided indices.
Expand All @@ -80,37 +36,5 @@ func Find(dst []int, src []float64, f func(v float64) bool) []int {
// Take will panic if length of indices is larger than length of src.
// Take will panic if length of indices is different from length of dst.
func Take(dst, src []float64, indices []int) []float64 {

if len(indices) > len(src) {
panic(errLength)
}

if dst == nil {
dst = make([]float64, len(indices))
}

if len(dst) != len(indices) {
panic(errLength)
}

if len(indices) == 0 {
return dst
}

dst[0] = src[indices[0]]
for i := 1; i < len(indices); i++ {
v0 := indices[i-1]
v1 := indices[i]
switch {
case v0 < v1:
// ok.
case v0 == v1:
panic(errDuplicateIndices)
case v0 > v1:
panic(errSortedIndices)
}
dst[i] = src[v1]
}

return dst
return sliceop.Take(dst, src, indices)
}
7 changes: 7 additions & 0 deletions sliceop/f64s/f64s_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ func panics(t *testing.T, want error) func() {
}

func TestMap(t *testing.T) {
var errLength = fmt.Errorf("sliceop: length mismatch")
defer panics(t, errLength)()

_ = Map(make([]float64, 3), make([]float64, 5), nil)
}

func TestTake(t *testing.T) {
var (
errLength = fmt.Errorf("sliceop: length mismatch")
errSortedIndices = fmt.Errorf("sliceop: indices not sorted")
errDuplicateIndices = fmt.Errorf("sliceop: duplicate indices")
)

for _, tc := range []struct {
dst, src []float64
inds []int
Expand Down
119 changes: 119 additions & 0 deletions sliceop/sliceop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright ©2021 The go-hep Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.18

// Package sliceop provides operations on slices not available in the stdlib
// slices package.
package sliceop // import "go-hep.org/x/hep/sliceop"

import (
"fmt"
)

var (
errLength = fmt.Errorf("sliceop: length mismatch")
errSortedIndices = fmt.Errorf("sliceop: indices not sorted")
errDuplicateIndices = fmt.Errorf("sliceop: duplicate indices")
)

// Filter creates a slice with all the elements x_i of src for which f(x_i) is true.
// Filter uses dst as work buffer, storing elements at the start of the slice.
// Filter clears dst if a slice is passed, and allocates a new slice if dst is nil.
func Filter[T any](dst, src []T, f func(v T) bool) []T {

if dst == nil {
dst = make([]T, 0, len(src))
}

dst = dst[:0]
for _, x := range src {
if f(x) {
dst = append(dst, x)
}
}

return dst
}

// Map creates a slice with all the elements f(x_i) where x_i are elements from src.
// Map uses dst as work buffer, storing elements at the start of the slice.
// Map allocates a new slice if dst is nil.
// Map will panic if the lengths of src and dst differ.
func Map[T, U any](dst []U, src []T, f func(v T) U) []U {

if dst == nil {
dst = make([]U, len(src))
}

if len(src) != len(dst) {
panic(errLength)
}

for i, x := range src {
dst[i] = f(x)
}
return dst
}

// Find creates a slice with all indices corresponding to elements for which f(x) is true.
// Find uses dst as work buffer, storing indices at the start of the slice.
// Find clears dst if a slice is passed, and allocates a new slice if dst is nil.
func Find[T any](dst []int, src []T, f func(v T) bool) []int {

if dst == nil {
dst = make([]int, 0, len(src))
}

dst = dst[:0]
for i, x := range src {
if f(x) {
dst = append(dst, i)
}
}

return dst
}

// Take creates a sub-slice of src with all elements indiced by the provided indices.
// Take uses dst as work buffer, storing elements at the start of the slice.
// Take clears dst if a slice is passed, and allocates a new slice if dst is nil.
// Take will panic if indices is not sorted or has duplicates.
// Take will panic if length of indices is larger than length of src.
// Take will panic if length of indices is different from length of dst.
func Take[T any](dst, src []T, indices []int) []T {

if len(indices) > len(src) {
panic(errLength)
}

if dst == nil {
dst = make([]T, len(indices))
}

if len(dst) != len(indices) {
panic(errLength)
}

if len(indices) == 0 {
return dst
}

dst[0] = src[indices[0]]
for i := 1; i < len(indices); i++ {
v0 := indices[i-1]
v1 := indices[i]
switch {
case v0 < v1:
// ok.
case v0 == v1:
panic(errDuplicateIndices)
case v0 > v1:
panic(errSortedIndices)
}
dst[i] = src[v1]
}

return dst
}
51 changes: 51 additions & 0 deletions sliceop/sliceop_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright ©2022 The go-hep Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package sliceop_test

import (
"fmt"

f64s "go-hep.org/x/hep/sliceop"
)

// An example of slice filtering
func ExampleFilter() {
slice := []float64{1, 2, -99, 4, 5, -99, 7}
condition := func(x float64) bool { return x > 0 }
fmt.Println(f64s.Filter(nil, slice, condition))

// Output:
// [1 2 4 5 7]
}

// An example of slice mapping
func ExampleMap() {
slice := []float64{1, 2, -99, 4, 5, -99, 7}
operation := func(x float64) float64 { return x * x }
fmt.Println(f64s.Map(nil, slice, operation))

// Output:
// [1 4 9801 16 25 9801 49]
}

// An example of slice finding
func ExampleFind() {
slice := []float64{1, 2, -99, 4, 5, -99, 7}
condition := func(x float64) bool { return x == -99 }
fmt.Println(f64s.Find(nil, slice, condition))

// Output:
// [2 5]
}

// An example of taking a sub-slice defined by indices
func ExampleTake() {
slice := []float64{1, 2, -99, 4, 5, -99, 7}
indices := []int{2, 5}
fmt.Println(f64s.Take(nil, slice, indices))

// Output:
// [-99 -99]
}
Loading

0 comments on commit 2b4b393

Please sign in to comment.