Cartesian Product in Go












0














I am still fairly new to go, and would appreciate any tips on style, best practices, etc, but am especially interested to know if this non-recursive cartesian product implementation can be made significantly faster (eg, when the number of results in the result set is on the order of 1e9).



I've played around with adding more goroutines, but parallelism doesn't seem to help much, if at all. I may be missing a much better approach though.



https://play.golang.org/p/H-M6CbmeFoV



package main

import (
"fmt"
)

// Given a mixed base, returns a function that:
//
// Increments a number, represented as a slice of digits, defined
// in that base. For example, if our base is 2 3 2, we'll count
// like this:
//
// 0 0 0 ; 0 0 1; 0 1 0; 0 1 1; 0 2 0; 0 2 1;
// 1 0 0 ; 1 0 1; 1 1 0; 1 1 1; 1 2 0; 1 2 1;
func mixedBaseInc(bases int) func(*int) {

return func(digits *int) {
ret := *digits
i := len(ret) - 1
for {
base := bases[i]
ret[i] = (ret[i] + 1) % base
noCarry := ret[i] != 0

if noCarry || i == 0 {
return
}
i--
}
}
}

func pick(indexes int, params interface{}) interface{} {
ret := make(interface{}, len(params))
for i, x := range indexes {
ret[i] = params[i][x]
}
return ret
}

func XProd(params ...interface{}) chan interface{} {
var paramLens, digits int
numElms := 1
c := make(chan interface{})

for _, x := range params {
paramLens = append(paramLens, len(x))
numElms *= len(x)
digits = append(digits, 0)
}

inc := mixedBaseInc(paramLens)

go func() {
defer close(c)
for i := 0; i < numElms; i++ {
c <- pick(digits, params)
inc(&digits)
}
}()

return c
}

func main() {
for x := range XProd(interface{}{1, 2, 3}, interface{}{4, 5}) {
fmt.Println(x)
}
}









share|improve this question



























    0














    I am still fairly new to go, and would appreciate any tips on style, best practices, etc, but am especially interested to know if this non-recursive cartesian product implementation can be made significantly faster (eg, when the number of results in the result set is on the order of 1e9).



    I've played around with adding more goroutines, but parallelism doesn't seem to help much, if at all. I may be missing a much better approach though.



    https://play.golang.org/p/H-M6CbmeFoV



    package main

    import (
    "fmt"
    )

    // Given a mixed base, returns a function that:
    //
    // Increments a number, represented as a slice of digits, defined
    // in that base. For example, if our base is 2 3 2, we'll count
    // like this:
    //
    // 0 0 0 ; 0 0 1; 0 1 0; 0 1 1; 0 2 0; 0 2 1;
    // 1 0 0 ; 1 0 1; 1 1 0; 1 1 1; 1 2 0; 1 2 1;
    func mixedBaseInc(bases int) func(*int) {

    return func(digits *int) {
    ret := *digits
    i := len(ret) - 1
    for {
    base := bases[i]
    ret[i] = (ret[i] + 1) % base
    noCarry := ret[i] != 0

    if noCarry || i == 0 {
    return
    }
    i--
    }
    }
    }

    func pick(indexes int, params interface{}) interface{} {
    ret := make(interface{}, len(params))
    for i, x := range indexes {
    ret[i] = params[i][x]
    }
    return ret
    }

    func XProd(params ...interface{}) chan interface{} {
    var paramLens, digits int
    numElms := 1
    c := make(chan interface{})

    for _, x := range params {
    paramLens = append(paramLens, len(x))
    numElms *= len(x)
    digits = append(digits, 0)
    }

    inc := mixedBaseInc(paramLens)

    go func() {
    defer close(c)
    for i := 0; i < numElms; i++ {
    c <- pick(digits, params)
    inc(&digits)
    }
    }()

    return c
    }

    func main() {
    for x := range XProd(interface{}{1, 2, 3}, interface{}{4, 5}) {
    fmt.Println(x)
    }
    }









    share|improve this question

























      0












      0








      0







      I am still fairly new to go, and would appreciate any tips on style, best practices, etc, but am especially interested to know if this non-recursive cartesian product implementation can be made significantly faster (eg, when the number of results in the result set is on the order of 1e9).



      I've played around with adding more goroutines, but parallelism doesn't seem to help much, if at all. I may be missing a much better approach though.



      https://play.golang.org/p/H-M6CbmeFoV



      package main

      import (
      "fmt"
      )

      // Given a mixed base, returns a function that:
      //
      // Increments a number, represented as a slice of digits, defined
      // in that base. For example, if our base is 2 3 2, we'll count
      // like this:
      //
      // 0 0 0 ; 0 0 1; 0 1 0; 0 1 1; 0 2 0; 0 2 1;
      // 1 0 0 ; 1 0 1; 1 1 0; 1 1 1; 1 2 0; 1 2 1;
      func mixedBaseInc(bases int) func(*int) {

      return func(digits *int) {
      ret := *digits
      i := len(ret) - 1
      for {
      base := bases[i]
      ret[i] = (ret[i] + 1) % base
      noCarry := ret[i] != 0

      if noCarry || i == 0 {
      return
      }
      i--
      }
      }
      }

      func pick(indexes int, params interface{}) interface{} {
      ret := make(interface{}, len(params))
      for i, x := range indexes {
      ret[i] = params[i][x]
      }
      return ret
      }

      func XProd(params ...interface{}) chan interface{} {
      var paramLens, digits int
      numElms := 1
      c := make(chan interface{})

      for _, x := range params {
      paramLens = append(paramLens, len(x))
      numElms *= len(x)
      digits = append(digits, 0)
      }

      inc := mixedBaseInc(paramLens)

      go func() {
      defer close(c)
      for i := 0; i < numElms; i++ {
      c <- pick(digits, params)
      inc(&digits)
      }
      }()

      return c
      }

      func main() {
      for x := range XProd(interface{}{1, 2, 3}, interface{}{4, 5}) {
      fmt.Println(x)
      }
      }









      share|improve this question













      I am still fairly new to go, and would appreciate any tips on style, best practices, etc, but am especially interested to know if this non-recursive cartesian product implementation can be made significantly faster (eg, when the number of results in the result set is on the order of 1e9).



      I've played around with adding more goroutines, but parallelism doesn't seem to help much, if at all. I may be missing a much better approach though.



      https://play.golang.org/p/H-M6CbmeFoV



      package main

      import (
      "fmt"
      )

      // Given a mixed base, returns a function that:
      //
      // Increments a number, represented as a slice of digits, defined
      // in that base. For example, if our base is 2 3 2, we'll count
      // like this:
      //
      // 0 0 0 ; 0 0 1; 0 1 0; 0 1 1; 0 2 0; 0 2 1;
      // 1 0 0 ; 1 0 1; 1 1 0; 1 1 1; 1 2 0; 1 2 1;
      func mixedBaseInc(bases int) func(*int) {

      return func(digits *int) {
      ret := *digits
      i := len(ret) - 1
      for {
      base := bases[i]
      ret[i] = (ret[i] + 1) % base
      noCarry := ret[i] != 0

      if noCarry || i == 0 {
      return
      }
      i--
      }
      }
      }

      func pick(indexes int, params interface{}) interface{} {
      ret := make(interface{}, len(params))
      for i, x := range indexes {
      ret[i] = params[i][x]
      }
      return ret
      }

      func XProd(params ...interface{}) chan interface{} {
      var paramLens, digits int
      numElms := 1
      c := make(chan interface{})

      for _, x := range params {
      paramLens = append(paramLens, len(x))
      numElms *= len(x)
      digits = append(digits, 0)
      }

      inc := mixedBaseInc(paramLens)

      go func() {
      defer close(c)
      for i := 0; i < numElms; i++ {
      c <- pick(digits, params)
      inc(&digits)
      }
      }()

      return c
      }

      func main() {
      for x := range XProd(interface{}{1, 2, 3}, interface{}{4, 5}) {
      fmt.Println(x)
      }
      }






      go






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Dec 30 '18 at 6:43









      Jonah

      3,414617




      3,414617






















          1 Answer
          1






          active

          oldest

          votes


















          2














          I think you should replace mixedBaseInc with a generator that returns the combinations of indexes. That would simplify XProd by taking out numElms and the construction of digits.



          That gives you the option to parallelise XProd by instantiating more instances of the goroutine that outputs the product vectors (because the closure no longer binds digits). If that is the bottleneck then that improves throughput.



          However it depends on the program where this is used; if most of the work is done by the consumer of the output vectors then the best speed-up is for the consumer to consume in a way that can be parallelised.



          An alternative approach is to build the output vectors one element at a time — https://github.com/schwarmco/go-cartesian-product/blob/master/cartesian.go for example . That solution has pros and cons and it's a bit more complicated to increase its parallelism, but it might be much better on some inputs (perhaps if there are a large number of small input sets to the product).






          share|improve this answer








          New contributor




          Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
          Check out our Code of Conduct.


















            Your Answer





            StackExchange.ifUsing("editor", function () {
            return StackExchange.using("mathjaxEditing", function () {
            StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
            StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
            });
            });
            }, "mathjax-editing");

            StackExchange.ifUsing("editor", function () {
            StackExchange.using("externalEditor", function () {
            StackExchange.using("snippets", function () {
            StackExchange.snippets.init();
            });
            });
            }, "code-snippets");

            StackExchange.ready(function() {
            var channelOptions = {
            tags: "".split(" "),
            id: "196"
            };
            initTagRenderer("".split(" "), "".split(" "), channelOptions);

            StackExchange.using("externalEditor", function() {
            // Have to fire editor after snippets, if snippets enabled
            if (StackExchange.settings.snippets.snippetsEnabled) {
            StackExchange.using("snippets", function() {
            createEditor();
            });
            }
            else {
            createEditor();
            }
            });

            function createEditor() {
            StackExchange.prepareEditor({
            heartbeatType: 'answer',
            autoActivateHeartbeat: false,
            convertImagesToLinks: false,
            noModals: true,
            showLowRepImageUploadWarning: true,
            reputationToPostImages: null,
            bindNavPrevention: true,
            postfix: "",
            imageUploader: {
            brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
            contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
            allowUrls: true
            },
            onDemand: true,
            discardSelector: ".discard-answer"
            ,immediatelyShowMarkdownHelp:true
            });


            }
            });














            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210590%2fcartesian-product-in-go%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            1 Answer
            1






            active

            oldest

            votes








            1 Answer
            1






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            2














            I think you should replace mixedBaseInc with a generator that returns the combinations of indexes. That would simplify XProd by taking out numElms and the construction of digits.



            That gives you the option to parallelise XProd by instantiating more instances of the goroutine that outputs the product vectors (because the closure no longer binds digits). If that is the bottleneck then that improves throughput.



            However it depends on the program where this is used; if most of the work is done by the consumer of the output vectors then the best speed-up is for the consumer to consume in a way that can be parallelised.



            An alternative approach is to build the output vectors one element at a time — https://github.com/schwarmco/go-cartesian-product/blob/master/cartesian.go for example . That solution has pros and cons and it's a bit more complicated to increase its parallelism, but it might be much better on some inputs (perhaps if there are a large number of small input sets to the product).






            share|improve this answer








            New contributor




            Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
            Check out our Code of Conduct.























              2














              I think you should replace mixedBaseInc with a generator that returns the combinations of indexes. That would simplify XProd by taking out numElms and the construction of digits.



              That gives you the option to parallelise XProd by instantiating more instances of the goroutine that outputs the product vectors (because the closure no longer binds digits). If that is the bottleneck then that improves throughput.



              However it depends on the program where this is used; if most of the work is done by the consumer of the output vectors then the best speed-up is for the consumer to consume in a way that can be parallelised.



              An alternative approach is to build the output vectors one element at a time — https://github.com/schwarmco/go-cartesian-product/blob/master/cartesian.go for example . That solution has pros and cons and it's a bit more complicated to increase its parallelism, but it might be much better on some inputs (perhaps if there are a large number of small input sets to the product).






              share|improve this answer








              New contributor




              Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
              Check out our Code of Conduct.





















                2












                2








                2






                I think you should replace mixedBaseInc with a generator that returns the combinations of indexes. That would simplify XProd by taking out numElms and the construction of digits.



                That gives you the option to parallelise XProd by instantiating more instances of the goroutine that outputs the product vectors (because the closure no longer binds digits). If that is the bottleneck then that improves throughput.



                However it depends on the program where this is used; if most of the work is done by the consumer of the output vectors then the best speed-up is for the consumer to consume in a way that can be parallelised.



                An alternative approach is to build the output vectors one element at a time — https://github.com/schwarmco/go-cartesian-product/blob/master/cartesian.go for example . That solution has pros and cons and it's a bit more complicated to increase its parallelism, but it might be much better on some inputs (perhaps if there are a large number of small input sets to the product).






                share|improve this answer








                New contributor




                Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.









                I think you should replace mixedBaseInc with a generator that returns the combinations of indexes. That would simplify XProd by taking out numElms and the construction of digits.



                That gives you the option to parallelise XProd by instantiating more instances of the goroutine that outputs the product vectors (because the closure no longer binds digits). If that is the bottleneck then that improves throughput.



                However it depends on the program where this is used; if most of the work is done by the consumer of the output vectors then the best speed-up is for the consumer to consume in a way that can be parallelised.



                An alternative approach is to build the output vectors one element at a time — https://github.com/schwarmco/go-cartesian-product/blob/master/cartesian.go for example . That solution has pros and cons and it's a bit more complicated to increase its parallelism, but it might be much better on some inputs (perhaps if there are a large number of small input sets to the product).







                share|improve this answer








                New contributor




                Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.









                share|improve this answer



                share|improve this answer






                New contributor




                Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.









                answered Dec 30 '18 at 9:46









                Colin Phipps

                1763




                1763




                New contributor




                Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.





                New contributor





                Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.






                Colin Phipps is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
                Check out our Code of Conduct.






























                    draft saved

                    draft discarded




















































                    Thanks for contributing an answer to Code Review Stack Exchange!


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    Use MathJax to format equations. MathJax reference.


                    To learn more, see our tips on writing great answers.





                    Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                    Please pay close attention to the following guidance:


                    • Please be sure to answer the question. Provide details and share your research!

                    But avoid



                    • Asking for help, clarification, or responding to other answers.

                    • Making statements based on opinion; back them up with references or personal experience.


                    To learn more, see our tips on writing great answers.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210590%2fcartesian-product-in-go%23new-answer', 'question_page');
                    }
                    );

                    Post as a guest















                    Required, but never shown





















































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown

































                    Required, but never shown














                    Required, but never shown












                    Required, but never shown







                    Required, but never shown







                    Popular posts from this blog

                    How to reconfigure Docker Trusted Registry 2.x.x to use CEPH FS mount instead of NFS and other traditional...

                    is 'sed' thread safe

                    How to make a Squid Proxy server?