I have an application that is supposed to book flights for customers within their specified budget. As such I have customer data and available flights data. I then develop the solutions in Clojure as follows.
First, I create a flights atom:
(def flights
(atom []))
I then create a function to initialize flights into an atom containing a collection of refs. Here I pass flights data which is included further down this post.
(defn initialize-flights [initial-flights]
(reset! flights (map ref initial-flights)))
I then process customers through the process-customers function as follows. And this is where it gets really confusing.
(defn process-customers [customers]
(doseq [customer1 (partitionCustomerInput N-THREADS customers)]
(doseq [customer2 customer1]
(swap! flights
(fn [flights_collection]
(if-let [updated-flight (process-customer flights_collection customer2)]
(assoc flights (:id updated-flight) updated-flight)
flights_collection)))))
(reset! finished-processing? true))
Inside process-customers I pass flights-collection to process-customer (notice process-customer is a helper function for process-customers and they are not the same function). Flights-collection at this point is a collection of flight refs process-customer is supposed to search through the list and in case a customer qualifies for a flight therein, it uses the book function to edit the flight. How should I pass flights-collection to process-customer? As it is, process-customer does not search through the flight refs and it does not alter the flight refs either?
Below is the process-customer function followed by its helper functions.
(defn- process-customer [flights customer]
"Try to book a flight from `flights` for `customer`, returning the updated
flight if found, or nil if no suitable flight was found."
(if-let [{:keys [flight price]} (find-flight flights customer)]
(let [updated-flight (book flight price (:seats customer))]
(log "Customer" (:id customer) "booked" (:seats customer)
"seats on flight" (:id updated-flight) "at $" price " (< budget of $"
(:budget customer) ").")
updated-flight)
(do
(log "Customer" (:id customer) "did not find a flight.")
nil)))
(defn filter-pricing-with-n-seats [pricing seats]
"Get `pricing` for which there are at least `seats` empty seats available."
(filter #(>= (second %) seats) pricing))
(defn lowest-available-price [flight seats]
"Returns the lowest price in `flight` for which at least `seats` empty seats
are available, or nil if none found."
(-> (:pricing flight) ; [[price available taken]]
(filter-pricing-with-n-seats seats)
(sort-pricing)
(first) ; [price available taken]
(first))) ; price
(defn- find-flight [flights customer]
"Find a flight in `flights` that is on the route and within the budget of
`customer`. If a flight was found, returns {:flight flight :price price},
else returns nil."
(let [{:keys [_id from to seats budget]}
customer
flights-and-prices
; flights that are on the route and within budget, and their price
(for [f flights
:when (and (= (:from f) from) (= (:to f) to))
:let [lowest-price (lowest-available-price f seats)]
:when (and (some? lowest-price) (<= lowest-price budget))]
{:flight f :price lowest-price})
cheapest-flight-and-price
(first (sort-by :price flights-and-prices))]
cheapest-flight-and-price))
(defn- book [flight price seats]
"Updates `flight` to book `seats` at `price`."
(update flight :pricing
(fn [pricing]
(for [[p a t] pricing]
(if (= p price)
[p (- a seats) (+ t seats)]
[p a t])))))
(def finished-processing?
"Set to true once all customers have been processed, so that sales process
can end."
(atom false))
(defn partitionCustomerInput
[threads customers]
(let [partitions (partition-all
(Math/ceil (/ (count customers) threads)) customers)]
partitions))
Below is the main function. It initializes flights and kickstarts customer procecessing
(defn main []
(initialize-flights input/flights)
(let [f1 (future (time (process-customers input/customers)))
#f1
)
(println "Flights:")
(print-flights (map deref #flights)))
(main)
(shutdown-agents)
Below are the customers and flights collection.
(def flights
[{:id 0
:from "BRU" :to "ATL"
:carrier "Delta"
:pricing [[600 150 0] ; price; # seats available at that price; # seats taken at that price
[650 50 0]
[700 50 0]
[800 50 0]]}
{:id 1
:from "BRU" :to "LON"
:carrier "Brussels Airlines"
:pricing [[300 150 0]
[350 50 0]
[370 20 0]
[380 30 0]]}
{:id 2
:from "BRU" :to "LON"
:carrier "Brussels Airlines"
:pricing [[250 100 0]
[300 50 0]]}
{:id 3
:from "BRU" :to "MAD"
:carrier "Brussels Airlines"
:pricing [[200 150 0]
[250 50 0]
[300 100 0]]}
{:id 4
:from "BRU" :to "MAD"
:carrier "Iberia"
:pricing [[250 150 0]
[300 50 0]]}])
(def customers
[{:id 0 :from "BRU" :to "ATL" :seats 5 :budget 700}
{:id 1 :from "BRU" :to "ATL" :seats 5 :budget 550}
{:id 2 :from "BRU" :to "LON" :seats 6 :budget 270}
{:id 3 :from "BRU" :to "ATL" :seats 4 :budget 600}
{:id 4 :from "BRU" :to "LON" :seats 3 :budget 270}
{:id 5 :from "BRU" :to "LON" :seats 9 :budget 250}
{:id 6 :from "BRU" :to "MAD" :seats 5 :budget 200}
{:id 7 :from "BRU" :to "MAD" :seats 9 :budget 150}
{:id 8 :from "BRU" :to "LON" :seats 5 :budget 250}
{:id 9 :from "BRU" :to "ATL" :seats 4 :budget 500}
{:id 10 :from "BRU" :to "MAD" :seats 1 :budget 180}
{:id 11 :from "BRU" :to "LON" :seats 2 :budget 320}
{:id 12 :from "BRU" :to "ATL" :seats 3 :budget 850}
{:id 13 :from "BRU" :to "ATL" :seats 4 :budget 200}])
Also, note that I want to use refs for this implementation to alter the flights as ref offers support for coordinated read and writes to change the flights atomically. I aim to formulate a highly parallelized solution for this application and conflicts cannot be tolerated.
I think you need a ref instead of an atom at the top level. It seems that you will need to coordinate change to individual flight and change to the list of flight. What if one thread is modifying a flight while another thread removes it from the list? Your process-customer side effects must all be done within a (dosync).
Performance wise, it should be ok, because if you don't modify your list-of-flights ref in transaction, it will not cause other transaction that alters it to retry.
Another reason is because you are breaking a very important rule for swap!. The function passed to swap! must be free of side effects as it can be retried by the STM. Altering a ref is side effect, and may cause difficult to understand bugs.
So I would do something like
(def flights
(ref [(ref {:id "flight-1"})
(ref {:id "flight-2"})]))
;; Run a bunch of these in their own threads...
(doseq [customer partitioned-customers]
(dosync (process-customer customer flights)))
Then you can fine tune process-customer with alter, commute and ensure to maximize concurrency and minimize retries.
Hope this helps and good luck!
Related
I have 2 vectors: employ and emp-income. I want to loop thru emp-income based on employ to find what all the missing records. In this case, it's missing id = 2. And i want to create the missing record in emp-income and set the income as the previous record's income value. What is the best way to do it in clojure?
(def employ
[{:id 1 :name "Aaron"}
{:id 2 :name "Ben"}
{:id 3 :name "Carry"}])
from:
(def emp-income
[{:emp-id 1 :income 1000}
{:emp-id 3 :income 2000}])
to:
(def emp-income
[{:emp-id 1 :income 1000}
{:emp-id 2 :income 1000}
{:emp-id 3 :income 2000}])
You could use:
(let [emp-id->income (into {} (map (fn [rec] [(:emp-id rec) rec]) emp-income))]
(reduce (fn [acc {:keys [id]}]
(let [{:keys [income]} (or (get emp-id->income id) (peek acc))]
(conj acc {:emp-id id :income income})))
[]
employ))
Note this will create a record of {:emp-id id :income nil} if the first record is not found in emp-income. It will also use the last :emp-id encountered if duplicate :emp-id values are found within emp-income.
I have a vector of maps like this:
[{:id 2 :val "v1"} {:id 5 :val "v2"} {:id 10 :val "v3"}]
and now I want to find an element previous to chosen id.
For example: when provided with id = 10 i want to receive:
{:id 5 :val "v2"}
and when selected id = 2 then return nil.
I'm new in clojurescript programming and cannot think of simple solution for this problem... Help please :)
You can use partition to pair adjacent maps and then search for a match on the second by id:
(def ms [{:id 2 :val "v1"} {:id 5 :val "v2"} {:id 10 :val "v3"}])
(ffirst (filter #(= 10 (:id (second %))) (partition 2 1 ms)))
(partition 2 1 data) in the accepted answer is an option, but here are two alternatives based on a "lagged" sequence.
This one first constructs the look-up table (mapping next ids to each item), which should be more performant if many lookups needs to be done. You could even easily map over it. But this approach requires ids to be unique.
(let [data [{:id 2 :val "v1"} {:id 5 :val "v2"} {:id 10 :val "v3"}]
ids (zipmap (map :id (rest data)) data)]
[(ids 10)
ids])
; [{:id 5, :val "v2"}
; {5 {:id 2, :val "v1"}, 10 {:id 5, :val "v2"}}]
This second one generates a sequence of matching documents, which is necessary if there might be more than one:
(let [data [{:id 2 :val "v1"} {:id 5 :val "v2"} {:id 10 :val "v3"}]
next-ids (->> data rest (map :id))]
(->>
(map (fn [item next-id] (if (= 10 next-id) item))
data next-ids)
(filter some?)
first))
; {:id 5, :val "v2"}
You'd get similar code by using partition but instead of #(...) you'd use destructuring: (fn [first-item second-item] (= 10 (:id second-item))). Indeed ffirst comes very handy in this approach.
I've got the following tree:
{:start_date "2014-12-07"
:data {
:people [
{:id 1
:projects [{:id 1} {:id 2}]}
{:id 2
:projects [{:id 1} {:id 3}]}
]
}
}
I want to update the people and projects subtrees by adding a :name key-value pair.
Assuming I have these maps to perform the lookup:
(def people {1 "Susan" 2 "John")
(def projects {1 "Foo" 2 "Bar" 3 "Qux")
How could I update the original tree so that I end up with the following?
{:start_date "2014-12-07"
:data {
:people [
{:id 1
:name "Susan"
:projects [{:id 1 :name "Foo"} {:id 2 :name "Bar"}]}
{:id 2
:name "John"
:projects [{:id 1 :name "Foo"} {:id 3 :name "Qux"}]}
]
}
}
I've tried multiple combinations of assoc-in, update-in, get-in and map calls, but haven't been able to figure this out.
I have used letfn to break down the update into easier to understand units.
user> (def tree {:start_date "2014-12-07"
:data {:people [{:id 1
:projects [{:id 1} {:id 2}]}
{:id 2
:projects [{:id 1} {:id 3}]}]}})
#'user/tree
user> (def people {1 "Susan" 2 "John"})
#'user/people
user> (def projects {1 "Foo" 2 "Bar" 3 "Qux"})
#'user/projects
user>
(defn integrate-tree
[tree people projects]
;; letfn is like let, but it creates fn, and allows forward references
(letfn [(update-person [person]
;; -> is the "thread first" macro, the result of each expression
;; becomes the first arg to the next
(-> person
(assoc :name (people (:id person)))
(update-in [:projects] update-projects)))
(update-projects [all-projects]
(mapv
#(assoc % :name (projects (:id %)))
all-projects))]
(update-in tree [:data :people] #(mapv update-person %))))
#'user/integrate-tree
user> (pprint (integrate-tree tree people projects))
{:start_date "2014-12-07",
:data
{:people
[{:projects [{:name "Foo", :id 1} {:name "Bar", :id 2}],
:name "Susan",
:id 1}
{:projects [{:name "Foo", :id 1} {:name "Qux", :id 3}],
:name "John",
:id 2}]}}
nil
Not sure if entirely the best approach:
(defn update-names
[tree people projects]
(reduce
(fn [t [id name]]
(let [person-idx (ffirst (filter #(= (:id (second %)) id)
(map-indexed vector (:people (:data t)))))
temp (assoc-in t [:data :people person-idx :name] name)]
(reduce
(fn [t [id name]]
(let [project-idx (ffirst (filter #(= (:id (second %)) id)
(map-indexed vector (get-in t [:data :people person-idx :projects]))))]
(if project-idx
(assoc-in t [:data :people person-idx :projects project-idx :name] name)
t)))
temp
projects)))
tree
people))
Just call it with your parameters:
(clojure.pprint/pprint (update-names tree people projects))
{:start_date "2014-12-07",
:data
{:people
[{:projects [{:name "Foo", :id 1} {:name "Bar", :id 2}],
:name "Susan",
:id 1}
{:projects [{:name "Foo", :id 1} {:name "Qux", :id 3}],
:name "John",
:id 2}]}}
With nested reduces
Reduce over the people to update corresponding names
For each people, reduce over projects to update corresponding names
The noisesmith solution looks better since doesn't need to find person index or project index for each step.
Naturally you tried to assoc-in or update-in but the problem lies in your tree structure, since the key path to update John name is [:data :people 1 :name], so your assoc-in code would look like:
(assoc-in tree [:data :people 1 :name] "John")
But you need to find John's index in the people vector before you can update it, same things happens with projects inside.
I am trying to use Clojure - Seesaw to read from a file and convert the string into a map (variables) so that I can use them to print to a GUI. Below is my current code:
(ns store.core
(:gen-class)
(:require [seesaw.core :as seesaw]))
(defn -main
[& args]
(seesaw/show!
(spit "amovies.txt" "")
;(spit "amovies.txt" (pr-str [{:id 1 :qty 4 :name "movie1" :price 3.50}
; {:id 2 :qty 5 :name "movie2" :price 3.00}]) :append true)
(spit "amovies.txt" "movie: Movie_Name\nprice: 5\nid: 1\nquantity: 2" :append true)
(print (read-string (slurp "amovies.txt")))
(with-open [rdr (reade "amovies.txt")]
(doseq [line (line-seq rdr)]
(println-str line)))
I am stuck figuring out how to read the string from amovies.txt line by line and then create a map with it. The desired output should be something like
movie: Movie_Name
price: 5
id: 1
quantity: 2
but in a way so that if I were to say, :movie, it would reference the name of the movie.
Can someone help? All help is appreciated!
1
(def movies-as-map
(let [lines (with-open [rdr (reade "amovies.txt")]
(line-seq rdr))]
(binding [*read-eval* false]
(map read-string lines))))
2
user> (def movie
(binding [*read-eval* false]
(read-string
"{:id 1 :qty 4 :name \"movie1\" :price 3.50}")))
#'user/movie
user> (keys movie)
(:id :qty :name :price)
Will work with this
(spit "amovies.txt"
(pr-str [{:id 1 :qty 4 :name "movie1" :price 3.50}
{:id 2 :qty 5 :name "movie2" :price 3.00}])
:append true)
But not with this
(spit "amovies.txt"
"movie: Movie_Name\nprice: 5\nid: 1\nquantity: 2"
:append true)
docs: https://clojure.github.io/clojure/branch-master/clojure.core-api.html#clojure.core/read-string
Wrong args being passed to show!
It looks like your passing a ton of arguments to seesaw/show!.
The documentation states
Usage: (show! targets)
Show a frame, dialog or widget.
If target is a modal dialog, the call will block and show! will
return the dialog's result. See (seesaw.core/return-from-dialog).
Returns its input.
I've got two maps:
(def people {:1 "John" :2 "Paul" :3 "Ringo" :4 "George"})
(def band
{:data
{:members
{:1 {:id 1 :name "John"}
:2 {:id 2 :name "Paul"}}}})
I want to loop over people and add any members that don't exist in [:data :members] to band, resulting in:
(def band
{:data
{:members
{:1 {:id 1 :name "John"}
:2 {:id 2 :name "Paul"}
:3 {:id 3 :name "Ringo"}
:4 {:id 4 :name "George"}}}})
Here's what I've tried:
(for [[id name] people]
(when-not
(contains? (get-in band [:data :members]) id)
(assoc-in band [:data :members id] {:id id :name name})))
Which yields:
({:data
{:members
{:4 {:id :4, :name "George"},
:1 {:name "John", :id 1},
:2 {:name "Paul", :id 2}}}}
nil
nil
{:data
{:members
{:1 {:name "John", :id 1},
:2 {:name "Paul", :id 2},
:3 {:id :3, :name "Ringo"}}}})
I'm not sure why I'm getting back what looks to be a list of each mutation of band. What am I doing wrong here? How can I add the missing members of people to band [:data :members]?
To be pedantic you aren't getting back any mutation of band. In fact, one of the most important features of Clojure is that the standard types are immutible, and the primary collection operations return a modified copy without changing the original.
Also, for in Clojure is not a loop, it is a list comprehension. This is why it always returns a sequence of each step. So instead of altering an input one step at a time, you made a new variation on the input for each step, each derived from the immutable original.
The standard construct for making a series of updated copies of an input based on a sequence of values is reduce, which passes a new version of the accumulator and each element of the list to your function.
Finally, you are misunderstanding the role of :keyword syntax - prefixing an item with a : is not needed in order to construct map keys - just about any clojure value is a valid key for a map, and keywords are just a convenient idiom.
user=> (def band
{:data
{:members
{1 {:id 1 :name "John"}
2 {:id 2 :name "Paul"}}}})
#'user/band
user=> (def people {1 "John" 2 "Paul" 3 "Ringo" 4 "George"})
#'user/people
user=> (pprint
(reduce (fn [band [id name :as person]]
(if-not (contains? (get-in band [:data :members]) id)
(assoc-in band [:data :members id] {:id id :name name})
band))
band
people))
{:data
{:members
{3 {:id 3, :name "Ringo"},
4 {:id 4, :name "George"},
1 {:name "John", :id 1},
2 {:name "Paul", :id 2}}}}
nil
You may notice the body of the fn passed to reduce is essentially the same as the body of your for comprehension. The difference is that instead of when-not which returns nil on the alternate case, I use if-not, which allows us to propagate the accumulator (here called band, same as the input) regardless of whether any new version of it is made.