braveandtrue

https://www.braveclojure.com/
quoll 2016-09-12T17:42:24.000004Z

@madarauchiha: are you still looking at the infix->prefix question?

quoll 2016-09-12T19:34:25.000005Z

I looked at this question, because I’d just done something like it recently

quoll 2016-09-12T19:35:08.000006Z

I’m guessing you’re in a timezone that won’t see this until later, so I’ll dump in here for now

quoll 2016-09-12T19:36:22.000007Z

I’ll confess that there may well be a much better way (and when I did it myself I used parsatron, so I was already doing it differently)

quoll 2016-09-12T19:36:30.000008Z

but this is how I did it...

quoll 2016-09-12T19:37:02.000009Z

first of all, I wanted to test for operators at each level. * and / have the same precedence, as do + and -

quoll 2016-09-12T19:37:57.000010Z

Also, I’m going to presume symbols, so my test expression will be similar to yours:

quoll 2016-09-12T19:38:37.000011Z

(def test-expression '(1 + 2 * 3 - 4 * 5 + 6))

quoll 2016-09-12T19:39:52.000013Z

if I simplify for a moment… let’s just look at the * operator:

quoll 2016-09-12T19:40:34.000014Z

(def test2 '(a * b * c * d))

quoll 2016-09-12T19:42:58.000015Z

to get to this, we want to group (1 + 2) as a, (3 - 4) as b, etc ARGH! I did this back to front! It still works but I need to invert everything that follows! Please bear this in mind as you read!!!! I’ll fix this at the end, promise!!! 😳

quoll 2016-09-12T19:44:08.000016Z

a function for that is split-with. The test to use is anything that’s not a * or /. That can be done by complementing the set: (def mtest (complement '#{* /}))

quoll 2016-09-12T19:44:37.000017Z

testing it on the expression:

quoll 2016-09-12T19:46:17.000018Z

=> (split-with mtest test-expression)
[(1 + 2) (* 3 - 4 * 5 + 6)]

quoll 2016-09-12T19:47:21.000019Z

decent start. So now let’s loop to split it all up. We’ll want to pull those operators out too, so I’ll use some destructuring to get at it, and the remainder:

quoll 2016-09-12T19:50:43.000020Z

(loop [result [] ex test-expression]
  (let [[next-operand [op & remaining]] (split-with mtest ex)
        new-result (conj result next-operand)]
    (if op
      (recur (conj new-result op) remaining)
      new-result)))

quoll 2016-09-12T19:51:46.000022Z

[(1 + 2) * (3 - 4) * (5 + 6)]

quoll 2016-09-12T19:52:19.000023Z

this let us split by the * character! For anyone watching… maybe there was an easier way?

quoll 2016-09-12T19:53:25.000024Z

anyway, this was useful. It also looks like something we could use to split by the + and - characters. So I can wrap it in a function and parameterize on that

quoll 2016-09-12T19:54:46.000025Z

(defn factor-out [the-test expression]
  (loop [result [] ex expression]
    (let [[next-operand [op & remaining]] (split-with the-test ex)
          new-result (conj result next-operand)]
      (if op
        (recur (conj new-result op) remaining)
        new-result))))

quoll 2016-09-12T19:55:36.000026Z

and a new test function for summation-type operators: (def stest (complement '#{+ -}))

quoll 2016-09-12T19:58:04.000028Z

so, now I have a way to take an expression and pull it down into the form '(a op b op c). So now we want to process it into (op a (op b c))

quoll 2016-09-12T20:00:01.000029Z

remember, I said they start as binary operators, so that means whenever we see '(a op ….) then we’re going to turn that into '(op a …) If we don’t see an op (i.e. just have a single thing like '(a) then we just want to return that

quoll 2016-09-12T20:00:14.000030Z

I’m going to recurse this time...

quoll 2016-09-12T20:00:23.000031Z

because it looks like a general rule

quoll 2016-09-12T20:02:30.000032Z

if we pull out the first item, the operator, and the remainder, then when there is a remainder still to be processed, we return a list of the operator, the first item and then everything else processed. When there is no remainder to be processed (the operator should be nil in this case) then just return that first item

quoll 2016-09-12T20:04:02.000033Z

(defn prefix-form [[item op & remainder]]
  (if (seq remainder)
    (list op item (prefix-form remainder))
    item))

quoll 2016-09-12T20:05:52.000035Z

how did this go? Trying it out:

quoll 2016-09-12T20:05:58.000036Z

=> (prefix-form (factor-out mtest test-expression))
(* (1 + 2) (* (3 - 4) (5 + 6)))

quoll 2016-09-12T20:06:37.000037Z

Great! So now we just want to do pretty much the same thing to the +- expressions

quoll 2016-09-12T20:07:21.000038Z

Let’s take the first one… '(1 + 2)

quoll 2016-09-12T20:07:55.000039Z

=> (factor-out stest '(1 + 2))
[(1) + (2)]

quoll 2016-09-12T20:08:19.000040Z

this is pretty good, but what about the parens around the 1 and 2?

quoll 2016-09-12T20:10:27.000041Z

Turns out, they’re OK. The reason is clearer if we think about operands in the general sense. If we look at a language that supports ^ for raising to a power, then this has higher precedence than */ which has higher precedence than +-

quoll 2016-09-12T20:13:25.000043Z

basically, we want to apply our method at each level of precedence, and then do it again with new tests each time

quoll 2016-09-12T20:13:47.000044Z

OK, at the point where I will try to invert everything 😳

quoll 2016-09-12T20:15:07.000045Z

let’s try again...

quoll 2016-09-12T20:15:32.000046Z

=> (prefix-form (factor-out stest test-expression))
(+ (1) (- (2 * 3) (+ (4 * 5) (6))))

quoll 2016-09-12T20:16:09.000047Z

the precedence is fixed, and it’s all still working. This is what abstraction does for us 😄

quoll 2016-09-12T20:17:07.000048Z

So I got things backwards, but abstracting meant it was a trivial bug 🙂

quoll 2016-09-12T20:18:27.000049Z

each element in the prefix form (the things being added and subtracted) now just need to be operated on in the same way, except with */ instead of +-

quoll 2016-09-12T20:20:19.000050Z

so… let’s re-write prefix-form, this time taking a function saying what we want to do with the elements that show up as arguments:

quoll 2016-09-12T20:21:39.000051Z

(defn prefix-form-better [f [item op & remainder]]
  (if (seq remainder)
    (list op (f item) (prefix-form remainder))
    (f item)))

quoll 2016-09-12T20:22:26.000052Z

if f=identity then it’s exactly what we had a moment ago

madarauchiha 2016-09-12T20:23:09.000053Z

Let me read it all, one moment 😄

quoll 2016-09-12T20:24:30.000054Z

OK… I have just 2 lines of code to the final solution now 🙂 (and they’re really simple lines)

madarauchiha 2016-09-12T20:26:43.000056Z

What does this mean?

madarauchiha 2016-09-12T20:26:56.000057Z

Complement of a set?

quoll 2016-09-12T20:27:23.000058Z

do you know how a set is a function?

madarauchiha 2016-09-12T20:27:30.000059Z

That's news to me.

madarauchiha 2016-09-12T20:27:42.000060Z

I knew it was an iterable

quoll 2016-09-12T20:27:47.000061Z

it can be used as a truthy/falsey test

madarauchiha 2016-09-12T20:27:58.000062Z

I see

quoll 2016-09-12T20:28:08.000063Z

there are a number of functions that can take advantage of it

madarauchiha 2016-09-12T20:28:14.000064Z

So a set is also a predicate that accepts an argument and returns whether or not that argument is in the set

quoll 2016-09-12T20:28:19.000065Z

exactly

madarauchiha 2016-09-12T20:28:29.000066Z

I see

madarauchiha 2016-09-12T20:28:44.000067Z

So complement will return the predicate which returns true when the argument isn't in the set

madarauchiha 2016-09-12T20:28:45.000068Z

Gotcha

quoll 2016-09-12T20:29:04.000069Z

if I try: (map #{3} [1 2 3 4 5]) I get back [nil nil 3 nil nil]

madarauchiha 2016-09-12T20:29:51.000070Z

So you can do things like (filter set1 set2) to get the intersection

quoll 2016-09-12T20:30:13.000071Z

yes 🙂

madarauchiha 2016-09-12T20:30:16.000072Z

Neat

madarauchiha 2016-09-12T20:30:56.000073Z

Alright back to reading

quoll 2016-09-12T20:30:59.000074Z

complement changes a truthiness test into a true/false test

madarauchiha 2016-09-12T20:31:07.000075Z

Yeah, I know what complement does

quoll 2016-09-12T20:31:11.000076Z

cool

madarauchiha 2016-09-12T20:31:17.000077Z

I wrote my own before learning about it 😄

madarauchiha 2016-09-12T20:31:45.000078Z

Then I thought "wait, there's no way a functional language doesn't already have complement, I'm an idiot"

quoll 2016-09-12T20:33:12.000079Z

OK… I’ll just finish…

quoll 2016-09-12T20:36:18.000080Z

if we look at the final steps first… evaluating things like ((2) * (3)) can be done by calling prefix-form-better using identity, and we get:

(prefix-form-better identity '((2) * (3)))
(* (2) (3))

quoll 2016-09-12T20:37:06.000081Z

since we have this handy function that we’re applying, then instead of identity, we can use first

quoll 2016-09-12T20:37:49.000082Z

define that to be a function, and it can be the “f” that gets passed in to make the prefix-sum!

quoll 2016-09-12T20:39:55.000084Z

(def prefix-prod #(prefix-form-better first (factor-out mtest %)))
(def prefix-sum #(prefix-form-better prefix-prod (factor-out stest %)))

quoll 2016-09-12T20:40:22.000085Z

yes, I did. I did it in my own repl using functions that had single character names 🙂

quoll 2016-09-12T20:40:46.000086Z

so when I did a copy/paste to Slack, I had to correct manually. Sometimes I stuffed up 🙂

madarauchiha 2016-09-12T20:41:00.000087Z

This is another thing I wanted to ask, because I'm unfamiliar with how developing in a Lispy language works

madarauchiha 2016-09-12T20:41:10.000088Z

Do you generally write code in the repl, and then copy it to a file?

madarauchiha 2016-09-12T20:41:29.000089Z

Do you write the code in the file, load it in repl, then play with it? How do you persist the work from the REPL afterwards?

quoll 2016-09-12T20:42:45.000090Z

most people do it in emacs or another editor that lets you apply read/eval to parts of the buffer

quoll 2016-09-12T20:43:18.000091Z

that typically involves a learning curve on how to do it. It doesn’t take long, but it can be annoying

quoll 2016-09-12T20:43:46.000092Z

if you’ve use a CLI repl, then I open the history file and use that 🙂

quoll 2016-09-12T20:43:58.000093Z

anyway…. I have to go

quoll 2016-09-12T20:44:40.000094Z

I hope I didn’t write at too simple a level for you. You were away and couldn’t ask questions, so I figured I should explain it to the simplest level that I could think of

madarauchiha 2016-09-12T20:45:16.000095Z

That's fine, it's really helpful

madarauchiha 2016-09-12T20:45:22.000096Z

Thanks a lot for your time!

quoll 2016-09-12T20:45:29.000097Z

I also apologize for getting the precedence wrong. No idea what I was thinking there! I think it’s very cool that it just involved a little bit of swapping of symbols and it worked again 🙂

quoll 2016-09-12T20:51:34.000098Z

you’re welcome on the time. Hopefully someone will offer a better solution than this one

quoll 2016-09-12T20:52:03.000099Z

to be complete… this is the working code I used:

quoll 2016-09-12T20:52:47.000100Z

(defn fp [t exp]
  (loop [r [] ex exp]                     
    (let [[n [op & rem]] (split-with t ex)
          newr (conj r n)]
      (if op                              
        (recur (conj newr op) rem)        
        newr))))

(defn prefix [f [l op & r]] (if (seq r) (list op (f l) (prefix f r)) (f l)))

(def stest (complement '#{+ -}))
(def mtest (complement '#{* /}))
(def prefix-prod #(prefix first (fp mtest %)))
(def prefix-sum #(prefix prefix-prod (fp stest %)))

(prefix-sum ‘(1 + 2 * 3 - 4 * 5 + 6))

madarauchiha 2016-09-12T20:58:01.000101Z

Hmm

madarauchiha 2016-09-12T20:58:05.000102Z

I think the result is wrong though 😛

madarauchiha 2016-09-12T20:58:23.000103Z

user=> (prefix-sum '(1 + 2 * 3 - 4 * 5 + 6))
(+ 1 (- (* 2 3) (+ (* 4 5) 6)))

madarauchiha 2016-09-12T20:58:46.000105Z

That should be something like

madarauchiha 2016-09-12T20:59:28.000106Z

(+ 1 (+ (- (* 2 3) (* 4 5)) 6))