The last post was the preamble, now it’s time to get some actual code down. And it turns out I’ve already kicked a hornet’s nest of opinion before I’ve written a line of Clojure. Seems that programmer’s shouldn’t be let near mathematical notation.
Mad Math, Fury Code
Let’s have a look at the scary math bit of The Birchbox Problem again.
Breaking Down The Problem
It’s always easier to break down a problem in to smaller chunks, well I believe so but I could come unstuck but it’s worth a shot. On the surface this looks pretty imposing but hopefully by breaking it down it will become easier. First of all there’s three distinct algorithms at play here. Looking back at the Birchbox post I see that M is a set of products and N is a set of customers.
There’s a few sightings of Sigma notation:
This basically is add all of them together. I wrote about that a short while ago. There is a gotcha with this algorithm though but more of that later.
Max, well that means maximum so there’s something else. The max of B. Okay, so far so good. I’ll tackle the matrix things H() and B() later. First I need some products and some customers to get things started.
Creating Products
Let’s create a vector of maps, each map is a product with an id, name and stock quantity.
(def products [{:id 1 :name "Product1" :stockqty 5} {:id 2 :name "Product2" :stockqty 2} {:id 3 :name "Product3" :stockqty 3} {:id 4 :name "Product4" :stockqty 9} {:id 5 :name "Product5" :stockqty 10} {:id 6 :name "Product6" :stockqty 2} {:id 7 :name "Product7" :stockqty 12} {:id 8 :name "Product8" :stockqty 15} {:id 9 :name "Product9" :stockqty 1} {:id 10 :name "Product10" :stockqty 8}])
Creating Customers
I’m going to do the same for customers but just an id and a customer name….
(def customers [{:id 1 :name "User1"} {:id 2 :name "User2"} {:id 3 :name "User3"} {:id 4 :name "User4"} {:id 5 :name "User5"} {:id 6 :name "User6"} {:id 7 :name "User7"} {:id 8 :name "User8"} {:id 9 :name "User9"} {:id 10 :name "User10"}])
A quick check in the REPL that we can get some values when we put some functions against them:
birchbox.maths.problem> customers ;; => [{:id 1, :name "User1"} {:id 2, :name "User2"} {:id 3, :name "User3"} {:id 4, :name "User4"} {:id 5, :name "User5"} {:id 6, :name "User6"} {:id 7, :name "User7"} {:id 8, :name "User8"} {:id 9, :name "User9"} {:id 10, :name "User10"}] birchbox.maths.problem> products ;; => [{:id 1, :name "Product1", :stockqty 5} {:id 2, :name "Product2", :stockqty 2} {:id 3, :name "Product3", :stockqty 3} {:id 4, :name "Product4", :stockqty 9} {:id 5, :name "Product5", :stockqty 10} {:id 6, :name "Product6", :stockqty 2} {:id 7, :name "Product7", :stockqty 12} {:id 8, :name "Product8", :stockqty 15} {:id 9, :name "Product9", :stockqty 1} {:id 10, :name "Product10", :stockqty 8}] birchbox.maths.problem> (count customers) ;; => 10 birchbox.maths.problem> (map (fn [prod] (:stockqty prod)) products) ;; => (5 2 3 9 10 2 12 15 1 8)
That looks good.
Sigma Functions
We know already that the Sigma function means “the sum”, ie add them up.
So the image above says loop through the numbers 1 to 100 and times the current value of i by 3.
In Clojure this would look like the following:
birchbox.maths.problem> (->> (range 1 101) (map #(* % 3)) (reduce +))
(Though when I originally did this post on Sigma I didn’t know any Clojure, @otfrom stuck that solution in the comments, oh how time flies).
What we have though is this:
Instead of calculating the product of the two sums the first feeds into the index of the second. Think nested for loop, or as I see it (if I’m right) to satisfy this small snippet will be:
(defn sum-products [products customers] (->> products (map (fn [product] [(:stockqty product) (count customers)]))))
When run gives us this:
birchbox.maths.problem> (sum-products products customers) ;; => ([5 10] [2 10] [3 10] [9 10] [10 10] [2 10] [12 10] [15 10] [1 10] [8 10])
Doesn’t feel like much but it’s a starting point. The next job is to put the next parts of the algorithm in. The main thing is, we’ve got started….
….though it’s all totally subject to change.
Calculating Happiness
The function is a bit of a domain knowledge quagmire for us as it’s going to be within the realms of Birchbox how it’s actually calculated. The happiness of a customer based on the product it’s presented with. The result is gathered and calculated from many sources such as customer reviews, star ratings, web site visits to product pages and even social media interaction for example.
This is called an objective function which is:
“ the result of an attempt to express a business goal in mathematical terms for use in decision analysis, operations research or optimization studies“
Which leaves everything a bit open ended but I can create a Clojure function to mock some form of happiness output. A random number will do for the time being while we get things defined. Then we can put some boundary to say anything greater than 75 means the customer is happy with the product.
birchbox.maths.problem> (rand 101) ;; => 69.79507413967906
Becomes…
(defn calc-happiness [] (rand 101))
Binary Assignments
Nice thing with binary it’s going to be 0 or 1. So the binary assignment (B) is basically saying is the product going in to the customer’s box, yes or no. That seems dependent on the result of the happiness rating.
R & D is Fun
I’m going park this part here. I’ve managed to navigate some ideas of what’s going on and got some code down. Whether it’s right or wrong well it’ll take a few more iterations to get a proper handle of what’s been coded is going to be any use or not. Saying that, it’s been fun.
The repo is on github.
References
Solving the Birchbox Problem in Clojure Part 1.