diff --git a/linq.go b/linq.go index 57bb266..63e4f03 100644 --- a/linq.go +++ b/linq.go @@ -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. diff --git a/linq_test.go b/linq_test.go index f3d86ee..25aeb90 100644 --- a/linq_test.go +++ b/linq_test.go @@ -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() diff --git a/plinq.go b/plinq.go index 3452f5c..4088a6c 100644 --- a/plinq.go +++ b/plinq.go @@ -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. // @@ -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){ diff --git a/plinq_test.go b/plinq_test.go index 69e9f38..2a4f343 100644 --- a/plinq_test.go +++ b/plinq_test.go @@ -167,6 +167,122 @@ func TestSelectParallel(t *testing.T) { }) } +func TestSelectManyParallel(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).AsParallel().Where(erroneusBinaryFunc).SelectMany(children).Results() + c.So(err, c.ShouldNotEqual, nil) + }) + + c.Convey("Nil func returns error", t, func() { + _, err := From(barArr).AsParallel().SelectMany(nil).Results() + c.So(err, c.ShouldEqual, ErrNilFunc) + }) + + c.Convey("Error returned from provided func", t, func() { + val, err := From(barArr).AsParallel().SelectMany(erroneusFunc).Results() + c.So(err, c.ShouldNotEqual, nil) + + c.Convey("Erroneus function is in chain with as-is select", func() { + _, err = From(barArr).AsParallel().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).AsParallel().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).AsParallel().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).AsParallel().SelectMany(children).Results() + c.So(err, c.ShouldEqual, nil) + c.So(val, shouldSlicesResemble, fooArr) + }) +} + +func TestSelectManyByParallel(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).AsParallel().Where(erroneusBinaryFunc).SelectManyBy(children, barStr).Results() + c.So(err, c.ShouldNotEqual, nil) + }) + + c.Convey("Nil transform func returns error", t, func() { + _, err := From(barArr).AsParallel().SelectManyBy(nil, barStr).Results() + c.So(err, c.ShouldEqual, ErrNilFunc) + }) + + c.Convey("Nil resultSelect func returns error", t, func() { + _, err := From(barArr).AsParallel().SelectManyBy(children, nil).Results() + c.So(err, c.ShouldEqual, ErrNilFunc) + }) + + c.Convey("Both nil func returns error", t, func() { + _, err := From(barArr).AsParallel().SelectManyBy(nil, nil).Results() + c.So(err, c.ShouldEqual, ErrNilFunc) + }) + + c.Convey("Error returned from provided func", t, func() { + val, err := From(barArr).AsParallel().SelectManyBy(erroneusFunc, barStr).Results() + c.So(err, c.ShouldNotEqual, nil) + + val, err = From(barArr).AsParallel().SelectManyBy(children, erroneusSelectFunc).Results() + c.So(err, c.ShouldNotEqual, nil) + + val, err = From(barArr).AsParallel().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).AsParallel().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).AsParallel().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).AsParallel().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).AsParallel().SelectManyBy(children, barStr).Results() + c.So(err, c.ShouldEqual, nil) + c.So(val, shouldSlicesResemble, fooBarArr) + }) +} + func TestAnyWithParallel(t *testing.T) { c.Convey("Previous error is reflected on result", t, func() { _, err := From(arr0).Where(erroneusBinaryFunc).AsParallel().AnyWith(alwaysTrueDelayed)