Split 100% in a given number of columns width random width - clojure

i want to split 100% in a given number of columns. but the width of the columns should be random. here is my first try. but it's static to 5 columns. any ideas to make it dynamic?
(defn create-columns []
(let [col1 (int (random/number 14 26))
col2 (int (random/number 14 26))
col3 (int (random/number 14 26))
col4 (int (random/number 14 26))
col5 (- 100 (+ col1 col2 col3 col4))]
[{:width col1, :left 0}
{:width col2, :left (int col1)}
{:width col3, :left (+ col1 col2)}
{:width col4, :left (+ col1 col2 col3)}
{:width col5, :left (+ col1 col2 col3 col4)}]))
result should be like
[{:width 23, :left 0} {:width 24, :left 23} {:width 23, :left 47} {:width 14, :left 70} {:width 16, :left 84}]
and ideas?

Here is an algorithm expressed as operations on lazy sequences:
Generate random positions separating the columns including bounds to the left and right.
Sort all the positions
Form pairs of every position and the next one
Map the pairs to maps with :left and :width.
Here's the code doing that.
(defn random-cols [total col-count]
(->> #(rand-int (inc total))
(repeatedly (dec col-count))
(into [0 total])
sort
(partition 2 1)
(map (fn [[left right]] {:left left :width (- right left)}))))
(random-cols 100 3)
;; => ({:left 0, :width 21} {:left 21, :width 24} {:left 45, :width 55})
This can generate columns that have a width as small as 0, but constraining the columns to some minimum width is likely something you might want to do although the question does not say anything about that:
(defn random-cols-constrained [total col-count min-width]
(let [widths (map #(+ min-width (:width %))
(random-cols (- total (* col-count min-width))
col-count))]
(map (fn [w l] {:width w :left l})
widths
(reductions + 0 widths))))

Related

What would be the functional / clojure way of transforming a sequence with changing state?

The problem context relates to stock trading. I'm trying to update the holdings for a particular stock, when a sale is made. Simplified excerpt
;; #holdings - an atom
{ "STOCK1" {:trades [Trade#{:id 100 :qty 50}, Trade#{ :id 140 :qty 50}]}
"STOCK2" ... }
Now given a sale trade of Trade{:id 200 :stock "STOCK1", :qty 75}, I'm expecting the holdings to reflect
{ "STOCK1" {:trades [Trade#{:id 100 :qty 0}, Trade#{ :id 140 :qty 25}]} }
;; or better drop the records with zero qty.
{ "STOCK1" {:trades [Trade#{ :id 140 :qty 25}]} }
The functional answer eludes me.. All I can see is a doseq loop with atoms to hold state (like sale-qty which may be satisfied by 1 or n trades) - but it feels like C in Clojure.
Is there a more clojure-aligned solution to this? Map doesnt look like a fit because every record processing needs to update an external state (pending sale-qty 75 -> 25 -> 0)
Disclaimer: Clojure Newbie, who wants to learn.
(require '[com.rpl.specter :as s])
(let [stocks {"STOCK1" {:trades [{:trade/id 100 :trade/qty 50}, {:trade/id 140 :trade/qty 50}]}}
sale-trade {:trade/id 200 :trade/stock "STOCK1" :trade/qty 75}
trade-path [(s/keypath (:trade/stock sale-trade) :trades) s/ALL]
qty-path (conj trade-path :trade/qty)
[new-qty _] (reduce (fn [[new-amounts leftover] v]
(let [due-amount (min v leftover)]
[(conj new-amounts (- v due-amount)) (- leftover due-amount)]))
[[] (:trade/qty sale-trade)]
(s/select qty-path stocks))]
(->> stocks
(s/setval (s/subselect qty-path) new-qty)
(s/setval [trade-path #(zero? (:trade/qty %))] s/NONE)))
=> {"STOCK1" {:trades [#:trade{:id 140, :qty 25}]}}
Whenever you want to go over a sequence/collection in Clojure, while passing some additional state around think of reduce Reduce is like a Swiss army knife, for example map and filter can both be implemented with reduce. But how can you store multiple states in a reducing function? You simply use a map as the accumulator.
Let me distill your problem a bit. Let's create a function that only deals with one problem.
(defn substract-from
"Given a seq of numbers `values`, substract the number `value` from each number
in `values` until whole `value` is substracted. Returns a map with 2 keys, :result contains
a vector of substracted values and :rem holds a remainder."
[values value]
(reduce (fn [{:keys [rem] :as result} n]
(if (zero? rem)
(update result :result conj n)
(let [sub (min rem n)
res (- n sub)
rem (Math/abs (- sub rem))]
(-> result
(update :result conj res)
(assoc :rem rem)))))
{:rem value :result []}
values))
;; when value is smaller than the sum of all values, remainder is 0
(substract-from [100 200 300 400] 500)
;; => {:rem 0, :result [0 0 100 400]}
;; when value is larger than the sum of all values, remainder is > 0
(substract-from [100 200 300 400] 1200)
;; => {:rem 200, :result [0 0 0 0]}
Now we can use this function to sell stocks. Note that map can accept multiple collections/sequences as arguments.
(def stocks
(atom { "STOCK1" {:trades [{:id 100 :qty 50} { :id 140 :qty 50}]}}))
(defn sell [stocks {:keys [id stock qty]}]
(let [trades (get-in stocks [stock :trades])
qtys (map :qty trades)
new-qtys (:result (substract-from qtys qty))]
(map (fn [trade qty]
(assoc trade :qty qty))
trades
new-qtys)))
(sell #stocks {:id 300 :qty 75 :stock "STOCK1"})
;; => ({:id 100, :qty 0} {:id 140, :qty 25})
i would probably start with finding out which part of essential functionality is absent from the core library. In your case it is the function to map over the collection while keeping some changing state.
It could look this way:
(defn map-state [f state data]
(when-let [[x & xs] (seq data)]
(lazy-seq
(let [[new-state new-x] (f state x)]
(cons new-x (map-state f new-state xs))))))
small example of how it could work in context like yours:
(def running-subtract (partial map-state
#(let [qty (min %1 %2)]
[(- %1 qty) (- %2 qty)])))
#'user/running-subtract
user> (running-subtract 10 (range 7))
;;=> (0 0 0 0 0 5 6)
so, you can use it to subtract the state from your trades:
(defn running-decrease-trades [trades amount]
(map-state (fn [amount trade]
(let [sub (min (:qty trade) amount)]
[(- amount sub) (update trade :qty - sub)]))
amount
trades))
and transforming your data with this function would be as easy as the following:
(defn handle-trade [data {:keys [stock qty]}]
(update-in data [stock :trades] running-decrease-trades qty))
user> (handle-trade
{"STOCK1" {:trades [{:id 100, :qty 50} {:id 140, :qty 50}]}}
{:stock "STOCK1" :qty 75})
{"STOCK1" {:trades ({:id 100, :qty 0} {:id 140, :qty 25})}}
Although i like specter very much, i would say it is an overkill for this one.
Unlike imperative programming, where you often modify values in place, in functional programming you instead create new values that contain the modifications. So you will have to create a new version of your map (using update-in) that contains a modified vector with your trades. Something like this:
(def conj-positive-trade ((filter (comp pos? :qty)) conj))
(defn sell [trades sale]
(update-in trades
[(:stock sale) :trades]
#(first
(reduce (fn [[dst remaining] {:keys [qty id]}]
(let [diff (- qty remaining)]
[(conj-positive-trade dst {:id id :qty diff})
(max 0 (- diff))]))
[[] (:qty sale)]
%))))
Here, conj-positive-trade is a function that only conjoins positive trades to a vector.
Here is how to use the sell function:
(sell {"STOCK1" {:trades [{:id 100 :qty 50} {:id 140 :qty 50} {:id 150 :qty 70}]}}
{:id 200 :stock "STOCK1", :qty 75})
;; => {"STOCK1" {:trades [{:id 140, :qty 25} {:id 150, :qty 70}]}}
As an alternative solution that wouldn't use specter (which is great, but requires buy-in). I would keep two atoms, one that is a raw listing of all trades (a vector of maps that you just conj to, so for instance {:trade-id 1 :name "AAPL" :price 100 :qty 20}]), and another that is a map of maps indexed by stock name grouped-result. You'd go from one to the other by group-by or filter so if you added a trade in "AAPL" you can update the quantity as such:
(swap! grouped-result update-in ["AAPL"] (-> #listing (filter #(= (:name %) "AAPL")) (map :qty) (reduce +)))
When it comes to the trade-id you keep it's a bit more complicated as when you factor in PnL there can be FIFO or LIFO considerations - but again you can use reductions or reduced to stop where you want.

Calculate winrate with loop recur

(defn to-percentage [wins total]
(if (= wins 0) 0
(* (/ wins total) 100)))
(defn calc-winrate [matches]
(let [data (r/atom [])]
(loop [wins 0
total 0]
(if (= total (count matches))
#data
(recur (if (= (get (nth matches total) :result) 1)
(inc wins))
(do
(swap! data conj (to-percentage wins total))
(inc total)))))))
(calc-winrate [{:result 0} {:result 1} {:result 0} {:result 1} {:result 1}])
I got the following code, calc-winrate on the last line returns [0 0 50 0 25]. I'm trying to make it return [0 50 33.33333333333333 50 60].
Am I doing the increment for wins wrong? When I print the value of wins for each iteration I get
0
nil
1
nil
1
so I'm guessing I somehow reset or nil wins somehow?
Also, could this whole loop be replaced with map/map-indexed or something? It feels like map would be perfect to use but I need to keep the previous iteration wins/total in mind for each iteration.
Thanks!
Here's a lazy solution using reductions to get a sequence of running win totals, and transducers to 1) join the round numbers with the running totals 2) divide the pairs 3) convert fractions to percentages:
(defn calc-win-rate [results]
(->> results
(map :result)
(reductions +)
(sequence
(comp
(map-indexed (fn [round win-total] [win-total (inc round)]))
(map (partial apply /))
(map #(* 100 %))
(map float)))))
(calc-win-rate [{:result 0} {:result 1} {:result 0} {:result 1} {:result 1}])
=> (0.0 50.0 33.333332 50.0 60.0)
You can calculate the running win rates as follows:
(defn calc-winrate [matches]
(map
(comp float #(* 100 %) /)
(reductions + (map :result matches))
(rest (range))))
For example,
=> (calc-winrate [{:result 0} {:result 1} {:result 0} {:result 1} {:result 1}])
(0.0 50.0 33.333332 50.0 60.0)
The map operates on two sequences:
(reductions + (map :result matches)) - the running total of wins;
(rest (range)))) - (1 2 3 ... ), the corresponding number of matches.
The mapping function, (comp float #(* 100 %) /),
divides the corresponding elements of the sequences,
multiplies it by 100, and
turns it into floating point.
Here's a solution with reduce:
(defn calc-winrate [matches]
(let [total-matches (count matches)]
(->> matches
(map :result)
(reduce (fn [{:keys [wins matches percentage] :as result} match]
(let [wins (+ wins match)
matches (inc matches)]
{:wins wins
:matches matches
:percentage (conj percentage (to-percentage wins matches))}))
{:wins 0
:matches 0
:percentage []}))))
So the thing here is to maintain (and update) the state of the calculation thus far.
We do that in the map that's
{:wins 0
:matches 0
:percentage []}
Wins will contain the wins so far, matches are the number of matches we've analysed, and percentage is the percentage for so far.
(if (= (get (nth matches total) :result) 1)
(inc wins))
your if shall be written as follows:
(if (= (get (nth matches total) :result) 1)
(inc wins)
wins ; missing here , other wise it will return a nil, binding to wins in the loop
)
if you go with a reductions ,
(defn calc-winrate2 [ x y ]
(let [ {total :total r :wins } x
{re :result } y]
(if (pos? re )
{:total (inc total) :wins (inc r)}
{:total (inc total) :wins r}
)
)
)
(reductions calc-winrate2 {:total 0 :wins 0} [ {:result 0} {:result 1} {:result 0} {:result 1} {:result 1}])

Clojure Specter: how to find map keys that have specific value?

Eg in a map:
{"test-1" 23,
"test-2" 456,
"test-3" 23}
How to find keys that have value 23?
I think you don't need specter to do that, just filter by value. I.e:
(->> {:key-1 10
:key-2 20
:key-3 10}
(filter (fn [[k v]] (= v 10)))
(map first))
==> [:key-1 :key-3]
A solution with Specter is:
(keys (specter/setval [specter/MAP-VALS #(not= 10 %)]
specter/NONE
{:key-1 10
:key-2 20
:key-3 10}))
If you want to find something using Specter, it is better to use specter/select.
(use 'com.rpl.specter)
(select [ALL #(= (second %) 23) FIRST]
{"test-1" 23,
"test-2" 456,
"test-3" 23})

group-by with reduce in clojure

I want to aggregate large dataset to get something like
SELECT SUM(`profit`) as `profit`, `month` FROM `t` GROUP BY `month`
So, i modified clojure's group-by function like so
(defn group-reduce [f red coll]
(persistent!
(reduce
(fn [ret x]
(let [k (f x)]
(assoc! ret k (red (get ret k) x))))
(transient {}) coll)))
And here is usage:
(group-reduce :month (fn [s x]
(if s
(assoc s :profit (+ (:profit s) (:profit x)))
x))
[{:month 10 :profit 12}
{:month 10 :profit 15}
{:month 12 :profit 1}])
#_=> {10 {:profit 27, :month 10}, 12 {:profit 1, :month 12}}
It works, but maybe there is another way to do this, using clojure standard library?
Closest in the core is merge-with:
(def t [{:month 10 :profit 12}
{:month 10 :profit 15}
{:month 12 :profit 1}])
(apply merge-with + (for [x t] {(:month x) (:profit x)}))
;=> {12 1, 10 27}
Some examples:
user=> (def groups (group-by :month [{:month 10 :profit 12}
#_=> {:month 10 :profit 15}
#_=> {:month 12 :profit 1}])
{10 [{:profit 12, :month 10} {:profit 15, :month 10}], 12 [{:profit 1, :month 12}]}
user=> (for [[k v] groups] {:month k :sum-profit (apply + (map :profit v))})
({:month 10, :sum-profit 27} {:month 12, :sum-profit 1})
user=> (into {} (for [[k v] groups] [k (apply + (map :profit v))]))
{10 27, 12 1}

How To Capture "Row" Number

My question is how can I capture the index of the vector row where a match occurred? In the code below, what am I doing wrong?
I have a vector of vectors
(def v1 [[44 2 3 4 5][1 6 7 5 10][11 12 13 14 15]])
a column index, and a comparison value
(def cmp-val1 11)
(def col-idx 0)
I want to return the row index where a comparison returned true. With col-idx = 0 and cmp-val1 = 11, I should see (first row-num) return 2, and it is returning 1.
(defn ret-match-row
"Return the index of the row, in which the cmp-val is found.
It is okay to increment 0."
[in-seq cmp-val col-idx]
(let [rn 0]
(let [row-num
(for [seq-row in-seq
:let [local-row-num (inc rn)]
:when (= cmp-val (nth seq-row col-idx nil))]
local-row-num)]
(first row-num))))
From lein repl:
bene-csv.core=> (ret-match-row v1 cmp-val1 col-idx)
1
=> (defn ret-match-row
[coll cmp idx]
(keep-indexed (fn [i v] (if (= cmp (get v idx)) i)) coll))
=> (ret-match-row v1 11 0)
(2)
A flexible answer comes from separating this into three distinct problems and composing them.
creating the data you seek
finding the data you want
presenting the way it should look.
first we number the rows by adding row numbers to them
(map vector v1 (range))
then filter out the rows not containing the number you want:
(filter (fn [[data index]] (some #{11} data)) (map vector v1 (range)))
> ([[11 12 13 14 15] 2])
here i used the trick that sets are functions that test their input for inclusion in the set which allows this to test for multiple values:
(filter (fn [[data index]] (some #{11 44} data)) (map vector v1 (range)))
> ([[44 2 3 4 5] 0] [[11 12 13 14 15] 2])
then since you only want to know where it matched and not what matched we filter that out:
(map second (filter (fn [[data index]] (some #{11 44} data)) (map vector v1 (range))))
> (0 2)
to wrap this into a nice function we write out the steps:
(defn with-row-numbers [col] (map vector col (range)))
(defn find-my-rows [rows goals]
(filter (fn [[data index]] (some (set goals) data)) rows))
(defn present-rows [rows] (map second rows))
and then compose them:
(defn ret-match-row [data rows]
(-> data
(with-row-numbers)
(find-my-rows rows)
(present-rows)))
(ret-match-row v1 [11])
(2)
sorry i couldn't help making it work with multiple values, its a habit.
(ret-match-row v1 [11 15 44])
> (0 2)
There may be other ways to do what your'e asking, but you can use a loop/recur to achieve the iteration you're after:
(defn ret-match-row [rows val col-idx]
(loop [[row & rows] rows
pos 0]
(cond
(not row)
nil
(= val (nth row col-idx))
pos
:not-found
(recur rows (inc pos)))))
(ret-match-row [[44 2 3 4 5]
[1 6 7 8 10]
[11 12 13 14 15]]
11
0)
;; => 2
You're also running into Clojure's immutability - the (inc rn) is not actually modifying rn. The loop / recur solution uses inc as well, but it passes the result of inc to the next iteration of the loop.
Clojure's for (list comprehension) form also will loop over all of the values in the sequence, resulting in a new sequence -- which is most likely not what you want. Even if you made the for loop do what you want, it will find all the matches, not just the first. The loop / recur example stops at the first match.
My take, using
clojure.contrib.seq find-first, indexed:
(defn ret-match-row [rows val col-idx]
(first
(find-first #(= val (nth (second %) col-idx))
(indexed rows))))