Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/samber/lo

go 1.18
go 1.25
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please revert. I try to keep a good compatiibility with older go versions.


//
// Dev dependencies are excluded from releases. Please check CI.
Expand Down
20 changes: 20 additions & 0 deletions slice.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,32 @@
package lo

import (
"iter"
"sort"

"github.com/samber/lo/internal/constraints"
"github.com/samber/lo/mutable"
)

// SliceToSeq2 converts a slice into a sequence of pairs (key, value).
// Elements are paired consecutively: s[0] with s[1], s[2] with s[3], etc.
// If the slice has an odd number of elements, the last element is paired with the zero value of type E.
func SliceToSeq2[Slice ~[]E, E any](collection Slice) iter.Seq2[E, E] {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rename it Pairwise.

See the samber/ro implem: https://ro.samber.dev/docs/operator/combining#pairwise

Please write 2 implementations:

  • core package -> returns [][]T or []T2[T, T]
  • it/ package -> returns iter.Seq[[]T] or iter.Seq[T2[T, T]]

Using iter.Seq2 does not seem a good idea here, since first argument of Seq2 is expected to be a key.

I would use T2 tuples instead of simple slices []T.

return func(yield func(E, E) bool) {
n := len(collection)
var zero E
for i := 0; i < n; i += 2 {
v := zero
if i+1 < n {
v = collection[i+1]
}
if !yield(collection[i], v) {
return
}
}
}
}

// Filter iterates over elements of collection, returning a slice of all elements predicate returns true for.
// Play: https://go.dev/play/p/Apjg3WeSi7K
func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice {
Expand Down
65 changes: 65 additions & 0 deletions slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package lo

import (
"fmt"
"maps"
"math"
"reflect"
"sort"
"strconv"
"strings"
Expand All @@ -11,6 +13,69 @@ import (
"github.com/stretchr/testify/assert"
)

func TestSliceToSeq2(t *testing.T) {
t.Run("Even length slice", func(t *testing.T) {
data := []string{"a", "1", "b", "2"}
expected := map[string]string{"a": "1", "b": "2"}

got := maps.Collect(SliceToSeq2(data))

if !reflect.DeepEqual(got, expected) {
t.Errorf("expected %v, got %v", expected, got)
}
})

t.Run("Odd length slice", func(t *testing.T) {
data := []string{"a", "1", "b"}
// 最后一个元素 b 应该对应零值 ""
expected := map[string]string{"a": "1", "b": ""}

got := maps.Collect(SliceToSeq2(data))

if !reflect.DeepEqual(got, expected) {
t.Errorf("expected %v, got %v", expected, got)
}
})

t.Run("Empty slice", func(t *testing.T) {
data := []string{}
got := maps.Collect(SliceToSeq2(data))

if len(got) != 0 {
t.Errorf("expected empty map, got %v", got)
}
})

t.Run("Early termination (Break)", func(t *testing.T) {
data := []string{"a", "1", "b", "2", "c", "3"}
count := 0

// 模拟只取前两个键值对的情况
for k, v := range SliceToSeq2(data) {
count++
if k == "b" {
break // 触发 yield 返回 false
}
_ = v
}

if count != 2 {
t.Errorf("expected to iterate 2 times, but got %d", count)
}
})

t.Run("Integer slice", func(t *testing.T) {
data := []int{1, 10, 2, 20}
expected := map[int]int{1: 10, 2: 20}

got := maps.Collect(SliceToSeq2(data))

if !reflect.DeepEqual(got, expected) {
t.Errorf("expected %v, got %v", expected, got)
}
})
}

func TestFilter(t *testing.T) {
t.Parallel()
is := assert.New(t)
Expand Down