Moving partition-by's splits "back by one" - clojure

I'm parsing some Hiccup in CLJS, with the goal of taking :h2 and :h3 elements and converting them to a tree of nested :ul and :li.
My starting point is a flat vector like:
[[:h2 {} "Foo"] [:h2 {} "Bar"] [:h3 {} "Child1"] [:h2 {} "Baz"]]
If I just map over these and replace (first el) with [:li], I have a flat list. But I'd like to get something like:
[[:li "Foo"] [:li "Bar"] [:ul [:li "Child1"]] [:li "Baz"]]
If I call (partition-by #(= :h2 (first %)) my-vec), I get something almost useful:
(([:h2 {} "Foo"] [:h2 {} "Bar"]) ([:h3 {} "Child1"]) ([:h2 {} "Baz"]))
The partition happens when the predicate #(= :h2 (first %)) changes, (which is what the documentation says it does).
How can I get the behavior I'm looking for?

Here is one way to do it:
(def data [
[:h2 {} "Foo"]
[:h2 {} "Bar"]
[:h3 {} "Child1"]
[:h2 {} "Baz"] ] )
(defn formatter [elem]
(condp = (first elem)
:h2 [:li (last elem)]
:h3 [:ul [:li (last elem)]]
))
(newline) (println :data data)
(newline) (println :result (mapv formatter data))
with result
:data [[:h2 {} Foo] [:h2 {} Bar] [:h3 {} Child1] [:h2 {} Baz]]
:result [[:li Foo] [:li Bar] [:ul [:li Child1]] [:li Baz]]
Update:
Rewrite like so to get all the :h3 items in one :ul
(def data [
[:h2 {} "Foo"]
[:h3 {} "Child1"]
[:h2 {} "Bar"]
[:h3 {} "Child2"]
[:h3 {} "Child3"]
[:h2 {} "Baz"] ] )
(defn h2? [elem]
(= :h2 (first elem)))
(defn ->li [elem]
[:li (last elem)])
(defn fmt [data]
(let [h2 (filter h2? data)
h3 (filter #(not (h2? %)) data)
result (conj (mapv ->li h2)
(apply vector :ul (mapv ->li h3))) ]
result ))
(newline) (println :data data)
(newline) (println :result (fmt data))
with result
:data [[:h2 {} Foo] [:h3 {} Child1] [:h2 {} Bar] [:h3 {} Child2] [:h3 {} Child3] [:h2 {} Baz]]
:result [[:li Foo] [:li Bar] [:li Baz] [:ul [:li Child1] [:li Child2] [:li Child3]]]

Here's an answer that does the job, but is horribly inelegant, since it essentially mutates the last element in the reduce call when necessary:
(defn listify-element [element]
"Replaces element type with :li."
(vec (concat [:li (last element))]))
(defn listify-headings [headings-list]
"Takes subitems (in :h2 :h3) and creates sub :uls out of the :h3 lists."
(vec
(concat
[:ul]
(map-indexed
(fn [ind headings]
(if (= 0 (mod ind 2))
(map listify-element headings)
(vec (concat [:ul] (map listify-element headings)))))
(partition-by #(= :h2 (first %)) headings-list)))))
(defn nest-listified-headings [vector-list]
"Nests sub-:uls inside their preceding :lis."
(vec (concat [:ul]
(reduce
(fn [acc el] (if (= (first el) :ul)
(conj (pop (vec acc)) (conj (last acc) el))
(concat acc el)))
vector-list))))
Produces:
(nest-listified-headings
(listify-headings [[:h2 "Foo"] [:h2 "Bar"] [:h3 "Baz"] [:h3 "Bat"]])
[:ul [:li "Foo"]
[:li "Bar"
[:ul
[:li "Baz"]
[:li "Bat"]]]]

Related

Proper nesting of Clojure Specs?

I think I may have a problem with the correct order of nesting Specs within a function - specifically s/with-gen and s/or...
I have this function and Spec:
(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
(if (nil? g-hist)
[{:giver :none, :givee :none}]
(assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
:args (s/with-gen
(s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
#(gen/let [hist (s/gen :unq/gift-history)
year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
pair (s/gen :unq/gift-pair)]
[hist year pair]))
:ret :unq/gift-history)
Which tests correctly:
(stest/check `set-gift-pair-in-gift-history)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
0x729d93b6
"clojure.spec.alpha$fspec_impl$reify__2451#729d93b6"],
:clojure.spec.test.check/ret {:result true,
:num-tests 1000,
:seed 1531413555637},
:sym clojure-redpoint.roster/set-gift-pair-in-gift-history})
And its parameters conform correctly:
(s/conform (s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
[[{:giver :GeoHar, :givee :JohLen}] 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-hist
{:g-hist [{:giver :GeoHar, :givee :JohLen}],
:g-year 0,
:g-pair {:giver :RinStaXX, :givee :PauMccXX}}]
(s/conform (s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
[nil 0 {:giver :RinStaXX, :givee :PauMccXX}])
=>
[:input-nil
{:g-hist nil, :g-year 0, :g-pair {:giver :RinStaXX, :givee :PauMccXX}}]
But when this "correct" function is consumed by a second function:
(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
(let [plr (get-player-in-roster plrs-map plr-sym)
gh (get-gift-history-in-player plr)
ngh (set-gift-pair-in-gift-history gh g-year g-pair)
nplr (set-gift-history-in-player ngh plr)]
(assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
:args (s/cat :plrs-map ::plr-map
:plr-sym keyword?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
:ret ::plr-map)
The consumed function becomes a source of error for the consuming function (the nil case - which I thought was handled):
(stest/check `set-gift-pair-in-roster)
=>
({:spec #object[clojure.spec.alpha$fspec_impl$reify__2451
0x3bbc704a
"clojure.spec.alpha$fspec_impl$reify__2451#3bbc704a"],
:clojure.spec.test.check/ret {:result #error{:cause "Call to #'clojure-redpoint.roster/set-gift-pair-in-gift-history did not conform to spec:
In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
val: {:g-hist nil, :g-year 1, :g-pair {:givee :_+, :giver :RJK/Y24}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))
I have tried changing the order and grouping (nesting) of the specs in the consumed function - but then it fails the tests that it used to pass, before even getting to testing the consuming function.
Any thoughts on what is going wrong here?
Thank you!
EDIT:
As suggested, here is the full code for a better understanding:
;Here is an example of The Beatles keeping track of the Xmas gifts
;they give to each other (:giver and :givee) each year over time:
(ns clojure-redpoint.roster2
(:require [clojure.spec.alpha :as s]
[orchestra.spec.test :as st]
[clojure.test.check.generators :as gen]
[clojure.spec.test.alpha :as stest]))
(s/def ::givee keyword?)
(s/def ::giver keyword?)
(s/def :unq/gift-pair (s/keys :req-un [::givee ::giver]))
(s/def ::name string?)
(s/def :unq/gift-history (s/coll-of :unq/gift-pair :kind vector?))
(s/def :unq/player (s/keys :req-un [::name :unq/gift-history]))
(s/def ::plr-map (s/map-of keyword? :unq/player))
(defn- get-player-in-roster [plrs-map plr-sym]
(get plrs-map plr-sym))
(s/fdef get-player-in-roster
:args (s/cat :plrs-map ::plr-map :plr-sym keyword?)
:ret (s/or :found :unq/player
:not-found nil?))
(defn- get-gift-history-in-player [plr]
(get plr :gift-history))
(s/fdef get-gift-history-in-player
:args (s/or :input-plr (s/cat :plr :unq/player)
:input-nil (s/cat :plr nil?))
:ret (s/or :found :unq/gift-history
:not-found nil?))
(defn set-gift-pair-in-gift-history [g-hist g-year g-pair]
(if (nil? g-hist)
[{:giver :none, :givee :none}]
(assoc g-hist g-year g-pair)))
(s/fdef set-gift-pair-in-gift-history
:args (s/with-gen
(s/or :input-hist (s/and
(s/cat :g-hist :unq/gift-history
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %))))
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
#(gen/let [hist (s/gen :unq/gift-history)
year (gen/large-integer* {:min 0 :max (max 0 (dec (count hist)))})
pair (s/gen :unq/gift-pair)]
[hist year pair]))
:ret :unq/gift-history)
(defn set-gift-history-in-player [g-hist plr]
(if (or (nil? g-hist) (nil? plr))
{:name "none", :gift-history [{:giver :none, :givee :none}]}
(assoc plr :gift-history g-hist)))
(s/fdef set-gift-history-in-player
:args (s/or :input-good (s/cat :g-hist :unq/gift-history
:plr :unq/player)
:input-hist-nil (s/cat :g-hist nil?
:plr :unq/player)
:input-plr-nil (s/cat :g-hist :unq/gift-history
:plr nil?)
:input-both-nil (s/cat :g-hist nil?
:plr nil?))
:ret :unq/player)
(defn set-gift-pair-in-roster [plrs-map plr-sym g-year g-pair]
(let [plr (get-player-in-roster plrs-map plr-sym)
gh (get-gift-history-in-player plr)
ngh (set-gift-pair-in-gift-history gh g-year g-pair)
nplr (set-gift-history-in-player ngh plr)]
(assoc plrs-map plr-sym nplr)))
(s/fdef set-gift-pair-in-roster
:args (s/cat :plrs-map ::plr-map
:plr-sym keyword?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
:ret ::plr-map)
(st/instrument)
(def roster-map
{:RinSta {:name "Ringo Starr", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
:JohLen {:name "John Lennon", :gift-history [{:giver :JohLen, :givee :GeoHar}]},
:GeoHar {:name "George Harrison", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
:PauMcc {:name "Paul McCartney", :gift-history [{:giver :PauMcc, :givee :RinSta}]}})
(s/conform ::plr-map
(set-gift-pair-in-roster roster-map :PauMcc 0 {:giver :JohLenXXX, :givee :GeoHarXXX}))
;=>
;{:RinSta {:name "Ringo Starr", :gift-history [{:giver :GeoHar, :givee :JohLen}]},
; :JohLen {:name "John Lennon", :gift-history [{:giver :RinSta, :givee :PauMcc}]},
; :GeoHar {:name "George Harrison",
; :gift-history [{:giver :PauMcc, :givee :RinSta}]},
; :PauMcc {:name "Paul McCartney",
; :gift-history [{:giver :JohLenXXX, :givee :GeoHarXXX}]}}
;(stest/check `set-gift-pair-in-roster)
Unfortunately, it did not help me find my error...
The problem is that one of your instrumented functions set-gift-pair-in-gift-history is being called with invalid arguments when you (stest/check `set-gift-pair-in-roster):
CompilerException clojure.lang.ExceptionInfo: Call to #'playground.so/set-gift-pair-in-gift-history did not conform to spec:
In: [0] val: nil fails spec: :unq/gift-history at: [:args :input-hist :g-hist] predicate: vector?
val: {:g-hist nil, :g-year 1, :g-pair {:givee :A, :giver :A}} fails at: [:args :input-nil] predicate: (<= (:g-year %) (count (:g-hist %)))
The check output gives us a minimal input to reproduce the error:
(set-gift-pair-in-roster {} :A 1 {:givee :A, :giver :A})
We can see the first argument to the failing function is nil. Looking at set-gift-pair-in-gift-history's function spec, there's a suspect spec that covers that case:
:input-nil (s/and
(s/cat :g-hist nil?
:g-year (s/and int? #(> % -1))
:g-pair :unq/gift-pair)
#(<= (:g-year %) (count (:g-hist %)))))
This will only conform when g-hist is nil and g-year is 0, but the generator for :g-year is going to generate many numbers besides 0. That's why the calls to it fail when it's instrumented.
instrument and check have shown a discrepancy between how the program is specified to behave and how it actually behaves. I'd start by thinking about how set-gift-pair-in-gift-history should be spec'd when its first argument is nil. The implementation doesn't care about the other arguments when the first arg is nil, so you could adjust the function spec to reflect that:
:input-nil (s/cat :g-hist nil? :g-year any? :g-pair any?)
With that change, your top-level function should check successfully.

Accessing elements of a Clojure map in a vector of maps

I have:
(def moo (my-func))
which returns:
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}]
How do I now access moo to get the :name where :id=3? Thanks.
I would rather prefer using some (since it is more logical than using filter i guess, because it is designed to find exactly one value):
(def data
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
(defn name-by-id [id data]
(some #(when (= (:id %) id) (:name %)) data))
user>
(name-by-id 3 data)
"Greg"
user>
(name-by-id 100 data)
nil
One way would be to use a filter
(def moos
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
(defn name-for-id
[id]
(:name (first (filter #(= (:id %) id) moos))))
(name-for-id 3) ; => "Greg"
(def names
[{:id 1 :name "Bob"}
{:id 2 :name "Jane"}
{:id 3 :name "Greg"}])
;;get the :name where :id=3
(defn answer []
(:name (first (filter (fn [e] (= 3 (:id e))) names))))
In the above rather than names you could have moo.

Clojure: a function that removes a given node from a collection

I'd like to have a function that removes any node (sub-collection) from a collection containing that node.
(def coll {:a ["b" {:c "d" :e ["f" {:g "h"}]}]})
(def node {:g "h"})
What would be a good remove-node function?
(remove-node coll node)
;=> {:a ["b" {:c "d" :e ["f"]}]})
Thanks!
EDIT :
What I want to do is delete an enlive-node
(def enlive-node
[{:type :dtd, :data ["html" nil nil]}
{:tag :html,
:attrs nil,
:content ["\n"
{:tag :head,
:attrs nil,
:content ["\n \n "
{:tag :title,
:attrs nil,
:content ["Stack Overflow"]}
"\n "
{:tag :link,
:attrs {:href "//cdn.sstatic.net/stackoverflow/img/favicon.ico",
:rel "shortcut icon"},
:content nil}]}]}])
The node to remove is always a string or an entire hash-map.
(remove-node enlive-node {:tag :title,
:attrs nil,
:content ["Stack Overflow"]})
For the example you have, you can use clojure.walk/postwalk to walk the hashmap and remove the node.
(require '[clojure.walk :as walk])
(defn remove-node [coll target]
(walk/postwalk
(fn [item]
(if (vector? item)
(filterv #(not= target %) item)
item))
coll))
(remove-node coll node)
EDIT:
From you updated question, it looks like you are operating on an Enlive node collection. An additional solution, in your case, would be to generate an Enlive node selector and transform the collection using the net.cgrand.enlive-html/at* function.
(require '[net.cgrand.enlive-html :as e])
(defn gen-transform [target]
[[(cond
(string? target) e/text-node
(map? target) (:tag target)
:else e/any-node)]
#(when (not= target %) %)])
(defn remove-node [coll & nodes]
(e/at* coll (map gen-transform nodes)))
(remove-node enlive-node
{:tag :title, :attrs nil, :content ["Stack Overflow"]}
"\n")

Basic Clojure: how do I flatten a nested list?

Please look at the following code:
(def data {:color ["R", "B", "G"] :name "Hello" :up "down"})
(defn collapse-vector-kvp [k v]
(map #(hash-map k %) v))
(defn collapse-map [m]
(map #(let
[x %]
(if (vector? (val x))
(collapse-vector-kvp (key x) (val x))
(hash-map (key x) (val x))
)) m))
(collapse-map data)
=> ({:name "Hello"} ({:color "R"} {:color "B"} {:color "G"}) {:up "down"})
What I would like to do is create a single list, rather than have the 'color' entries be in a list within the list. Is this easily achievable?
user=> (def data2 '({:name "Hello"} ({:color "R"} {:color "B"} {:color "G"}) {:up "down"}))
#'user/data2
user=> (flatten data2)
({:name "Hello"} {:color "R"} {:color "B"} {:color "G"} {:up "down"})
Another version of collapse-map:
(defn collapse-map [m]
(let [sep-m (group-by (comp vector? val) m)]
(concat (map (fn [[k v]] {k v})
(sep-m false))
(apply concat (map (fn [[k v]]
(collapse-vector-kvp k v))
(sep-m true))))))
(def test-data {:color ["R" "B" "G"]
:name "Hello"
:k ["v1" "v2" "v3"]
:up "down"})
(collapse-map test-data)
=> ({:name "Hello"}
{:up "down"}
{:color "R"}
{:color "B"}
{:color "G"}
{:k "v1"}
{:k "v2"}
{:k "v3"})

Filter a map with complex nested structure

What would be a best way to impose a condition on the nested fields of complex nested structure like...
{
:aa {:a "a_val",:b "b_val"},
:qq {:abc
{
:x1 {:x "abc",:u "ee"},
:x2 {:y "abc",:i "ee"},
:x3 {:x "abc",:i "ee"}
}
},
:ww {:xyz {
:y1 {:x "abc",:u "ee"},
:y2 {:y "abc",:i "0"},
:y3 {:x "abc",:i "ee"}
}
}
}
I want to check whether the "i" part exist and has value "0" in each of aa,qq and ww and depending upon that exclude(or perform any operation) on aa,qq and ww. For example if "ww" has "i"="0" at that position then get a map like below
{
:ww {:xyz {
:y1 {:x "abc",:u "ee"},
:y2 {:y "abc",:i "0"},
:y3 {:x "abc",:i "ee"}
}
}
}
user> (defn vvals [m] (when (map? m) (vals m)))
'user/vvals
user> (filter #(some #{"0"} (for [v (vvals (val %)), v (vvals v)] (:i v))) xx)
([:ww {:xyz {:y3 {:x "abc", :i "ee"}, :y2 {:y "abc", :i "0"}, :y1 {:x "abc", :u "ee"}}}])