Convert a list of this form:
( arg1 arg2 ... :first_keyword val_1 :key2 val_2 ... )
Into this map:
{ 1 arg1, 2 arg2, ..., :first_keyword val_1, :key2 val_2, ... }
I can see very ugly ways to do this. But what is the Clojure way to do this?
.
.
.
(P.S. My early reaction to Clojure is that it feels a little like it is
optimized for more "toy-like" tasks. if the logic connecting your inputs
and outputs has any hair on it, then it feels to me I am spending more
effort fighting the language rather than the problem. specifically,
keeping track of the fifth argument in your 'recurs' statement feels
like I am doing something the compiler should be doing...
but perhaps my Clojure vision is still too weak...)
Here's one solution:
(let [[vals keyvals]
(split-with (complement keyword?)
[100 101 102 :a 103 :b 104 :c 105])]
(merge (zipmap (range) vals)
(apply hash-map keyvals)))
=> {:a 103, :b 104, :c 105, 2 102, 1 101, 0 100}
That makes 0 the key of the first element. If you want 1-based keys, you could wrap (range) in (map inc _).
Other notes:
(split-with (complement keyword?) ...) splits the sequence into two parts: a sequence with no keywords, and the rest.
(zipmap (range) vals) "zips" the two sequences together into a map, using as many integers from range as there are vals.
(loop [inputs ["arg1" "arg2" :key1 1 :key2 2] index 1 output {}]
(if (empty? inputs) output
(let [input (first inputs) rest-inputs (rest inputs)]
(if (keyword? input)
(recur (rest rest-inputs) index (assoc output input (second inputs)))
(recur rest-inputs (inc index) (assoc output index input))))))
You could maybe use a for comprehension or reduce with some pre-processing but a loop may be cleanest in this case.
Related
I need to build a seq of seqs (vec of vecs) by combining first, second, etc elements of the given seqs.
After a quick searching and looking at the cheat sheet. I haven't found one and finished with writing my own:
(defn zip
"From the sequence of sequences return a another sequence of sequenses
where first result sequense consist of first elements of input sequences
second element consist of second elements of input sequenses etc.
Example:
[[:a 0 \\a] [:b 1 \\b] [:c 2 \\c]] => ([:a :b :c] [0 1 2] [\\a \\b \\c])"
[coll]
(let [num-elems (count (first coll))
inits (for [_ (range num-elems)] [])]
(reduce (fn [cols elems] (map-indexed
(fn [idx coll] (conj coll (elems idx))) cols))
inits coll)))
I'm interested if there is a standard method for this?
(apply map vector [[:a 0 \a] [:b 1 \b] [:c 2 \c]])
;; ([:a :b :c] [0 1 2] [\a \b \c])
You can use the variable arity of map to accomplish this.
From the map docstring:
... Returns a lazy sequence consisting of the result of applying f to
the set of first items of each coll, followed by applying f to the set
of second items in each coll, until any one of the colls is exhausted.
Any remaining items in other colls are ignored....
Kyle's solution is a great one and I see no reason why not to use it, but if you want to write such a function from scratch you could write something like the following:
(defn zip
([ret s]
(let [a (map first s)]
(if (every? nil? a)
ret
(recur (conj ret a) (map rest s)))))
([s]
(reverse (zip nil s))))
The first thing I want to say is that I am new to clojure, secondly I want to ask you how to iterate through a map and show all elements in a table? Here is what I have done.
This is my map-
(def kvote(assoc kvote (keyword kljuc){:id id :liga liga :dan dan :cas cas :domaciTim domaciTim :gostujuciTim gostujuciTim :par par :konacanIshod{:1 jedinica :x nerijeseno :2 dvojka}}))
I was using cldwalker table function and did this...
(table [ ["liga" "dan" "cas" "id" "par" "1" "X" "2"] [(:liga(get kvote(keyword :101)))
(:dan(get kvote(keyword :101)))
(:cas(get kvote(keyword :101)))
(:id(get kvote(keyword :101)))
(:par(get kvote(keyword :101)))
(get-in kvote [:101 :konacanIshod :1])
(get-in kvote [:101 :konacanIshod :x])
(get-in kvote [:101 :konacanIshod :2])
]] )
the result is something like this...
+---------+---------+-------+-----+--------------------+-----+-----+---+
| liga | dan | cas | id | par | 1 | X | 2 |
+---------+---------+-------+-----+--------------------+-----+-----+---+
| Serie A | nedelja | 20:00 | 101 | Bologna - Cagliari | 1.5 | 2.3 | 4 |
+---------+---------+-------+-----+--------------------+-----+-----+---+
How can I iterate through the map and show all the elements, not only one by the specific keyword? Can I somehow increment my keyword value and show it like that?
Maps implement the Seq interface, meaning you can use all these useful higher-order functions like map, filter, reduce, ... to process them. The important part here is that the sequential representation of a map consists of [key value] vectors, e.g.:
(seq {:a 0 :b 1})
;; => ([:a 0] [:b 1])
(map (fn [x] (inc (second x))) {:a 0 :b 1})
;; => (1 2)
(If you do not know what map & co. do, read up on them - you will love them (eventually)!)
Now, in your case you're only interested in the values, not the keys, it seems, so vals will retrieve them for you:
(vals {:a 0 :b 1})
;; => (0 1)
(map inc (vals {:a 0 :b 1}))
;; => (1 2)
Your values, however, are maps themselves and you want to access certain keys in said maps and put them into a single vector/list. You can do that!
(map
(fn [x]
[(:key-1 x) (:key-2 x) ...])
(vals your-map-of-maps))
This looks tedious. But creating the inner result is nothing else then looking up each one of a list (!) of keys in a hash map, so another use case for fancy higher-order map:
(map
(fn [x]
(map (fn [k] (k x)) [:key-1 :key-2 ...]))
(vals your-map-of-maps))
Now, actually Clojure makes it really easy to apply different functions (keywords are functions!) to the same value to obtain a list of results - juxt is what it's called, taking a series of functions and producing a new one that does exactly what I just described.
(def inc-and-dec (juxt inc dec))
(inc-and-dec 1)
;; => [2 0]
And here we go for maps:
((juxt :a :b) {:a 0 :b 1 :c 2})
;; => [0 1]
Okay, that's a lot to process but you'll only be able to work efficiently with Clojure if you understand what tools it offers you - and higher-level functions are probably those you'll use the most. Finally, let us create a table:
(table
(cons
["header-1" "header-2" ...]
(map (juxt :key-1 :key-2 ...) (vals your-map-of-maps))))
And now for the grand finale, cleaning up using threading macros!
(->> your-map-of-maps
(map (juxt :key-1 :key-2 ...))
(cons ["header-1" "header-2" ...])
(table))
Yup, there's a lot Clojure can do, and sequences + higher-order-functions are a very powerful combination. And they still solve practical problems like creating a table!
Here is the function I'm trying to run...
(defn mongean [cards times]
(let [_cards (transient cards)]
(loop [i 0 c (get cards i) _count (count cards) _current (/ _count 2)]
(assoc! _cards _current c)
(if ((rem i 2) = 0)
(def _newcur (- _current (inc i)))
(def _newcur (+ _current (inc i))))
(if (<= i _count)
(recur (inc i) (get cards i) _count _newcur )))
(persistent! _cards)))
It's resulting in this Exception...
Exception in thread "main" java.lang.ClassCastException: clojure.lang.PersistentHashSet$TransientHashSet cannot be cast to clojure.lang.ITransientAssociative
Being new to clojure, I'd also appreciate any constructive criticism of my approach above. The goal is to take a List, and return a re-ordered list.
I assume that you are trying to implement the Mongean shuffle. Your approach is very imperative and you should try to use a more functional approach.
This would be a possible implementation, were we calculate the final order of the cards (as per Wikipedia formula) and then we use the built-in replace function to do the mapping:
(defn mongean [cards]
(let [num-cards (count cards)
final-order (concat (reverse (range 1 num-cards 2)) (range 0 num-cards 2))]
(replace cards final-order)))
user> (mongean [1 2 3 4 5 6 7 8])
(8 6 4 2 1 3 5 7)
How do you call that function? It looks like you're passing a set, so that its transient version will also be a set and hence can't be used with any of the assoc functions, as they work on associative data structures and vectors:
user=> (assoc #{} :a 1)
ClassCastException clojure.lang.PersistentHashSet cannot be cast to clojure.lang.Associative clojure.lang.RT.assoc (RT.java:691)
user=> (assoc! (transient #{}) :a 1)
ClassCastException clojure.lang.PersistentHashSet$TransientHashSet cannot be cast to clojure.lang.ITransientAssociative clojure.core/assoc! (core.clj:2959)
; the following works as it uses maps and vectors
user=> (assoc {} :a 1)
{:a 1}
user=> (assoc! (transient {}) :a 1)
#<TransientArrayMap clojure.lang.PersistentArrayMap$TransientArrayMap#65cd1dff>
user=> (assoc [] 0 :a)
[:a]
Now, let's try to discuss the code itself. It's a bit hard to follow your code and try to understand what the goal really is without some more hints on what you want to achieve, but as general comments:
you have a times input parameter you don't use at all
you are supposed to use the result of a transient mutation, not assume that the transient will mutate in place
avoid transients if you can, they're only meant as a performance optimization
the binding _current (/ _count 2) is probably not what you want, as (/ 5 2) really returns 5/2 and it seems that you want to use it as a position in the result
constants like _count don't need to be part of the loop binding, you can use the outer let so that you don't have to pass them at each and every iteration
use let instead of def for naming things inside a function
(if ((rem 1 2) = 0)) is definitely not what you want
Now, leaving aside the shuffling algorithm, if you need to rearrange a sequence you might just produce a sequence of new positions, map them with the original cards to produce pairs of [position card] and finally reduce them by placing the card at the new position, using the original sequence as the seed:
(defn generate [coll] ; counts down from (count coll) to 0, change to
; implement your shuffling algorithm
(range (dec (count coll)) -1 -1))
(defn mongean [cards times]
(let [positions (generate cards) ; get the new positions
assemble (fn [dest [pos card]] ; assoc the card at the wanted position
(assoc dest pos card))]
(reduce assemble cards (map vector positions cards))))
If you simply want to shuffle:
(defn mongean [cards times] (shuffle cards))
I am trying to find a Clojure-idiomatic way to "compress" a vector:
(shift-nils-left [:a :b :c :a nil :d nil])
;=> (true [nil nil :a :b :c :a :d])
(shift-nils-left [nil :a])
;=> (false [nil :a])
(shift-nils-left [:a nil])
;=> (true [nil :a])
(shift-nils-left [:a :b])
;=> (false [:a :b])
In other words, I want to move all of the nil values to the left end of the vector, without changing the length. The boolean indicates whether any shifting occurred. The "outside" structure can be any seq, but the inside result should be a vector.
I suspect that the function will involve filter (on the nil values) and into to add to a vector of nils of the same length as the original, but I'm not sure how to reduce the result back to the original length. I know how to this "long-hand", but I suspect that Clojure will be able to do it in a single line.
I am toying with the idea of writing a Bejeweled player as an exercise to learn Clojure.
Thanks.
I would write it like this:
(ns ...
(:require [clojure.contrib.seq-utils :as seq-utils]))
(defn compress-vec
"Returns a list containing a boolean value indicating whether the
vector was changed, and a vector with all the nils in the given
vector shifted to the beginning."
([v]
(let [shifted (vec (apply concat (seq-utils/separate nil? v)))]
(list (not= v shifted)
shifted))))
Edit: so, the same as what Thomas beat me to posting, but I wouldn't use flatten just in case you end up using some sort of seqable object to represent the jewels.
Maybe this way:
(defn shift-nils-left
"separate nil values"
[s]
(let [s1 (vec (flatten (clojure.contrib.seq/separate nil? s)))]
(list (not (= s s1)) s1)))
A little more low-level approach. It traverses the input seq just once as well as the vector of non-nils once. The two more highlevel approaches traverse the input sequence two times (for nil? and (complenent nil?)). The not= traverses the input a third time in the worst-case of no shift.
(defn compress-vec
[v]
(let [[shift? nils non-nils]
(reduce (fn [[shift? nils non-nils] x]
(if (nil? x)
[(pos? (count non-nils)) (conj nils nil) non-nils]
[shift? nils (conj non-nils x)]))
[false [] []] v)]
[shift? (into nils non-nils)]))
(def v [1 2 nil 4 5 nil 7 8] )
(apply vector (take 8 (concat (filter identity v) (repeat nil))))
This creates a sequence of the non- nil values in the vector using filter and then appends nils to the end of the sequence. This gives the values you want as a sequence and then converts them into a vector. The take 8 ensures that the vector is right size.
Given:
(def my-vec [{:a "foo" :b 10} {:a "bar" :b 13} {:a "baz" :b 7}])
How could iterate over each element to print that element's :a and the sum of all :b's to that point? That is:
"foo" 10
"bar" 23
"baz" 30
I'm trying things like this to no avail:
; Does not work!
(map #(prn (:a %2) %1) (iterate #(+ (:b %2) %1) 0)) my-vec)
This doesn't work because the "iterate" lazy-seq can't refer to the current element in my-vec (as far as I can tell).
TIA! Sean
user> (reduce (fn [total {:keys [a b]}]
(let [total (+ total b)]
(prn a total)
total))
0 my-vec)
"foo" 10
"bar" 23
"baz" 30
30
You could look at this as starting with a sequence of maps, filtering out a sequence of the :a values and a separate sequence of the rolling sum of the :b values and then mapping a function of two arguments onto the two derived sequences.
create sequence of just the :a and :b values with
(map :a my-vec)
(map :b my-vec)
then a function to get the rolling sum:
(defn sums [sum seq]
"produce a seq of the rolling sum"
(if (empty? seq)
sum
(lazy-seq
(cons sum
(recur (+ sum (first seq)) (rest seq))))))
then put them together:
(map #(prn %1 %s) (map :a my-vec) (sums 0 (map :b my-vec)))
This separates the problem of generating the data from processing it. Hopefully this makes life easier.
PS: whats a better way of getting the rolling sum?
Transform it into the summed sequence:
(defn f [start mapvec]
(if (empty? mapvec) '()
(let [[ m & tail ] mapvec]
(cons [(m :a)(+ start (m :b))] (f (+ start (m :b)) tail)))))
Called as:
(f 0 my-vec)
returns:
(["foo" 10] ["bar" 23] ["baz" 30])