Parsing infix expressions in Clojure












2












$begingroup$


I'm trying to teach myself Clojure by reading the Brave Clojure book online, and at the end of Chapter 7 there is an exercise to parse a mathematical expression written as a list in infix notation to s-expressions, following operator precedence rules.



To do this I implemented the shunting-yard algorithm in Clojure. However I am not sure if I am doing this in a functional way, as I am used to programming in languages like C++ and Python. In particular, I am uncertain whether the use of the loop operator is valid, as I'm coding this in basically the same way I would as in C++, just using recur on every loop iteration instead of mutating variables. This makes it seem a little repetitive.



Below is the code. It would be greatly appreciated someone takes a look and offers advice about how to do this in a more idiomatic functional way, or about anything else.



(def operator-precedence {'= 1, '+ 5, '- 5, '* 6, '/ 6})

(defn infix-parse
"Converts an infix expression to s-expressions in linear time with the
shunting-yard algorithm."
[expr]
(if (list? expr)
(loop [val-stack ()
op-stack ()
remaining expr
next-op false]
(if next-op
(let [prec (if (empty? remaining)
##-Inf
(operator-precedence (first remaining)))
popped (take-while #(>= (operator-precedence %) prec) op-stack)
result (reduce
(fn [[a b & vals] op]
(cons (list op b a) vals))
val-stack
popped)]
(if (empty? remaining)
(first result)
(recur result
(cons (first remaining)
(drop (count popped) op-stack))
(rest remaining)
false)))
(recur (cons (infix-parse (first remaining)) val-stack)
op-stack
(rest remaining)
true)))
expr))

(defmacro infix
[form]
(infix-parse form))


(infix (1 + 3 * (4 - 5) * 10 / 2))
=> -14
(infix-parse '(1 + 3 * (4 - 5) * 10 / 2))
=> (+ 1 (/ (* (* 3 (- 4 5)) 10) 2))









share|improve this question









New contributor




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







$endgroup$

















    2












    $begingroup$


    I'm trying to teach myself Clojure by reading the Brave Clojure book online, and at the end of Chapter 7 there is an exercise to parse a mathematical expression written as a list in infix notation to s-expressions, following operator precedence rules.



    To do this I implemented the shunting-yard algorithm in Clojure. However I am not sure if I am doing this in a functional way, as I am used to programming in languages like C++ and Python. In particular, I am uncertain whether the use of the loop operator is valid, as I'm coding this in basically the same way I would as in C++, just using recur on every loop iteration instead of mutating variables. This makes it seem a little repetitive.



    Below is the code. It would be greatly appreciated someone takes a look and offers advice about how to do this in a more idiomatic functional way, or about anything else.



    (def operator-precedence {'= 1, '+ 5, '- 5, '* 6, '/ 6})

    (defn infix-parse
    "Converts an infix expression to s-expressions in linear time with the
    shunting-yard algorithm."
    [expr]
    (if (list? expr)
    (loop [val-stack ()
    op-stack ()
    remaining expr
    next-op false]
    (if next-op
    (let [prec (if (empty? remaining)
    ##-Inf
    (operator-precedence (first remaining)))
    popped (take-while #(>= (operator-precedence %) prec) op-stack)
    result (reduce
    (fn [[a b & vals] op]
    (cons (list op b a) vals))
    val-stack
    popped)]
    (if (empty? remaining)
    (first result)
    (recur result
    (cons (first remaining)
    (drop (count popped) op-stack))
    (rest remaining)
    false)))
    (recur (cons (infix-parse (first remaining)) val-stack)
    op-stack
    (rest remaining)
    true)))
    expr))

    (defmacro infix
    [form]
    (infix-parse form))


    (infix (1 + 3 * (4 - 5) * 10 / 2))
    => -14
    (infix-parse '(1 + 3 * (4 - 5) * 10 / 2))
    => (+ 1 (/ (* (* 3 (- 4 5)) 10) 2))









    share|improve this question









    New contributor




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







    $endgroup$















      2












      2








      2





      $begingroup$


      I'm trying to teach myself Clojure by reading the Brave Clojure book online, and at the end of Chapter 7 there is an exercise to parse a mathematical expression written as a list in infix notation to s-expressions, following operator precedence rules.



      To do this I implemented the shunting-yard algorithm in Clojure. However I am not sure if I am doing this in a functional way, as I am used to programming in languages like C++ and Python. In particular, I am uncertain whether the use of the loop operator is valid, as I'm coding this in basically the same way I would as in C++, just using recur on every loop iteration instead of mutating variables. This makes it seem a little repetitive.



      Below is the code. It would be greatly appreciated someone takes a look and offers advice about how to do this in a more idiomatic functional way, or about anything else.



      (def operator-precedence {'= 1, '+ 5, '- 5, '* 6, '/ 6})

      (defn infix-parse
      "Converts an infix expression to s-expressions in linear time with the
      shunting-yard algorithm."
      [expr]
      (if (list? expr)
      (loop [val-stack ()
      op-stack ()
      remaining expr
      next-op false]
      (if next-op
      (let [prec (if (empty? remaining)
      ##-Inf
      (operator-precedence (first remaining)))
      popped (take-while #(>= (operator-precedence %) prec) op-stack)
      result (reduce
      (fn [[a b & vals] op]
      (cons (list op b a) vals))
      val-stack
      popped)]
      (if (empty? remaining)
      (first result)
      (recur result
      (cons (first remaining)
      (drop (count popped) op-stack))
      (rest remaining)
      false)))
      (recur (cons (infix-parse (first remaining)) val-stack)
      op-stack
      (rest remaining)
      true)))
      expr))

      (defmacro infix
      [form]
      (infix-parse form))


      (infix (1 + 3 * (4 - 5) * 10 / 2))
      => -14
      (infix-parse '(1 + 3 * (4 - 5) * 10 / 2))
      => (+ 1 (/ (* (* 3 (- 4 5)) 10) 2))









      share|improve this question









      New contributor




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







      $endgroup$




      I'm trying to teach myself Clojure by reading the Brave Clojure book online, and at the end of Chapter 7 there is an exercise to parse a mathematical expression written as a list in infix notation to s-expressions, following operator precedence rules.



      To do this I implemented the shunting-yard algorithm in Clojure. However I am not sure if I am doing this in a functional way, as I am used to programming in languages like C++ and Python. In particular, I am uncertain whether the use of the loop operator is valid, as I'm coding this in basically the same way I would as in C++, just using recur on every loop iteration instead of mutating variables. This makes it seem a little repetitive.



      Below is the code. It would be greatly appreciated someone takes a look and offers advice about how to do this in a more idiomatic functional way, or about anything else.



      (def operator-precedence {'= 1, '+ 5, '- 5, '* 6, '/ 6})

      (defn infix-parse
      "Converts an infix expression to s-expressions in linear time with the
      shunting-yard algorithm."
      [expr]
      (if (list? expr)
      (loop [val-stack ()
      op-stack ()
      remaining expr
      next-op false]
      (if next-op
      (let [prec (if (empty? remaining)
      ##-Inf
      (operator-precedence (first remaining)))
      popped (take-while #(>= (operator-precedence %) prec) op-stack)
      result (reduce
      (fn [[a b & vals] op]
      (cons (list op b a) vals))
      val-stack
      popped)]
      (if (empty? remaining)
      (first result)
      (recur result
      (cons (first remaining)
      (drop (count popped) op-stack))
      (rest remaining)
      false)))
      (recur (cons (infix-parse (first remaining)) val-stack)
      op-stack
      (rest remaining)
      true)))
      expr))

      (defmacro infix
      [form]
      (infix-parse form))


      (infix (1 + 3 * (4 - 5) * 10 / 2))
      => -14
      (infix-parse '(1 + 3 * (4 - 5) * 10 / 2))
      => (+ 1 (/ (* (* 3 (- 4 5)) 10) 2))






      algorithm functional-programming clojure lisp math-expression-eval






      share|improve this question









      New contributor




      Eric Zhang 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 question









      New contributor




      Eric Zhang 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 question




      share|improve this question








      edited 3 hours ago









      200_success

      130k17155419




      130k17155419






      New contributor




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









      asked 9 hours ago









      Eric ZhangEric Zhang

      1112




      1112




      New contributor




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





      New contributor





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






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






















          1 Answer
          1






          active

          oldest

          votes


















          0












          $begingroup$

          First, yes, this is functional, and yes, this is how to use loop properly.




          . . . using recur on every loop iteration instead of mutating variables.




          This is the intent of loop. Instead of mutating a variable, you just pass the "altered data" to the next iteration via recur.



          You also aren't abusing side effects by misusing def or atoms, so that's good.





          My main concern with this code is how it's really just one giant function. There's no function names indicating what certain code is doing, and no comments noting the significance of any lines. Now, I'm personally a stickler for breaking code down into functions, but it's generally regard as best practice as well. As this answer notes (ignoring the mention of "imperative"):




          Having a large imperative function that conveys many ideas is hard to digest and reuse.




          I think the size of this function does make it hard to digest.



          Just as an extreme counter example, here's the same algorithm that I wrote a year ago*. It's almost 4x longer as yours, but it's also much clearer what each bit of code is doing (and has ~10 lines dedicated to documentation). Code like



          (-> equation
          (tokenize-equation)
          (infix->RPN-tokens op-attr-map)
          (tokens-to-string)) ; Should arguably be called token->string


          makes it fairly clear what it's responsible for, even without taking into consideration the function name that it's in.



          I'm won't try to break your code down (mostly because I burned my hand pretty bad, and typing this is hard enough), but if you take a look at my example, you may be able to find some inspiration and strike a middle ground for granularity.



          Good luck





          * Although my version doesn't evaluate the expression since I'm translating it into a String and in my example, the data isn't available at compile-time.






          share|improve this answer











          $endgroup$













            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
            });


            }
            });






            Eric Zhang is a new contributor. Be nice, and check out our Code of Conduct.










            draft saved

            draft discarded


















            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216123%2fparsing-infix-expressions-in-clojure%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









            0












            $begingroup$

            First, yes, this is functional, and yes, this is how to use loop properly.




            . . . using recur on every loop iteration instead of mutating variables.




            This is the intent of loop. Instead of mutating a variable, you just pass the "altered data" to the next iteration via recur.



            You also aren't abusing side effects by misusing def or atoms, so that's good.





            My main concern with this code is how it's really just one giant function. There's no function names indicating what certain code is doing, and no comments noting the significance of any lines. Now, I'm personally a stickler for breaking code down into functions, but it's generally regard as best practice as well. As this answer notes (ignoring the mention of "imperative"):




            Having a large imperative function that conveys many ideas is hard to digest and reuse.




            I think the size of this function does make it hard to digest.



            Just as an extreme counter example, here's the same algorithm that I wrote a year ago*. It's almost 4x longer as yours, but it's also much clearer what each bit of code is doing (and has ~10 lines dedicated to documentation). Code like



            (-> equation
            (tokenize-equation)
            (infix->RPN-tokens op-attr-map)
            (tokens-to-string)) ; Should arguably be called token->string


            makes it fairly clear what it's responsible for, even without taking into consideration the function name that it's in.



            I'm won't try to break your code down (mostly because I burned my hand pretty bad, and typing this is hard enough), but if you take a look at my example, you may be able to find some inspiration and strike a middle ground for granularity.



            Good luck





            * Although my version doesn't evaluate the expression since I'm translating it into a String and in my example, the data isn't available at compile-time.






            share|improve this answer











            $endgroup$


















              0












              $begingroup$

              First, yes, this is functional, and yes, this is how to use loop properly.




              . . . using recur on every loop iteration instead of mutating variables.




              This is the intent of loop. Instead of mutating a variable, you just pass the "altered data" to the next iteration via recur.



              You also aren't abusing side effects by misusing def or atoms, so that's good.





              My main concern with this code is how it's really just one giant function. There's no function names indicating what certain code is doing, and no comments noting the significance of any lines. Now, I'm personally a stickler for breaking code down into functions, but it's generally regard as best practice as well. As this answer notes (ignoring the mention of "imperative"):




              Having a large imperative function that conveys many ideas is hard to digest and reuse.




              I think the size of this function does make it hard to digest.



              Just as an extreme counter example, here's the same algorithm that I wrote a year ago*. It's almost 4x longer as yours, but it's also much clearer what each bit of code is doing (and has ~10 lines dedicated to documentation). Code like



              (-> equation
              (tokenize-equation)
              (infix->RPN-tokens op-attr-map)
              (tokens-to-string)) ; Should arguably be called token->string


              makes it fairly clear what it's responsible for, even without taking into consideration the function name that it's in.



              I'm won't try to break your code down (mostly because I burned my hand pretty bad, and typing this is hard enough), but if you take a look at my example, you may be able to find some inspiration and strike a middle ground for granularity.



              Good luck





              * Although my version doesn't evaluate the expression since I'm translating it into a String and in my example, the data isn't available at compile-time.






              share|improve this answer











              $endgroup$
















                0












                0








                0





                $begingroup$

                First, yes, this is functional, and yes, this is how to use loop properly.




                . . . using recur on every loop iteration instead of mutating variables.




                This is the intent of loop. Instead of mutating a variable, you just pass the "altered data" to the next iteration via recur.



                You also aren't abusing side effects by misusing def or atoms, so that's good.





                My main concern with this code is how it's really just one giant function. There's no function names indicating what certain code is doing, and no comments noting the significance of any lines. Now, I'm personally a stickler for breaking code down into functions, but it's generally regard as best practice as well. As this answer notes (ignoring the mention of "imperative"):




                Having a large imperative function that conveys many ideas is hard to digest and reuse.




                I think the size of this function does make it hard to digest.



                Just as an extreme counter example, here's the same algorithm that I wrote a year ago*. It's almost 4x longer as yours, but it's also much clearer what each bit of code is doing (and has ~10 lines dedicated to documentation). Code like



                (-> equation
                (tokenize-equation)
                (infix->RPN-tokens op-attr-map)
                (tokens-to-string)) ; Should arguably be called token->string


                makes it fairly clear what it's responsible for, even without taking into consideration the function name that it's in.



                I'm won't try to break your code down (mostly because I burned my hand pretty bad, and typing this is hard enough), but if you take a look at my example, you may be able to find some inspiration and strike a middle ground for granularity.



                Good luck





                * Although my version doesn't evaluate the expression since I'm translating it into a String and in my example, the data isn't available at compile-time.






                share|improve this answer











                $endgroup$



                First, yes, this is functional, and yes, this is how to use loop properly.




                . . . using recur on every loop iteration instead of mutating variables.




                This is the intent of loop. Instead of mutating a variable, you just pass the "altered data" to the next iteration via recur.



                You also aren't abusing side effects by misusing def or atoms, so that's good.





                My main concern with this code is how it's really just one giant function. There's no function names indicating what certain code is doing, and no comments noting the significance of any lines. Now, I'm personally a stickler for breaking code down into functions, but it's generally regard as best practice as well. As this answer notes (ignoring the mention of "imperative"):




                Having a large imperative function that conveys many ideas is hard to digest and reuse.




                I think the size of this function does make it hard to digest.



                Just as an extreme counter example, here's the same algorithm that I wrote a year ago*. It's almost 4x longer as yours, but it's also much clearer what each bit of code is doing (and has ~10 lines dedicated to documentation). Code like



                (-> equation
                (tokenize-equation)
                (infix->RPN-tokens op-attr-map)
                (tokens-to-string)) ; Should arguably be called token->string


                makes it fairly clear what it's responsible for, even without taking into consideration the function name that it's in.



                I'm won't try to break your code down (mostly because I burned my hand pretty bad, and typing this is hard enough), but if you take a look at my example, you may be able to find some inspiration and strike a middle ground for granularity.



                Good luck





                * Although my version doesn't evaluate the expression since I'm translating it into a String and in my example, the data isn't available at compile-time.







                share|improve this answer














                share|improve this answer



                share|improve this answer








                edited 7 hours ago

























                answered 7 hours ago









                CarcigenicateCarcigenicate

                3,77811632




                3,77811632






















                    Eric Zhang is a new contributor. Be nice, and check out our Code of Conduct.










                    draft saved

                    draft discarded


















                    Eric Zhang is a new contributor. Be nice, and check out our Code of Conduct.













                    Eric Zhang is a new contributor. Be nice, and check out our Code of Conduct.












                    Eric Zhang is a new contributor. Be nice, and check out our Code of Conduct.
















                    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.




                    draft saved


                    draft discarded














                    StackExchange.ready(
                    function () {
                    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f216123%2fparsing-infix-expressions-in-clojure%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?