Skip to content

Commit 8755356

Browse files
authored
feat: Add NthOr and NthOrEmpty functions (#611)
* feat: Add NthOr and NthOrEmpty functions This commit introduces two new functions, `NthOr` and `NthOrEmpty`, to the `find` package. These functions provide a safer way to access elements at a specific index in a slice, handling out-of-bounds scenarios gracefully. - `NthOr`: Returns the element at the specified index or a provided fallback value if the index is out of bounds. - `NthOrEmpty`: Returns the element at the specified index or the zero value for the slice's element type if the index is out of bounds. * update readme
1 parent 17d82f4 commit 8755356

File tree

3 files changed

+147
-0
lines changed

3 files changed

+147
-0
lines changed

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ Supported search helpers:
253253
- [LastOrEmpty](#LastOrEmpty)
254254
- [LastOr](#LastOr)
255255
- [Nth](#nth)
256+
- [NthOr](#nthor)
257+
- [NthOrEmpty](#nthorempty)
256258
- [Sample](#sample)
257259
- [SampleBy](#sampleby)
258260
- [Samples](#samples)
@@ -2803,6 +2805,40 @@ nth, err := lo.Nth([]int{0, 1, 2, 3}, -2)
28032805
// 2
28042806
```
28052807

2808+
### NthOr
2809+
2810+
Returns the element at index `nth` of the collection. If `nth` is negative, it returns the `nth` element from the end. If `nth` is out of slice bounds, it returns the provided fallback value
2811+
```go
2812+
nth := lo.NthOr([]int{10, 20, 30, 40, 50}, 2, -1)
2813+
// 30
2814+
2815+
nth := lo.NthOr([]int{10, 20, 30, 40, 50}, -1, -1)
2816+
// 50
2817+
2818+
nth := lo.NthOr([]int{10, 20, 30, 40, 50}, 5, -1)
2819+
// -1 (fallback value)
2820+
```
2821+
2822+
### NthOrEmpty
2823+
2824+
Returns the element at index `nth` of the collection. If `nth` is negative, it returns the `nth` element from the end. If `nth` is out of slice bounds, it returns the zero value for the element type (e.g., 0 for integers, "" for strings, etc).
2825+
``` go
2826+
nth := lo.NthOrEmpty([]int{10, 20, 30, 40, 50}, 2)
2827+
// 30
2828+
2829+
nth := lo.NthOrEmpty([]int{10, 20, 30, 40, 50}, -1)
2830+
// 50
2831+
2832+
nth := lo.NthOrEmpty([]int{10, 20, 30, 40, 50}, 5)
2833+
// 0 (zero value for int)
2834+
2835+
nth := lo.NthOrEmpty([]string{"apple", "banana", "cherry"}, 2)
2836+
// "cherry"
2837+
2838+
nth := lo.NthOrEmpty([]string{"apple", "banana", "cherry"}, 5)
2839+
// "" (zero value for string)
2840+
```
2841+
28062842
### Sample
28072843

28082844
Returns a random item from collection.
@@ -2815,6 +2851,8 @@ lo.Sample([]string{})
28152851
// ""
28162852
```
28172853

2854+
2855+
28182856
### SampleBy
28192857

28202858
Returns a random item from collection, using a given random integer generator.

find.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,29 @@ func Nth[T any, N constraints.Integer](collection []T, nth N) (T, error) {
579579
return collection[l+n], nil
580580
}
581581

582+
// NthOr returns the element at index `nth` of collection.
583+
// If `nth` is negative, it returns the nth element from the end.
584+
// If `nth` is out of slice bounds, it returns the fallback value instead of an error.
585+
func NthOr[T any, N constraints.Integer](collection []T, nth N, fallback T) T {
586+
value, err := Nth(collection, nth)
587+
if err != nil {
588+
return fallback
589+
}
590+
return value
591+
}
592+
593+
// NthOrEmpty returns the element at index `nth` of collection.
594+
// If `nth` is negative, it returns the nth element from the end.
595+
// If `nth` is out of slice bounds, it returns the zero value (empty value) for that type.
596+
func NthOrEmpty[T any, N constraints.Integer](collection []T, nth N) T {
597+
value, err := Nth(collection, nth)
598+
if err != nil {
599+
var zeroValue T
600+
return zeroValue
601+
}
602+
return value
603+
}
604+
582605
// randomIntGenerator is a function that should return a random integer in the range [0, n)
583606
// where n is the parameter passed to the randomIntGenerator.
584607
type randomIntGenerator func(n int) int

find_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,92 @@ func TestNth(t *testing.T) {
630630
is.Equal(err6, nil)
631631
}
632632

633+
func TestNthOr(t *testing.T) {
634+
t.Parallel()
635+
is := assert.New(t)
636+
t.Run("Integers", func(t *testing.T) {
637+
const defaultValue = -1
638+
intSlice := []int{10, 20, 30, 40, 50}
639+
640+
is.Equal(30, NthOr(intSlice, 2, defaultValue))
641+
is.Equal(50, NthOr(intSlice, -1, defaultValue))
642+
is.Equal(defaultValue, NthOr(intSlice, 5, defaultValue))
643+
})
644+
645+
t.Run("Strings", func(t *testing.T) {
646+
const defaultValue = "none"
647+
strSlice := []string{"apple", "banana", "cherry", "date"}
648+
649+
is.Equal("banana", NthOr(strSlice, 1, defaultValue)) // Index 1, expected "banana"
650+
is.Equal("cherry", NthOr(strSlice, -2, defaultValue)) // Negative index -2, expected "cherry"
651+
is.Equal(defaultValue, NthOr(strSlice, 10, defaultValue)) // Out of bounds, fallback "none"
652+
})
653+
654+
t.Run("Structs", func(t *testing.T) {
655+
type User struct {
656+
ID int
657+
Name string
658+
}
659+
userSlice := []User{
660+
{ID: 1, Name: "Alice"},
661+
{ID: 2, Name: "Bob"},
662+
{ID: 3, Name: "Charlie"},
663+
}
664+
665+
expectedUser := User{ID: 1, Name: "Alice"}
666+
is.Equal(expectedUser, NthOr(userSlice, 0, User{ID: 0, Name: "Unknown"}))
667+
668+
expectedUser = User{ID: 3, Name: "Charlie"}
669+
is.Equal(expectedUser, NthOr(userSlice, -1, User{ID: 0, Name: "Unknown"}))
670+
671+
expectedUser = User{ID: 0, Name: "Unknown"}
672+
is.Equal(expectedUser, NthOr(userSlice, 10, User{ID: 0, Name: "Unknown"}))
673+
})
674+
}
675+
676+
func TestNthOrEmpty(t *testing.T) {
677+
t.Parallel()
678+
is := assert.New(t)
679+
t.Run("Integers", func(t *testing.T) {
680+
const defaultValue = 0
681+
intSlice := []int{10, 20, 30, 40, 50}
682+
683+
is.Equal(30, NthOrEmpty(intSlice, 2))
684+
is.Equal(50, NthOrEmpty(intSlice, -1))
685+
is.Equal(defaultValue, NthOrEmpty(intSlice, 10))
686+
})
687+
688+
t.Run("Strings", func(t *testing.T) {
689+
const defaultValue = ""
690+
strSlice := []string{"apple", "banana", "cherry", "date"}
691+
692+
is.Equal("banana", NthOrEmpty(strSlice, 1))
693+
is.Equal("cherry", NthOrEmpty(strSlice, -2))
694+
is.Equal(defaultValue, NthOrEmpty(strSlice, 10))
695+
})
696+
697+
t.Run("Structs", func(t *testing.T) {
698+
type User struct {
699+
ID int
700+
Name string
701+
}
702+
userSlice := []User{
703+
{ID: 1, Name: "Alice"},
704+
{ID: 2, Name: "Bob"},
705+
{ID: 3, Name: "Charlie"},
706+
}
707+
708+
expectedUser := User{ID: 1, Name: "Alice"}
709+
is.Equal(expectedUser, NthOrEmpty(userSlice, 0))
710+
711+
expectedUser = User{ID: 3, Name: "Charlie"}
712+
is.Equal(expectedUser, NthOrEmpty(userSlice, -1))
713+
714+
expectedUser = User{ID: 0, Name: ""}
715+
is.Equal(expectedUser, NthOrEmpty(userSlice, 10))
716+
})
717+
}
718+
633719
func TestSample(t *testing.T) {
634720
t.Parallel()
635721
is := assert.New(t)

0 commit comments

Comments
 (0)