Skip to content

Commit

Permalink
Merge pull request #12 from mkitawaki/myFeatureSpike
Browse files Browse the repository at this point in the history
Add SelectMany and SelectManyBy
  • Loading branch information
ahmetb committed Apr 1, 2015
2 parents 8abc193 + e0d717a commit bd15765
Show file tree
Hide file tree
Showing 4 changed files with 389 additions and 0 deletions.
58 changes: 58 additions & 0 deletions linq.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,64 @@ func (q Query) Select(f func(T) (T, error)) (r Query) {
return
}

// SelectMany returns flattens the resulting sequences into one sequence.
//
// Example:
// names, err := From(parents).SelectMany(func (p T, idx int) (T, error) {
// return p.(*Parent).Children, nil
// }).Results()
func (q Query) SelectMany(f func(T, int) (T, error)) (r Query) {
return q.SelectManyBy(f, func(p T, c T) (T, error) {
return c, nil
})
}

// SelectMany returns flattens the resulting sequences into one sequence.
//
// resultSelector takes parent element and child element as inputs
// and returns a value which will be an element in the resulting query.
//
// Example:
// names, err := From(parents).SelectManyBy(func (p T, idx int) (T, error) {
// return p.(*Parent).Children, nil
// }, func (p T, c T) (T, error) {
// return p.(*Parent).Name + ":" + c.(*Child).Name, nil
// }).Results()
func (q Query) SelectManyBy(f func(T, int) (T, error),
resultSelector func(T, T) (T, error)) (r Query) {

if q.err != nil {
r.err = q.err
return r
}
if f == nil || resultSelector == nil {
r.err = ErrNilFunc
return
}

for i, p := range q.values {
val, err := f(p, i)
if err != nil {
r.err = err
return r
}
innerCollection, ok := takeSliceArg(val)
if !ok {
r.err = ErrInvalidInput
return r
}
for _, c := range innerCollection {
res, err := resultSelector(p, c)
if err != nil {
r.err = err
return r
}
r.values = append(r.values, res)
}
}
return
}

// Distinct returns distinct elements from the provided source using default
// equality comparer, ==. This is a set operation and returns an unordered
// sequence.
Expand Down
132 changes: 132 additions & 0 deletions linq_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,138 @@ func TestSelect(t *testing.T) {
})
}

type bar struct {
str string
foos []foo
}
type fooBar struct {
fooStr string
barStr string
}

var (
fooArr = []foo{foo{"A", 0}, foo{"B", 1}, foo{"C", -1}}
barArr = []bar{bar{"a", []foo{foo{"A", 0}, foo{"B", 1}}}, bar{"b", []foo{foo{"C", -1}}}}
fooEmpty = []bar{bar{"c", nil}}
fooBarArr = []fooBar{fooBar{"A", "a"}, fooBar{"B", "a"}, fooBar{"C", "b"}}
)

func TestSelectMany(t *testing.T) {
children := func(i T, x int) (T, error) {
return i.(bar).foos, nil
}
erroneusFunc := func(i T, x int) (T, error) {
return nil, errFoo
}

c.Convey("Previous error is reflected on result", t, func() {
_, err := From(barArr).Where(erroneusBinaryFunc).SelectMany(children).Results()
c.So(err, c.ShouldNotEqual, nil)
})

c.Convey("Nil func returns error", t, func() {
_, err := From(barArr).SelectMany(nil).Results()
c.So(err, c.ShouldEqual, ErrNilFunc)
})

c.Convey("Error returned from provided func", t, func() {
val, err := From(barArr).SelectMany(erroneusFunc).Results()
c.So(err, c.ShouldNotEqual, nil)

c.Convey("Erroneus function is in chain with as-is select", func() {
_, err = From(barArr).SelectMany(children).SelectMany(erroneusFunc).Results()
c.So(err, c.ShouldNotEqual, nil)
})
c.Convey("Erroneus function is in chain but not called", func() {
val, err = From(barArr).Where(alwaysFalse).SelectMany(erroneusFunc).Results()
c.So(err, c.ShouldEqual, nil)
c.So(len(val), c.ShouldEqual, 0)
})

})

c.Convey("Select empty as is", t, func() {
val, err := From(fooEmpty).SelectMany(children).Results()
c.So(err, c.ShouldEqual, nil)
c.So(val, shouldSlicesResemble, empty)
})

c.Convey("Select all elements as is", t, func() {
val, err := From(barArr).SelectMany(children).Results()
c.So(err, c.ShouldEqual, nil)
c.So(val, shouldSlicesResemble, fooArr)
})
}

func TestSelectManyBy(t *testing.T) {
children := func(b T, x int) (T, error) {
return b.(bar).foos, nil
}
barStr := func(b T, f T) (T, error) {
return fooBar{f.(foo).str, b.(bar).str}, nil
}
erroneusFunc := func(b T, x int) (T, error) {
return nil, errFoo
}
erroneusSelectFunc := func(b T, f T) (T, error) {
return nil, errFoo
}

c.Convey("Previous error is reflected on result", t, func() {
_, err := From(barArr).Where(erroneusBinaryFunc).SelectManyBy(children, barStr).Results()
c.So(err, c.ShouldNotEqual, nil)
})

c.Convey("Nil transform func returns error", t, func() {
_, err := From(barArr).SelectManyBy(nil, barStr).Results()
c.So(err, c.ShouldEqual, ErrNilFunc)
})

c.Convey("Nil resultSelect func returns error", t, func() {
_, err := From(barArr).SelectManyBy(children, nil).Results()
c.So(err, c.ShouldEqual, ErrNilFunc)
})

c.Convey("Both nil func returns error", t, func() {
_, err := From(barArr).SelectManyBy(nil, nil).Results()
c.So(err, c.ShouldEqual, ErrNilFunc)
})

c.Convey("Error returned from provided func", t, func() {
val, err := From(barArr).SelectManyBy(erroneusFunc, barStr).Results()
c.So(err, c.ShouldNotEqual, nil)

val, err = From(barArr).SelectManyBy(children, erroneusSelectFunc).Results()
c.So(err, c.ShouldNotEqual, nil)

val, err = From(barArr).SelectManyBy(erroneusFunc, erroneusSelectFunc).Results()
c.So(err, c.ShouldNotEqual, nil)

c.Convey("Erroneus function is in chain with as-is select", func() {
_, err = From(barArr).SelectManyBy(children, barStr).SelectManyBy(erroneusFunc, erroneusSelectFunc).Results()
c.So(err, c.ShouldNotEqual, nil)
})
c.Convey("Erroneus function is in chain but not called", func() {
val, err = From(barArr).Where(alwaysFalse).SelectManyBy(erroneusFunc, erroneusSelectFunc).Results()
c.So(err, c.ShouldEqual, nil)
c.So(len(val), c.ShouldEqual, 0)
})

})

c.Convey("Select empty as is", t, func() {
val, err := From(fooEmpty).SelectManyBy(children, barStr).Results()
c.So(err, c.ShouldEqual, nil)
c.So(val, shouldSlicesResemble, empty)
})

c.Convey("Select all elements as is", t, func() {
val, err := From(barArr).SelectManyBy(children, barStr).Results()
c.So(err, c.ShouldEqual, nil)
c.So(val, shouldSlicesResemble, fooBarArr)
})
}

func TestDistinct(t *testing.T) {
c.Convey("Empty slice", t, func() {
res, err := From(empty).Distinct().Results()
Expand Down
83 changes: 83 additions & 0 deletions plinq.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ type parallelValueResult struct {
index int
}

type parallelArrayValueResult struct {
values []T
err error
index int
}

// Results evaluates the query and returns the results as T slice.
// An error occurred in during evaluation of the query will be returned.
//
Expand Down Expand Up @@ -198,6 +204,83 @@ func (q ParallelQuery) Select(f func(T) (T, error)) (r ParallelQuery) {
return
}

// SelectMany returns flattens the resulting sequences into one sequence.
//
// Example:
// names, err := From(parents).AsParallel().SelectMany(func (p T, idx int) (T, error) {
// return p.(*Parent).Children, nil
// }).Results()
func (q ParallelQuery) SelectMany(f func(T, int) (T, error)) (r ParallelQuery) {
return q.SelectManyBy(f, func(p T, c T) (T, error) {
return c, nil
})
}

// SelectMany returns flattens the resulting sequences into one sequence.
//
// resultSelector takes parent element and child element as inputs
// and returns a value which will be an element in the resulting query.
//
// Example:
// names, err := From(parents).AsParallel().SelectManyBy(func (p T, idx int) (T, error) {
// return p.(*Parent).Children, nil
// }, func (p T, c T) (T, error) {
// return p.(*Parent).Name + ":" + c.(*Child).Name, nil
// }).Results()
func (q ParallelQuery) SelectManyBy(f func(T, int) (T, error),
resultSelector func(T, T) (T, error)) (r ParallelQuery) {

r = q.copyMeta()
if r.err != nil {
return r
}
if f == nil || resultSelector == nil {
r.err = ErrNilFunc
return
}

ch := make(chan *parallelArrayValueResult)
arrValues := make([][]T, len(q.values))
for i, v := range q.values {
go func(ind int, f func(T, int) (T, error), in T) {
out := parallelArrayValueResult{index: ind}
val, err := f(in, ind)
if err != nil {
out.err = err
} else {
innerCollection, ok := takeSliceArg(val)
if !ok {
out.err = ErrInvalidInput
}
for _, v := range innerCollection {
res, err := resultSelector(in, v)
if err != nil {
out.err = err
} else {
out.values = append(out.values, res)
}
}
}
ch <- &out
}(i, f, v)
}

for i := 0; i < len(q.values); i++ {
out := <-ch
if out.err != nil {
r.err = out.err
return
}
arrValues[out.index] = out.values
}
for _, arr := range arrValues {
for _, v := range arr {
r.values = append(r.values, v)
}
}
return
}

// Any determines whether the query source contains any elements.
// Example:
// anyOver18, err := From(students).AsParallel().Where(func (s T)(bool, error){
Expand Down
Loading

1 comment on commit bd15765

@mkitawaki
Copy link
Contributor

Choose a reason for hiding this comment

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

Thank you.
It's been a good education.

Please sign in to comment.