Skip to content

Commit

Permalink
Merge pull request #31 from halprin/external-api-changes
Browse files Browse the repository at this point in the history
External API Changes
  • Loading branch information
halprin committed Nov 23, 2021
2 parents c05464c + 8f96a6e commit e35489b
Show file tree
Hide file tree
Showing 25 changed files with 241 additions and 182 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ chain := rangechain.FromSlice(container)
| `FromSlice` |`slice` - A slice to be used to start the chain. | Starts the chain with the supplied slice. Chaining and terminating methods can now be called on the result. |
| `FromArray` |`array` - An array to be used to start the chain. | Starts the chain with the supplied array. Chaining and terminating methods can now be called on the result. |
| `FromChannel` |`channel` - A channel to be used to start the chain. | Starts the chain with the supplied channel. Chaining and terminating methods can now be called on the result. |
| `FromMap` |`aMap` - A map to be used to start the chain. | Starts the chain with the supplied map. Chaining and terminating methods can now be called on the result. The singular value used to represent the key and value pairs is `generator.MapTuple` of `github.com/halprin/rangechain/generator`. |
| `FromMap` |`aMap` - A map to be used to start the chain. | Starts the chain with the supplied map. Chaining and terminating methods can now be called on the result. The singular value used to represent the key and value pairs is `keyvalue.KeyValuer` of `github.com/halprin/rangechain/keyvalue`. |

From there, one can call a plethora of additional methods to modify the container passed in originally. The methods are
outlined below. The methods fall into one of two categories: chaining or terminating.
Expand Down Expand Up @@ -76,7 +76,7 @@ terminating method is called.
| `Skip` | Skips over the parameter `skipNumber` number of values and effectively removes them from the chain. Also skips over any errors previously generated. |
| `Limit` | Stops the chain after the parameter `keepSize` number of values. Any elements afterward are effectively removed. |
| `Distinct` | Removes any duplicates. |
| `Flatten` | Will iterate over all the values in the chain, but any value encountered that is a range-able container itself will also have its values iterated over first before continuing with the remaining values in the chain. Maps flatten to its `generator.MapTuple` key and value pairs. |
| `Flatten` | Will iterate over all the values in the chain, but any value encountered that is a range-able container itself will also have its values iterated over first before continuing with the remaining values in the chain. Maps flatten to its `keyvalue.KeyValuer` key and value pairs. |
| `Sort` | Sorts the chain given the `Less` function returned from the `returnLessFunction` function parameter. The `returnLessFunction` function is called with the entire serialized chain as a slice and _returns_ a function that satisfies the same requirements as the [Interface type's](https://pkg.go.dev/sort#Interface) `Less` function. See the [`TestSortingMaps` example](./example_test.go). This method is expensive because it must serialize all the values into a slice first. |
| `Reverse` | Reverses the order of the chain. The last item will be first, and the first item will be last. This method is expensive because it must serialize all the values into a slice first. |

Expand Down
21 changes: 10 additions & 11 deletions begin.go
Original file line number Diff line number Diff line change
@@ -1,42 +1,41 @@
package rangechain

import (
"github.com/halprin/rangechain/generator"
"github.com/halprin/rangechain/intermediate"
"github.com/halprin/rangechain/internal/generator"
)

// FromSlice starts the chain with the supplied slice.
// Chaining and terminating methods can now be called on the result.
func FromSlice(slice interface{}) *intermediate.Link {
func FromSlice(slice interface{}) *Link {
sliceGenerator := generator.FromSlice(slice)

link := intermediate.NewLink(sliceGenerator)
link := newLink(sliceGenerator)
return link
}

// FromArray starts the chain with the supplied array.
// Chaining and terminating methods can now be called on the result.
func FromArray(array interface{}) *intermediate.Link {
func FromArray(array interface{}) *Link {
arrayGenerator := generator.FromArray(array)

link := intermediate.NewLink(arrayGenerator)
link := newLink(arrayGenerator)
return link
}

// FromChannel starts the chain with the supplied channel.
// Chaining and terminating methods can now be called on the result.
func FromChannel(channel interface{}) *intermediate.Link {
func FromChannel(channel interface{}) *Link {
channelGenerator := generator.FromChannel(channel)

link := intermediate.NewLink(channelGenerator)
link := newLink(channelGenerator)
return link
}

// FromMap starts the chain with the supplied map.
// Chaining and terminating methods can now be called on the result. The singular value used to represent the key and value pairs is `generator.MapTuple` of `github.com/halprin/rangechain/generator`.
func FromMap(aMap interface{}) *intermediate.Link {
// Chaining and terminating methods can now be called on the result. The singular value used to represent the key and value pairs is `keyvalue.KeyValuer` of `github.com/halprin/rangechain/keyvalue`.
func FromMap(aMap interface{}) *Link {
mapGenerator := generator.FromMap(aMap)

link := intermediate.NewLink(mapGenerator)
link := newLink(mapGenerator)
return link
}
74 changes: 61 additions & 13 deletions begin_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package rangechain

import (
"github.com/halprin/rangechain/generator"
"github.com/halprin/rangechain/keyvalue"
"github.com/stretchr/testify/assert"
"testing"
)
Expand Down Expand Up @@ -35,7 +35,7 @@ func TestFromChannel(t *testing.T) {

innerInput := []string{"DogCows", "goes", "Moof!", "Do", "you", "like", "Clarus", "the", "DogCow?"}
expectedOutput := []interface{}{"DogCows", "goes", "Moof!", "Do", "you", "like", "Clarus", "the", "DogCow?"}
input := createTestChannel(innerInput)
input := createTestStringChannel(innerInput)
chain := FromChannel(input)

slice, err := chain.Slice()
Expand All @@ -61,27 +61,40 @@ func TestFromMap(t *testing.T) {
chain := FromMap(input)

expectedOutput := []interface{}{
generator.MapTuple{
Key: key1,
Value: value1,
&testKeyValue{
TheKey: key1,
TheValue: value1,
},
generator.MapTuple{
Key: key2,
Value: value2,
&testKeyValue{
TheKey: key2,
TheValue: value2,
},
generator.MapTuple{
Key: key3,
Value: value3,
&testKeyValue{
TheKey: key3,
TheValue: value3,
},
}

slice, err := chain.Slice()
//not testing the order because we are not guaranteed the order in which a map is iterated over
assert.ElementsMatch(expectedOutput, slice)
assertEqualsBasedOnKeyValuerInterface(t, expectedOutput, slice)
assert.Nil(err)
}

func createTestChannel(stringSlice []string) chan string {
type testKeyValue struct {
TheKey interface{}
TheValue interface{}
}

func (t *testKeyValue) Key() interface{} {
return t.TheKey
}

func (t *testKeyValue) Value() interface{} {
return t.TheValue
}

func createTestStringChannel(stringSlice []string) chan string {
stringChannel := make(chan string)

go func() {
Expand All @@ -93,3 +106,38 @@ func createTestChannel(stringSlice []string) chan string {

return stringChannel
}

func assertEqualsBasedOnKeyValuerInterface(t *testing.T, expected []interface{}, actual []interface{}) {
assert := assert.New(t)

assert.Len(actual, len(expected))

for _, expectedValue := range expected {
expectedKeyValuer, isType := expectedValue.(keyvalue.KeyValuer)
if !isType {
continue
}
keyToFind := expectedKeyValuer.Key()
foundMatch := false

for _, actualValue := range actual {
actualKeyValuer, isType := actualValue.(keyvalue.KeyValuer)
if !isType {
continue
}
actualKey := actualKeyValuer.Key()

if actualKey != keyToFind {
continue
}

if expectedKeyValuer.Value() != actualKeyValuer.Value() {
continue
}

foundMatch = true
}

assert.True(foundMatch)
}
}
14 changes: 14 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,18 @@
//
// Notice `stringValue := value.(string)` above. This allows one to do the string concatenation on the next line because
// the `+` operator doesn't work on an `interface{}` type.
//
// Continuing the Chain
//
// Chaining methods apply some modification to the values in the container values, but keeps the chain alive.
// This allows additional chaining methods to be subsequently called on the result. The subsequent chain methods operate
// on any changes performed by the previous chain method.
// Because modifications are lazily computed, none of the modifications from chaining methods are applied until _after_ a
// terminating method is called.
//
// Terminating the Chain
//
// Terminating methods also apply some modification, requests some information, or executes something on the values.
// They stop the chaining by returning an actual value. This value will depend on all the previous chaining methods being
// executed first.
package rangechain
14 changes: 7 additions & 7 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package rangechain

import (
"fmt"
"github.com/halprin/rangechain/generator"
"github.com/halprin/rangechain/keyvalue"
"testing"
)

Expand Down Expand Up @@ -60,17 +60,17 @@ func TestSortingMaps(t *testing.T) {
chain := FromMap(aMap)
sortedAppleStuff, _ := chain.Sort(func(mapValuesToSort []interface{}) func(int, int) bool {
return func(index1 int, index2 int) bool {
mapValue1 := mapValuesToSort[index1].(generator.MapTuple)
mapValue2 := mapValuesToSort[index2].(generator.MapTuple)
mapValue1 := mapValuesToSort[index1].(keyvalue.KeyValuer)
mapValue2 := mapValuesToSort[index2].(keyvalue.KeyValuer)

rating1 := mapValue1.Value.(int)
rating2 := mapValue2.Value.(int)
rating1 := mapValue1.Value().(int)
rating2 := mapValue2.Value().(int)

return rating1 > rating2
}
}).Map(func(value interface{}) (interface{}, error) {
mapValue := value.(generator.MapTuple)
return mapValue.Key, nil
mapValue := value.(keyvalue.KeyValuer)
return mapValue.Key(), nil
}).Slice()

fmt.Println(sortedAppleStuff)
Expand Down
2 changes: 0 additions & 2 deletions helper/doc.go

This file was deleted.

16 changes: 0 additions & 16 deletions intermediate/doc.go

This file was deleted.

13 changes: 0 additions & 13 deletions intermediate/link.go

This file was deleted.

24 changes: 9 additions & 15 deletions generator/generator.go → internal/generator/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,14 @@ package generator

import (
"errors"
"github.com/halprin/rangechain/helper"
"github.com/halprin/rangechain/internal/helper"
"reflect"
)

// Exhausted is returned as an expected error from the generators to designate an end of the generator.
var Exhausted = errors.New("generator exhausted")

// MapTuple is used to represent the key and value pairs of a map when iterating over a map.
type MapTuple struct {
Key interface{}
Value interface{}
}

// FromSlice is not meant to be called directly by external users. Creates a generator for a slice.
// FromSlice creates a generator for a slice.
func FromSlice(slice interface{}) func() (interface{}, error) {
if !helper.IsSlice(slice) {
panic("non-slice type provided")
Expand All @@ -25,7 +19,7 @@ func FromSlice(slice interface{}) func() (interface{}, error) {
return generatorFromSliceOrArray(slice)
}

// FromArray is not meant to be called directly by external users. Creates a generator for an array.
// FromArray creates a generator for an array.
func FromArray(array interface{}) func() (interface{}, error) {
if !helper.IsArray(array) {
panic("non-array type provided")
Expand All @@ -34,7 +28,7 @@ func FromArray(array interface{}) func() (interface{}, error) {
return generatorFromSliceOrArray(array)
}

// FromChannel is not meant to be called directly by external users. Creates a generator for a channel.
// FromChannel creates a generator for a channel.
func FromChannel(channel interface{}) func() (interface{}, error) {
if !helper.IsChannel(channel) {
panic("non-channel type provided")
Expand All @@ -54,7 +48,7 @@ func FromChannel(channel interface{}) func() (interface{}, error) {
}
}

// FromMap is not meant to be called directly by external users. Creates a generator for a map.
// FromMap creates a generator for a map.
func FromMap(aMap interface{}) func() (interface{}, error) {
if !helper.IsMap(aMap) {
panic("non-map type provided")
Expand All @@ -69,12 +63,12 @@ func FromMap(aMap interface{}) func() (interface{}, error) {
return 0, Exhausted
}

mapTuple := MapTuple{
Key: mapIterator.Key().Interface(),
Value: mapIterator.Value().Interface(),
tuple := &mapTuple{
TheKey: mapIterator.Key().Interface(),
TheValue: mapIterator.Value().Interface(),
}

return mapTuple, nil
return tuple, nil
}
}

Expand Down
File renamed without changes.
15 changes: 15 additions & 0 deletions internal/generator/keyvalue.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package generator

// mapTuple implements the `keyvalue.KeyValuer` interface and is used to represent map's keys and values
type mapTuple struct {
TheKey interface{}
TheValue interface{}
}

func (m *mapTuple) Key() interface{} {
return m.TheKey
}

func (m *mapTuple) Value() interface{} {
return m.TheValue
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
8 changes: 8 additions & 0 deletions keyvalue/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Package keyvalue exists to specify the interface of `KeyValuer`.
package keyvalue

// KeyValuer is used to represent key and value pairs.
type KeyValuer interface {
Key() interface{}
Value() interface{}
}
12 changes: 12 additions & 0 deletions link.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package rangechain

// Link is not meant to be initialized directly by external users. Use the `From*` functions.
type Link struct {
generator func() (interface{}, error)
}

func newLink(generator func() (interface{}, error)) *Link {
return &Link{
generator: generator,
}
}
Loading

0 comments on commit e35489b

Please sign in to comment.