(feat): filter function, list#Mappable() (#1)

This commit is contained in:
bfu4 2021-09-06 14:36:55 -04:00 committed by GitHub
commit 93af011977
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 158 additions and 32 deletions

@ -37,6 +37,32 @@ func Showcase() {
</details>
<details close>
<summary>Filter</summary>
```go
import (
"github.com/prequist/gomap"
"strings"
)
func Showcase() {
e := gomap.New("a", "b", "c", "ab", "d")
predicate := func(i interface{}) bool {
if str, ok := i.(string) {
return strings.Contains(str, "a")
}
return false
}
// Get the mappable list
mappable := e.Mappable()
// Apply the predicate, return the filtered list as a variable
mappable = e.Filter(predicate) // "a", "ab"
}
```
</details>
<details closed>
<summary>Boxed</summary>
@ -48,7 +74,7 @@ import (
func MakeBoxedAndConvert() []int {
e := gomap.NewBoxed(1, 2, 3, 4, 5)
mappable := gomap.MappableList{e}
mappable := e.Mappable()
mappable.Map(func(v interface{}) interface{} {
ib := v.(ptypes.Box).IntBox()
return ptypes.FromInt(*ib.Int() + 2)
@ -74,7 +100,7 @@ import (
func MakeAndConvert() []int {
e := gomap.New(1, 2, 3, 4, 5)
mappable := gomap.MappableList{e}
mappable := e.Mappable()
mappable.Map(func(v interface{}) interface{} {
return v.(int) + 1
})
@ -113,4 +139,13 @@ BenchmarkAdd-8 17820822 94.33 ns/op
```
BenchmarkIteratorNext-8 1000000000 0.5661 ns/op
```
### filter_test.go
```
BenchmarkFilter
BenchmarkFilter/Filter
BenchmarkFilter/Filter-8 1000000000 0.0000009 ns/op
```

29
filter.go Normal file

@ -0,0 +1,29 @@
package gomap
// The Predicate is a type alias for a function that
// takes in a parameter and returns a boolean.
// The predicate is similar to Java's `Predicate<T>`,
// without the generic usage. This makes the usage of a predicate not
// necessarily type safe. This also means that the writer of the predicate
// has to write code that they trust when passing the predicate into the filter.
type Predicate func(interface{}) bool
func (l *MappableList) Filter(predicate Predicate) MappableList {
// Get the iterator.
itr := l.Iterator()
// Utilise the iterator recursively, to map
// the entire list.
var newArray List
// We utilize the goto call to avoid recursively calling the filter function.
sorter:
if next, value := itr.Next(); next {
// Filter by the predicate.
if predicate(value) {
newArray.Add(value)
}
// Jump back to the sorter label.
goto sorter
}
// Return the list.
return MappableList{&newArray}
}

54
filter_test.go Normal file

@ -0,0 +1,54 @@
package gomap_test
import (
"github.com/prequist/gomap"
"reflect"
"strings"
"testing"
)
// Test the filtering function MappableList.Filter(Predicate).
func TestFilter(t *testing.T) {
stringMap := gomap.New("test", "test1", "test2", "not_applicable", "test3")
mappable := stringMap.Mappable()
filtered := mappable.Filter(func(i interface{}) bool {
if str, ok := i.(string); ok {
return strings.Contains(str, "test")
}
return false
})
filteredItems := mapToString(filtered.Items())
knownKeys := []string{"test", "test1", "test2", "test3"}
if !reflect.DeepEqual(filteredItems, knownKeys) {
t.Fail()
}
}
// Benchmark the filtering function.
func BenchmarkFilter(b *testing.B) {
stringMap := gomap.New("test", "test1", "test2", "not_applicable", "test3")
mappable := stringMap.Mappable()
// Benchmark the actual filtering.
b.Run("Filter", func(b *testing.B) {
mappable = mappable.Filter(func(i interface{}) bool {
if str, ok := i.(string); ok {
return strings.Contains(str, "test")
}
return false
})
})
filteredItems := mapToString(mappable.Items())
knownKeys := []string{"test", "test1", "test2", "test3"}
if !reflect.DeepEqual(filteredItems, knownKeys) {
b.Fail()
}
}
// Map an interface array to a string.
func mapToString(i []interface{}) []string {
var strArr []string
for _, v := range i {
strArr = append(strArr, v.(string))
}
return strArr
}

@ -38,8 +38,6 @@ func (list *List) Add(v interface{}) *List {
list.mu.Lock()
// Append the elements to the list and the iterator.
list.elements = append(list.elements, v)
it := list.Iterator()
*it.elements = append(*it.elements, v)
// Unlock the mutex.
list.mu.Unlock()
return list

14
map.go

@ -11,6 +11,11 @@ type MappableList struct {
*List
}
// Mappable returns a mappable list from an existing List.
func (list *List) Mappable() MappableList {
return MappableList{list}
}
// The Map function is called on a MappableList to apply
// the requested Transformer.
func (l *MappableList) Map(transformer Transformer) *List {
@ -24,12 +29,15 @@ func Map(list *List, transformer Transformer) *List {
itr := list.Iterator()
// Utilise the iterator recursively, to map
// the entire list.
var mapped List
sorter:
if next, value := itr.Next(); next {
// Transform the item.
list.Items()[itr.current] = transformer(value)
transformed := transformer(value)
mapped.Add(transformed)
// Remap everything.
return Map(list, transformer)
goto sorter
}
// Return the list.
return list
return &mapped
}

@ -1,6 +1,7 @@
package gomap
package gomap_test
import (
"github.com/prequist/gomap"
"github.com/prequist/ptypes"
"testing"
)
@ -126,7 +127,7 @@ func BenchmarkAdd(b *testing.B) {
v1 := "hello"
v2 := "world"
v3 := "test"
arr := NewBoxed(v1, v2, v3)
arr := gomap.NewBoxed(v1, v2, v3)
for i := 0; i < b.N; i++ {
arr.Add("hello")
}
@ -134,9 +135,9 @@ func BenchmarkAdd(b *testing.B) {
// Create a plain int list.
func Plain(v ...interface{}) []int {
e := New(v...)
mappable := MappableList{e}
mappable.Map(func(v interface{}) interface{} {
e := gomap.New(v...)
mappable := e.Mappable()
e = mappable.Map(func(v interface{}) interface{} {
return v.(int) + 1
})
arr := make([]int, len(e.Items()))
@ -148,9 +149,9 @@ func Plain(v ...interface{}) []int {
// Create a plain string list.
func String(v ...interface{}) []string {
e := New(v...)
mappable := MappableList{e}
mappable.Map(func(v interface{}) interface{} {
e := gomap.New(v...)
mappable := e.Mappable()
e = mappable.Map(func(v interface{}) interface{} {
return v.(string) + "---"
})
arr := make([]string, len(e.Items()))
@ -162,9 +163,9 @@ func String(v ...interface{}) []string {
// Create a boxed int list.
func Boxed(v ...interface{}) []int {
e := NewBoxed(v...)
mappable := MappableList{e}
mappable.Map(func(v interface{}) interface{} {
e := gomap.NewBoxed(v...)
mappable := e.Mappable()
e = mappable.Map(func(v interface{}) interface{} {
ib := v.(ptypes.Box).IntBox()
return ptypes.FromInt(*ib.Int() + 2)
})
@ -177,9 +178,9 @@ func Boxed(v ...interface{}) []int {
// Create a boxed string list.
func BoxedString(v ...interface{}) []string {
e := NewBoxed(v...)
mappable := MappableList{e}
mappable.Map(func(v interface{}) interface{} {
e := gomap.NewBoxed(v...)
mappable := e.Mappable()
e = mappable.Map(func(v interface{}) interface{} {
box := v.(ptypes.Box)
ib, _ := box.String()
return ptypes.FromString(ib + "---")

@ -1,13 +1,14 @@
package gomap
package gomap_test
import (
"github.com/prequist/gomap"
"testing"
)
// TestStringHandle tests the slice string handle.
func TestStringHandle(t *testing.T) {
values := []string{"a", "b", "c"}
list := NewFromPrimitiveSlice(values, values[0])
list := gomap.NewFromPrimitiveSlice(values, values[0])
for index, v := range list.Items() {
if values[index] != v.(string) {
t.Errorf("got %s, expected %s", values[index], v.(string))
@ -18,7 +19,7 @@ func TestStringHandle(t *testing.T) {
// TestIntHandle tests the int handle.
func TestIntHandle(t *testing.T) {
values := []int{1, 2, 3}
list := NewFromPrimitiveSlice(values, values[0])
list := gomap.NewFromPrimitiveSlice(values, values[0])
if list == nil {
t.Error("list was nil")
return
@ -33,7 +34,7 @@ func TestIntHandle(t *testing.T) {
// TestInt32Handle tests the int32 handle.
func TestInt32Handle(t *testing.T) {
values := []int32{1, 2, 3}
list := NewFromPrimitiveSlice(values, values[0])
list := gomap.NewFromPrimitiveSlice(values, values[0])
if list == nil {
t.Error("list was nil")
return
@ -50,7 +51,7 @@ func TestUintHandle(t *testing.T) {
// Create a slice.
values := []uint{1, 2, 3}
// Create a list.
list := NewFromPrimitiveSlice(values, values[0])
list := gomap.NewFromPrimitiveSlice(values, values[0])
// If the list is nil, error.
if list == nil {
t.Error("list was nil")
@ -69,14 +70,14 @@ func TestUintHandleMap(t *testing.T) {
return i.(uint) * 2
}
values := []uint{1, 3, 5}
list := NewFromPrimitiveSlice(values, values[0])
list := gomap.NewFromPrimitiveSlice(values, values[0])
for index, v := range list.Items() {
if v != values[index] {
t.Errorf("got %d, expected %d", v, values[index])
}
}
mappable := MappableList{list}
mappable.Map(transformer)
mappable := list.Mappable()
list = mappable.Map(transformer)
for index, v := range list.Items() {
if v != values[index] * 2 {
t.Errorf("got %d, expected %d", v, values[index] * 2)
@ -87,7 +88,7 @@ func TestUintHandleMap(t *testing.T) {
// TestFloatHandle tests the float handle.
func TestFloatHandle(t *testing.T) {
values := []float32{1.2222, 2.3333, 3.4444}
list := NewFromPrimitiveSlice(values, values[0])
list := gomap.NewFromPrimitiveSlice(values, values[0])
if list == nil {
t.Error("list was nil")
return
@ -102,7 +103,7 @@ func TestFloatHandle(t *testing.T) {
// TestComplexHandle tests the complex handle.
func TestComplexHandle(t *testing.T) {
values := []complex64{1, 2, 3}
list := NewFromPrimitiveSlice(values, values[0])
list := gomap.NewFromPrimitiveSlice(values, values[0])
if list == nil {
t.Error("list was nil")
return
@ -117,7 +118,7 @@ func TestComplexHandle(t *testing.T) {
// TestComplexHandle tests the complex handle.
func TestUintptrHandle(t *testing.T) {
values := []uintptr{1, 2, 3}
list := NewFromPrimitiveSlice(values, values[0])
list := gomap.NewFromPrimitiveSlice(values, values[0])
if list == nil {
t.Error("list was nil")
return