Skip to content

Commit 5240c27

Browse files
rjNemoclaude
andauthored
feat: add First and FirstN functions (#44)
- Add First: returns first element or error if empty - Add FirstN: returns first n elements safely - ErrEmptySlice error for consistent error handling - Comprehensive tests including edge cases - Benchmarks included Resolves Issue 16 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <[email protected]>
1 parent 0bf04c2 commit 5240c27

File tree

2 files changed

+130
-0
lines changed

2 files changed

+130
-0
lines changed

first.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package underscore
2+
3+
import "errors"
4+
5+
// ErrEmptySlice is returned when trying to get the first element of an empty slice
6+
var ErrEmptySlice = errors.New("underscore: empty slice")
7+
8+
// First returns the first element of the slice.
9+
// Returns an error if the slice is empty.
10+
func First[T any](values []T) (T, error) {
11+
var zero T
12+
if len(values) == 0 {
13+
return zero, ErrEmptySlice
14+
}
15+
return values[0], nil
16+
}
17+
18+
// FirstN returns the first n elements of the slice.
19+
// If n is greater than the slice length, returns the entire slice.
20+
// If n is less than or equal to 0, returns an empty slice.
21+
func FirstN[T any](values []T, n int) []T {
22+
if n <= 0 {
23+
return []T{}
24+
}
25+
if n >= len(values) {
26+
res := make([]T, len(values))
27+
copy(res, values)
28+
return res
29+
}
30+
res := make([]T, n)
31+
copy(res, values[:n])
32+
return res
33+
}

first_test.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package underscore_test
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
u "github.com/rjNemo/underscore"
10+
)
11+
12+
func TestFirst(t *testing.T) {
13+
nums := []int{1, 2, 3, 4, 5}
14+
result, err := u.First(nums)
15+
assert.NoError(t, err)
16+
assert.Equal(t, 1, result)
17+
}
18+
19+
func TestFirstEmpty(t *testing.T) {
20+
_, err := u.First([]int{})
21+
assert.Error(t, err)
22+
assert.True(t, errors.Is(err, u.ErrEmptySlice))
23+
}
24+
25+
func TestFirstSingleElement(t *testing.T) {
26+
result, err := u.First([]int{42})
27+
assert.NoError(t, err)
28+
assert.Equal(t, 42, result)
29+
}
30+
31+
func TestFirstStrings(t *testing.T) {
32+
words := []string{"hello", "world"}
33+
result, err := u.First(words)
34+
assert.NoError(t, err)
35+
assert.Equal(t, "hello", result)
36+
}
37+
38+
func TestFirstN(t *testing.T) {
39+
nums := []int{1, 2, 3, 4, 5}
40+
result := u.FirstN(nums, 3)
41+
assert.Equal(t, []int{1, 2, 3}, result)
42+
}
43+
44+
func TestFirstNEmpty(t *testing.T) {
45+
result := u.FirstN([]int{}, 3)
46+
assert.Equal(t, []int{}, result)
47+
}
48+
49+
func TestFirstNZero(t *testing.T) {
50+
nums := []int{1, 2, 3}
51+
result := u.FirstN(nums, 0)
52+
assert.Equal(t, []int{}, result)
53+
}
54+
55+
func TestFirstNNegative(t *testing.T) {
56+
nums := []int{1, 2, 3}
57+
result := u.FirstN(nums, -5)
58+
assert.Equal(t, []int{}, result)
59+
}
60+
61+
func TestFirstNGreaterThanLength(t *testing.T) {
62+
nums := []int{1, 2, 3}
63+
result := u.FirstN(nums, 10)
64+
assert.Equal(t, []int{1, 2, 3}, result)
65+
}
66+
67+
func TestFirstNSingleElement(t *testing.T) {
68+
result := u.FirstN([]int{42}, 1)
69+
assert.Equal(t, []int{42}, result)
70+
}
71+
72+
func TestFirstNAll(t *testing.T) {
73+
nums := []int{1, 2, 3}
74+
result := u.FirstN(nums, 3)
75+
assert.Equal(t, []int{1, 2, 3}, result)
76+
}
77+
78+
func BenchmarkFirst(b *testing.B) {
79+
nums := []int{1, 2, 3, 4, 5}
80+
81+
b.ResetTimer()
82+
for i := 0; i < b.N; i++ {
83+
u.First(nums)
84+
}
85+
}
86+
87+
func BenchmarkFirstN(b *testing.B) {
88+
nums := make([]int, 1000)
89+
for i := range nums {
90+
nums[i] = i
91+
}
92+
93+
b.ResetTimer()
94+
for i := 0; i < b.N; i++ {
95+
u.FirstN(nums, 100)
96+
}
97+
}

0 commit comments

Comments
 (0)