Fork me on GitHub
#beginners
<
2019-03-31
>
Nate Sutton00:03:27

I'm trying to walk a grid represented using nested vectors like [[[] [] []] [[] [] []] [[] [] []]] (3x3 grid)

Nate Sutton00:03:41

the outer vector is the entire grid

Nate Sutton00:03:49

the next inner vectors are the rows

Nate Sutton00:03:58

and the innermost vectors are the columns

Nate Sutton00:03:35

I'd like to walk this and pass in both the x and the y into a function and allow the updating of the grid

Nate Sutton00:03:10

since I'll be updating the grid as I go it feels like I should be using reduce

Nate Sutton00:03:25

but I'm stuck on how to reduce over two levels of the structure like that, and to keep the indexes I'm on

Nate Sutton00:03:34

nested reduce-kv?

Lennart Buit00:03:15

I like to think of reducing as the act of changing the dimensions of my collection

Nate Sutton00:03:03

that's usually what I use it for

Nate Sutton00:03:42

but I also use it for transforming data structures as I enumerate over a collection

Lennart Buit00:03:03

if you keep the same number of collection members, map is way easier for that ^^

Lennart Buit00:03:22

and, there is also map-indexed, which I think is what you are looking for

Nate Sutton00:03:54

I'll be changing bits in other parts of the grid when I change an individual cell in the grid, though

Lennart Buit00:03:29

oh… yeah that changes things

Nate Sutton00:03:22

it's a maze I'm building, and doing a binary tree algorithm on it. so if I open a path to the north of a cell then that cell to the north also needs to know the south is open

Nate Sutton00:03:58

it feels like reduce-kv is what I want

Nate Sutton00:03:08

and to nest them

Nate Sutton00:03:04

and to keep returning the entire updated grid on each iteration

Nate Sutton00:03:12

of both the inner and outer reduce-kv loops

Lennart Buit00:03:44

would nested reduce-kv solve your issue? Like calling reduce-kv inside the reducing function of an outer reduce-kv?

Nate Sutton00:03:45

I could do a loop, too, I guess

Nate Sutton00:03:03

yeah, that's what I was thinking, nesting them

seancorfield01:03:18

@nate_clojurians probably easier if you used a different representation for your data. Perhaps a map keyed on the X,Y coordinates as a pair?

Nate Sutton01:03:12

yeeaaahhh maybe that's the better choice

Nate Sutton01:03:45

it certainly sounds easier

elamje03:03:55

The other day I saw that clojure allows metadata to be appended to data, this does not affect the data during comparison.

elamje03:03:29

After reading that I wondered how some of you use this ability in your programs? What is a cool way you have heard of someone using metadata?

seancorfield03:03:47

@j3elam Probably the coolest thing that comes to minds right now is that you can add metadata to data so that the data can participate in a protocol. For example, with datafy and nav which are new in Clojure 1.10.

Nate Sutton04:03:02

hmmm I'm not sure that a hash-map with [x y] keys is actually better... I still need nested loops to iterate over (range n-cols) and (range n-rows) and accumulate changes as I walk the grid

Nate Sutton04:03:26

it removes depth in the structure but not depth in the iteration

Nate Sutton04:03:34

and for the structure I could just use update-in if it has any depth

seancorfield04:03:46

@nate_clojurians I thought you were already doing the loops to get x and y...

Nate Sutton04:03:19

to generate the basic structure I use a for loop

seancorfield04:03:54

Since it's a 2D grid, you pretty much have to address it via x and y tho', right?

Nate Sutton04:03:05

yeah, seems like it

Nate Sutton04:03:33

I guess I could use another for loop to generate keys in the order I need to walk the maze

seancorfield04:03:14

My suggestion was meant to make it easy for you to make multiple changes easily since you can assoc or update against the entire hash map (maze) at any point, using just [x y] keys for whatever x and y you already have on hand.

Nate Sutton04:03:36

ahh yeah I suppose when I need to update multiple parts of the grid at once that'll simplify things

Nate Sutton04:03:42

is there a better way to generate the set here with all the ifs?

Nate Sutton04:03:45

(defn- valid-directions
  [n-cols n-rows [x y] relevant-directions]
  (let [at-max-x (= x (- n-cols 1))
        at-min-x (= x 0)
        at-max-y (= y (- n-rows 1))
        at-min-y (= y 0)]
    (set/intersection
     relevant-directions
     (set
      [(if at-max-x nil :east)
       (if at-min-x nil :west)
       (if at-max-y nil :north)
       (if at-min-y nil :south)]))))

Nate Sutton04:03:22

I suppose I could reduce over relevant-directions and use a cond...

Nate Sutton04:03:17

(defn- valid-directions
  [n-cols n-rows [x y] relevant-directions]
  (let [at-max-x (= x (- n-cols 1))
        at-min-x (= x 0)
        at-max-y (= y (- n-rows 1))
        at-min-y (= y 0)]
    (reduce (fn [s direction]
              (case direction
                :east (if at-max-x s (conj s :east))
                :west (if at-min-x s (conj s :west))
                :north (if at-max-y s (conj s :north))
                :south (if at-min-y s (conj s :south))
                s))
            #{}
            relevant-directions)))

Nate Sutton04:03:43

the former is a lot easier for me to read than the latter

Nate Sutton04:03:54

but I could use the form (when (not at-max-x) :east) maybe

Nate Sutton04:03:23

oh, there's a when-not

Nate Sutton04:03:51

yeah, I like this best, I think

Nate Sutton04:03:52

(defn- valid-directions
  [n-cols n-rows [x y] relevant-directions]
  (let [at-max-x (= x (- n-cols 1))
        at-min-x (= x 0)
        at-max-y (= y (- n-rows 1))
        at-min-y (= y 0)]
    (set/intersection
     relevant-directions
     (set
      [(when-not at-max-x :east)
       (when-not at-min-x :west)
       (when-not at-max-y :north)
       (when-not at-min-y :south)]))))

kenj05:03:59

when-not returns nil, such that if relevant-directions also contains nil for whatever reason, your final set would also end up with nil which I’m guessing is not desired behavior

kenj06:03:19

@nate_clojurians If you treat the tests as a sequence, you can get rid of all the ifs/whens

(defn- valid-directions
  "Return a seq of valid directions based on current position"
  [n-cols n-rows [x y]]
  (let [boundries [[:east  (= x (dec n-cols))]
                   [:west  (zero? x)]
                   [:north (= y (dec n-rows))]
                   [:south (zero? y)]]]
    (map first (remove second boundries))))

Nate Sutton06:03:41

it definitely won't contain nil but I do like how your code is setup

Nate Sutton06:03:01

I still need to intersect with relevant directions since depending on the maze algorithm different directions may be relevant

Nate Sutton06:03:29

but I suppose that could be outside this function

mbjarland08:03:38

I have a largish number of instances of the following pattern in my code:

(let [jn (partial str/join \newline)
      ja (fn [& r] (jn r))]
  (jn
   (for [:binding-or-coll-exprs]
     (ja (format "foo")
         (format "bar")
         (format "baz")))))
(rewritten from memory, no guarantees I haven’t missed something) where I essentially want to do a for comprehension which should produce a \newline separated string instead of a lazy sequence (as the normal for does). After enough instances of the above (they are producing a report in text format) this makes me wish for a macro to perform the job, say sfor where you could just do:
(sfor [:binding-or-coll-exprs]
      (format "foo")
      (format "bar")
      (format "baz"))
…`for` is quite complex and I’m a total macro noob…how would I go about writing such a macro and are there any pitfalls or reasons not to?

didibus19:03:50

What's ja for?

didibus19:03:19

It seems like your putting new lines between foo bar and baz, and then another new line between group of these?

didibus19:03:51

I'd say this seems a pretty custom use case. I feel macros are best used for more generic cases.

madstap19:03:28

This does seem a bit specific, but writing the macro is pretty straightforward.

(require '[clojure.string :as str])

(defmacro fors
  {:style/indent 1}
  [bindings & body]
  `(str/join \newline
             (for ~bindings
               (str/join \newline [[email protected]]))))

madstap19:03:39

The style/indent thing is so emacs (and maybe other editors?) knows that it should be indented like for. It's not necessary for it to work.

mbjarland20:03:10

@U0J9LVB6G wow!! {:style/indent 1} seems to work in cursive as well !!! awesome : )

madstap20:03:56

I think I saw somewhere that it didn't work in cursive, he must've added support for it, that's awesome.

mbjarland20:03:03

errr…seems I was mistaken…no support in cursive. That was me playing around with indentation settings

mbjarland21:03:12

and thank you both for the feedback…and apologies for an example which did not reflect the actual problem…the actual code is way more complex than the foo bar example above

didibus21:03:16

I guess the question to ask yourself is if the readability and composability loss from the macro worth the trade off.

didibus21:03:57

There's good cases for macros, and it's hard to judge without knowing the details. But, the common: data over fn over macro applies 😋

didibus21:03:27

For example, it's much easier to stringify a sequence, then it is to sequencify a string

didibus21:03:24

So having sequences until you are absolutely ready to stop processing it is more flexible

didibus21:03:23

So in general. It would be better to have a sequence of sequences. And when you are done, have one function that just generates your string report from it

didibus21:03:25

Most complex views are handled like that.

didibus21:03:36

Think HTML/CSS for example.

didibus21:03:10

One is just an abstract syntax tree. The other defines how that is displayed

didibus21:03:22

So if you had a sequence of sequences. You could render it so that each sequence is separated by new line. But you could also say separate by comma, or comma followed by newline, but no comma in last. Or newline + 4 space indents on every nested sequence. Etc.

didibus21:03:43

You could also choose to render it as rich text, or html, colorize it, etc.

mbjarland09:03:22

I’m assuming this is not doable in a function…

eggsyntax19:03:52

Hey @U4VDXB2TU! At first glance, no, won’t work as a fn with that syntax, since fns evaluate their args (so it’ll try to evaluate :binding-or-coll-exprs). Often the easiest way to write a macro is to start with something that’ll spit out a hardcoded example of what you want, so in your case

(defmacro sfor []
 `(let [jn (partial str/join \newline)
        ja (fn [& r] (jn r))]
    (jn
     (for [:binding-or-coll-exprs]
       (ja (format "foo")
           (format "bar")
           (format "baz"))))))
(note the preceding backtick). Then start adding args and swapping out hardcoded values for the ~-evaluated version of those args. You’ll probably want to spend a bit of time beforehand thinking or reading about how quoting works, and the difference between ~x, ~'x, and '~x, because you may encounter issues where your args are evaluated too much or not enough. That’s all right off the top of my head; take it with a slight grain of salt. Good luck!

eggsyntax19:03:07

Use macroexpand and macroexpand-1 frequently while you’re figuring it out, btw, totally indispensible.

mbjarland20:03:13

thank you. Been playing around with this and ended up with:

(defmacro sfor [seq-exprs & body-exprs]
  `(clojure.string/join
    \newline
    (for ~seq-exprs
      (clojure.string/join
       \newline
       (list [email protected])))))
which seems to do what I want

mbjarland20:03:54

now if I could only get cursive to grok my custom macro instead of thinking it makes my code invalid…

eggsyntax20:03:03

Nice work! Awesome that you managed to come up with that (list [email protected]), that must have taken some head-scratching.

mbjarland20:03:06

thank you. I could not get it to work without explicitly calling (list, not sure, perhaps there is a way but this is as far as I got : )

madstap20:03:06

In the other thread I wrote an implementation that's the exact same except I used a literal vector instead of list. So that works as well. I think it doesn't matter which one. I feel like clojure has that pythonian quality of there being one obvious way to do things, because I see this a lot, different people implementing the same thing the same way.

mbjarland20:03:37

ah did not consider a vector, thanks for the pointer

kari09:03:58

It is sunday and the weather is bad in Helsinki which means it's time to do some Clojure exercises! I'd like to start a new personal Clojure project. Something that I can start using deps.edn and also explore how to use protocols efficiently, and also practice with the Clojure sequence library (as instructed in "Programming Clojure", 3rd ed. page 85 - I now realize that I have used far too much recur instead of figuring out how to do the same thing using the Clojure sequence library). I'd like to use AWS services with Clojure and also experiment how to use Cognitect's AWS library. Any recommendations for the new exercise?

tabidots10:03:26

I don’t have an answer to your question but wanted to ask if you could mention some examples where you used recur but the sequence library would have been better? I feel like I might be missing out

kari10:03:22

"Programming Clojure" 3rd edition says in page 85 in the 2. instruction: "Use recur when you're producing scalar values or small, fixed sequences." I understood that in most cases when not producing scalar values you are able to use the sequence library to do various transformations if you know the library well. This is something that I'd like to focus next - I have a feeling that I have just used recur as a default method to process any transformation that I couldn't find simple map or filter type processing.

kari10:03:47

I don't have any new project ideas in mind. So, I think I'm going to make a second implementation of my Simple Server (https://github.com/karimarttila/clojure/tree/master/clj-ring-cljs-reagent-demo/simple-server) that I originally created for studying how to use Clojure for implementing a web server. This time I'm going to use deps.edn and Cognitects's new AWS library. And I'm also going to review my own code regarding instructions in "Programming Clojure" 3rd edition in page 85 - if I can figure out more ways to use the Clojure sequence library instead of processing things with recur.

didibus19:03:48

Starting with recur is normal. But eventually, you develop a vocabulary for the higher level sequence processing functions, and realize a lot of the recur use cases can be handled by a composition of them instead.

tabidots00:04:40

Can you give an example of some of those functions? I’m just not sure which ones you guys are talking about. Just want to gauge my understanding of the library.

tabidots04:04:10

Or it could just be because I’m mostly doing number theory stuff for the time being that I’m finding loop-recur to be more performant.

tabidots05:04:05

@U0K064KQV ah, yeah, I looked at that page and it left me scratching my head because I am fairly familiar with most of those and have the opposite problem—wouldn’t know how to write them as a loop-recur 😅