Growing Delicious Mung Bean Microgreens – Adventures In Indoor Farming

In my last post I showed you how Ikea’s hydroponics system works. I’m a great fan and it has allowed me to add cut-fresh herbs like basil, water-cress and baby-leaf salad to my day-to-day cooking.

It’s been absolutely great. The plants keep on growing even when I cut off a few leaves every day and all I have to do is add growth-solution and water once a week.

Now, from hydroponics – that’s plants growing in soil-less medium using light, water and growth-solution – I discovered the world of growing Microgreens.

I bought a bare-minimum set of lights, trays, seeds and soil and less than a week later I’m already eating mung-bean microgreens by the spoonful with practically every meal. They’re delicious!

What are Microgreens?

Here’s what Wikipedia has to say:

A microgreen is a young vegetable green that is used both as a visual and flavor component or ingredient primarily in fine dining restaurants. Fine dining chefs use microgreens to enhance the attractiveness and taste of their dishes with their delicate textures and distinctive flavors. Smaller than “baby greens,” and harvested later than sprouts, microgreens can provide a variety of leaf flavors, such as sweet and spicy. They are also known for their various colors and textures. Among upscale markets, they are now considered a specialty genre of greens that are good for garnishing salads, soups, plates, and sandwiches.

Edible young greens and grains are produced from various kinds of vegetables, herbs or other plants. They range in size from 1 to 3 inches (2.5 to 7.6 cm), including the stem and leaves. A microgreen has a single central stem which has been cut just above the soil line during harvesting. It has fully developed cotyledon leaves and usually has one pair of very small, partially developed true leaves. The average crop-time for most microgreens is 10–14 days from seeding to harvest.

So, they’re not sprouts and not baby greens, and as far as I know, there isn’t even a German word for it yet.

I easily get excited about this kind of stuff so I had to try it out.

My Setup

The basic setup that I bought from a grow-shop here in Berlin included:

  • 2 Strips of SanLight Flex20 for 80 Euro each
  • 1 power-adapter for 20 Euro
  • 3 plastic trays for 10 Euro each
  • 10 Liters of soil for 5 Euro
  • 1 meter of chain for 4 Euro
  • 4 square meters of plastic tarp for light control for 4 Euro
The basic components of my microgreens setup

The Growing Process Of 1 Week

This slideshow requires JavaScript.

A few things I’ve learned so far

  • It took less than a week for the beans to grow into harvestable, edible and delicious microgreens.
  • Stacking the trays for germination worked really well. I assume the pressure, darkness and moisture kick-starts the seed’s lifecycle rapidly.
  • Mung bean microgreens taste light and fresh and slightly buttery.
  • Supermarket-bought beans are completely ready to germinate and grow. The mung beans I got from a local store at 2 Euro per Kilo. I used most of that across the trays.
  • I had to water the trays twice in one week.
  • It was far easier than I expected to grow my own microgreens. The process is relatively hands-off, once you’ve gotten your seeds to sprout.

What’s Next?

I’ve read up on the most flavorful microgreens and learned that sunflower and pea greens are especially yummy.

So, the next round will be those: Sunflowers and peas.

Stay tuned, stay healthy!

Alexis

Growing Yummy, Hyper-Local Herbs – Adventures In Hydroponics

It’s been a month since I started my own little herb garden at home using Ikea’s Vaxer system.

The short of it is:

It works. Really well.

The whole setup cost me less than $120 and I’ve germinated, sprouted, grown and harvested salad, water-cress and basil in just a few weeks.

I’m a fan and wanted to show you the progress:

This slideshow requires JavaScript.

I’m so excited to have more, fresh and tasty greens at my fingertips that I’m going to expand my Ikea system with a custom-made lighting and tray system that I’ll write about in the future.

I strongly believe that growing your own food while living in a big city is a good idea.

Next up: Growing pea, sunflower and hemp microgreens!

Thanks for reading,

Alexis

Tracking Meetup Growth Rates in Berlin

I’ve meant to build this for months but never got around to it until this weekend.

Basically, out of sheer curiosity, I want to know the fastest-growing Meetups in Berlin. I want to keep a finger on the pulse of this city. For that I find that Meetup memberships are one clear, physical indicator of what people are subscribing to. And what they consider worth their time to eventually meet up for.

So, anyways, I came up with a system that scrapes Berlin’s Meetups every day and calculates the member growth-rate in percent. It allows me to look at the daily report via a simple web UI:

First, the fastest growing Meetups per day…

The Fastest Growing Meetups Over the Last 5 Days

…and yesterday’s Meetups compared and sorted:

Example Two-Day Report

I also added a simple page for each Meetup that summarizes its growth-journey:

Overview of a single Meetup

A few things I learned

The Meetup API gives me varying responses

The Meetup API responses vary in their results – probably because I’m doing something wrong.

Basically, for Berlin, I have to page through 26 pages of results with 100 Meetups each. That’s about 2,660 Meetups in Berlin.

When I run my job once a day, sometimes I only get 1 page, sometimes 10, sometimes 14 and very rarely the full 26 pages. The API just eventually responds with an empty array on which I finish. I always get 200 OK responses, too, which makes it harder to understand why the variation occurs.

To better understand the issue, I’ve created a monitoring table. If my job isn’t able to fetch the latest data for all 2,660+ Meetups I already know exist, the job is marked as “Incomplete”.

As you can see, two days ago, I fetched all Meetups while the next time I ran the job only 1,400 (14 pages with 100 records each) were returned until the API stopped giving data.

Fetching Data Report with Incomplete Jobs

I looked up their terms for fair use and experimented with 5- and 10-second delays between each HTTP request but the responses are still unpredictable.

So, yeah, this probably is all based on how I use their API but between the rate limit documentation which – to my knowledge I’m not over-using – I can’t figure out what the problem is.

Recently created Meetups have a natural growth-burst in their first days

I’ve only had data for the last 4 days but usually the fastest-growing ones were less than a month old. That totally makes sense, the organizer is excited, promotes the Meetup etc.

Top Growing Meetups over 2 days are less than a month old

To counter-point this, I’m looking forward to having more than a week’s worth of data, then a month to compare.

I need more data; and data costs

The value of this is directly related to how much data I can report on. It’ll take at least a week for me to really start using it for real. I think a report on the fastest-growing Meetup in the last month will even be more valuable.

Speaking of data, I quickly exceeded 10,000 rows of the free hobby database on Heroku. I added a $9/mo. mini database on the third day.

Each day, I’m adding ~2,600 Member Count records plus ~2,600 Reporting Cube records to load the UI fast. I guess, that’s 5,200 records per day in total or 156,000 records per month. Nothing crazy but just interesting.

Anyways, just a few thoughts I wanted to share!

Let me know what you think.

Alexis

How To Make Better, Faster Decisions With Lean Experiments

As many of you know by now, I strongly believe in the effectiveness of using the scientific method to validate business ideas.

Earlier this year, I began running a bunch of experiments for my ideas and ventures. But, quickly, my experiments became too much to handle on a white-board with stickies. I switched to a Google Sheet but that didn’t have the usability and speed that I loved from using stickies.

So I decided to build a simple experiment tracking system for myself.

Keeping Track of Ventures, Experiments and Costs

I would say it’s pretty basic and covers exactly what I need to keep track of:

  • My Ventures which are a sequential list of experiments and their Pivot-or-Persevere decision. Ventures can also be aborted if there is no encouraging signal.
  • Experiments, which comprise of:
    • Theory
    • Riskiest Assumption
    • Method
    • Prediction
    • Outcome
    • Decision
    • Cost
    • Learnings
  • Cost tracking
    • Per Experiment
    • Per Venture
    • Overall

Types Of Experiments:

I templated the main three experiment types that I usually run:

  • Market Experiment – Usually an overview how big the market is, how much traffic there is, how the market has grown so far.
  • Customer/Problem Experiment – If the market is attractive, I ask myself, what problems do these people really have? Depending on my theory and riskiest assumption I might do interviews or keyword research or – something I’ve fallen in love with – analyze Amazon customer reviews.
  • Problem/Solution Experiment – Knowing the problem, can I come up with a solution that is attractive to the customer segment?

Once I have nailed those three, I start running custom experiments for Acquisition, Activation, Retention and Revenue (but not earlier.)

If you look at my dashboard below, I’ve run 43 Experiments for 14 Ventures. As you’ll see, based on my learnings I aborted more than 2/3rds of my Ventures and continued on only a few promising ones.

I think that’s been a huge value for me. To know and to be sure when to persevere and when not.

So far, I’m quiet happy with it. It’s rudimentary but works.

And since the Venture Board was, in itself, an experiment for myself (My theory was that I’ll consistently use it to track my progress and costs) –

I’m persevering.

Let me know if you would like early-bird access to the board. Simply comment below.

Thanks for reading!

How To Get Better At What You Do Every Day With Retrospectives

I mentioned the Retrospectives I have been doing for the past years.

Retros are great fun. I use them to improve myself and my work week-over-week. I love them.

In case you’re every interested in doing one, here are three templates that I use regularly. Both, by yourself and as a team.

They’re a lot of fun.

“Happy, Meh, Sad + Action Items”

The simplest one is “Happy, Meh, Sad + Action Items” (The one I told you about yesterday)

On a piece of paper, make the 4 columns, and for 10 minutes just write down what went well (Happy), what was so-so (Meh) and what isn’t going well (Sad). Then, go through the lists and think out loud what you can do to improve on it. Usually, that’s for the “Sad” column items.

“Start, Stop, More, Less, Keep”

On a piece of paper, draw the star and fill in the blanks around each piece. “What should I start doing, stop doing, do more of, do less of and what should I just keep doing?”

I love this one because almost all of it is super actionable.

“Sailboat to Paradise”

Great fun, helps figure out short-term and long-term goals.

Draw the picture on a piece of paper, and fill out the sections “Island: Where do I want to be? “, “Wind: What’s pushing me forward?”, “Anchor: What’s holding me back?”

It’s that simple. And, if you do them regularly and work on your action-items, you’ll see tremendous improvements for yourself and your team.

Calculating Nonces That Result In Zero-Padded SHA256 Hashes

Build Status

Full Github Repo here

Yesterday I watched Anders Brownworth’s awesome Blockchain demo and got inspired to follow along in Clojure.

I had to play around with implementing a toy (read: unoptimized!) algorithm to “mine” for nonces that, when added to the hashing input, produces n zeros at the beginning of the hash:

I came up with make-nonce-for-zeros-finder, a function which takes the numbers of zeros you want to have at the beginning and a maximum search-depth and returns a function that will calculate the right nonce for your inputs.

What I personally enjoyed about this little exercise was to express an iterative process in a simple recursive function. And to make a function-maker once I wanted to play around with different types of hashes/nonces I wanted to find.

(with-test
  (defn make-nonce-for-zeros-finder [z-count]
    (fn
      ([num data]
       ((make-nonce-for-zeros-finder z-count) num data 0))
      ([num data nonce]
       (cond (= (generate-zeros-string z-count) (subs (generate-hash num data nonce) 0 z-count)) nonce
             :else (recur num data (inc nonce))))))

  (is (= ((make-nonce-for-zeros-finder 1) nil nil) 39))
  (is (= ((make-nonce-for-zeros-finder 1) 1 nil) 25))
  (is (= ((make-nonce-for-zeros-finder 1) 1 1) 11))
  (is (= ((make-nonce-for-zeros-finder 1) "foo" "bar" 0) 20))
  (is (= ((make-nonce-for-zeros-finder 2) nil nil) 286))
  (is (= ((make-nonce-for-zeros-finder 4) nil nil) 88484))
  (is (= ((make-nonce-for-zeros-finder 4) 1 1) 64840))
  (is (= ((make-nonce-for-zeros-finder 4) "foo" "bar") 42515)))

With it, I can quickly create finders for different lengths of zero paddings:

(def find-nonce-for-one-zero-padded-hash
  (make-nonce-for-zeros-finder 1))

(def find-nonce-for-two-zeros-padded-hash
  (make-nonce-for-zeros-finder 2))

(def find-nonce-for-three-zeros-padded-hash
  (make-nonce-for-zeros-finder 3))

(def find-nonce-for-four-zeros-padded-hash
  (make-nonce-for-zeros-finder 4))

(def find-nonce-for-five-zeros-padded-hash
  (make-nonce-for-zeros-finder 5))

(def find-nonce-for-six-zeros-padded-hash
  (make-nonce-for-zeros-finder 6))

;; and so on...

With these guys set up, we can now calculate the nonce for hashes with 1, 2, 3, 4 and 5 zeros padded. Let’s see how that looks:

(find-nonce-for-one-zero-padded-hash "foo" "bar")
;; => 20
;; takes less than a millisecond

;; we can verify that the hash has one zero at the beginning:
(generate-hash "foo" "bar" 20)
;; => "0fdc57809f5917eba08907d2805e43ce83f4c933a090b4a2b2549923a35e43d7"

(find-nonce-for-two-zeros-padded-hash "foo" "bar")
;; => 102
;; takes less than a millisecond

;; we can verify that the hash has two zeros at the beginning:
(generate-hash "foo" "bar" 102)
;; => "006668bba91b7e2d5b5357b56600784edb77a72ecf86dc09d515853a841485f6"

(find-nonce-for-three-zeros-padded-hash "foo" "bar")
;; => 4663
;; takes less than a millisecond

;; we can verify that the hash has three zeros at the beginning:
(generate-hash "foo" "bar" 4663)
;; => "0005d9cd6c13fe6bf56e169fd2a7008003fc3a4c6539f8a1cf7d82975d00210e"

And that’s really all there is to it. Once you cross 6 padded zeros, you’ll notice an increase in time it takes to compute.

How to test

I’ve been a huge fan of the with-test macro. Because of that, you’ll find all test-coverage in core.clj instead of a separate “_test.clj” file.

To run all tests, use lein test

The Little Schemer in Clojure – Recap Chapter 6

Shadows

Using helper functions increases readability and helps abstract away representations.

In particular, the example is a simple calculating function value such as (value '(1 + (7 + 11)) ;; => 19

We can implement this with standard procedures and recursion:

(with-test
  (def value
    (fn [u] 
      (cond (atom? u) u
            (eq? (car (cdr u)) '+) (+ (value (car u))
                                          (value (car (cdr (cdr u)))))
            (eq? (car (cdr u)) '*) (- (value (car u))
                                           (value (car (cdr (cdr u)))))
            :else (int (java.lang.Math/pow (value (car u))
                                           (value (car (cdr (cdr u)))))))))

  (is (= (value 13) 13))
  (is (= (value '(1 + 3)) 4))
  (is (= (value '(1 + (3 pow 4))) 82)))

Now, you can already see a lot of visual noise. There is a lot of car-ing and cdr-ing about. If you squint your eyes a little, you’ll also notice that they all look similar. in fact, there is a lot of repetition in this function:

We see this pattern three times and each it’s along the lines of:

((eq? (car (cdr u)) 'OPERATOR) (OPERATOR (value (car u)) (value (car (cdr (cdr u))))))

Since we know that our “language” right now is infix arithmetic expressions such as

(1 + 2) and (1 + (4 * 999))

…we can extract helper functions that get the operator, the first sub expression - 1 and the second sub expression - 2 like so:

(with-test
  (def operator
    (fn [aexp]
      (car (cdr aexp))))

  (is (= (operator '()) nil))
  (is (= (operator '(1)) nil))
  (is (= (operator '(1 + )) '+))
  (is (= (operator '(1 + 2)) '+)))

(with-test
  (def first-sub-exp
    (fn [aexp]
      (car aexp)))

  (is (= (first-sub-exp '()) nil))
  (is (= (first-sub-exp '(1)) 1))
  (is (= (first-sub-exp '(1 + 2)) 1))
  (is (= (first-sub-exp '(2 + 1)) 2)))

(with-test
  (def second-sub-exp
    (fn [aexp] (car (cdr (cdr aexp)))))

  (is (= (second-sub-exp '()) nil))
  (is (= (second-sub-exp '(1)) nil))
  (is (= (second-sub-exp '(1 +)) nil))
  (is (= (second-sub-exp '(1 + 2)) 2))
  (is (= (second-sub-exp '(1 + 3)) 3)))

And then, since we have test-coverage already, refactor our value function to be…

(with-test
  (def value
    (fn [u] 
      (cond (atom? u) u
            (eq? (operator u) '+) (+ (value (first-sub-exp u))
                                          (value (second-sub-exp u)))
            (eq? (operator u) '*) (- (value (first-sub-exp u))
                                           (value (second-sub-exp u)))
            :else (int (java.lang.Math/pow (value (first-sub-exp u))
                                           (value (second-sub-exp u)))))))

  (is (= (value 13) 13))
  (is (= (value '(1 + 3)) 4))
  (is (= (value '(1 + (3 pow 4))) 82)))

Dandy. The new help functions help read this much better.

Changing Your Mind

Now, say, you wake up today and would like to change your newly-born arithmetic language to be written in prefix notation, such as (+ 1 2). In a way, I want to say:

I’ve changed my mind. Now, I want have (+ 1 2) express the common arithmetic expression that evaluates to 4.

The changes needed to make this happen are now trivial since we have used help functions to hide the representation of operator, first sub expression and second sub expression.

Simply changing the helper functions as follows implements the prefix notation without having to change our value function.

You have to adjust your tests accordingly, of course, and, in a way, doing so is as much as saying

“I want to change this and it should work as follows…”

(with-test
  (def operator
    (fn [aexp]
      (car aexp)))

  (is (= (operator '()) nil))
  (is (= (operator '(+)) '+))
  (is (= (operator '(+ 1)) '+)))

(with-test
  (def first-sub-exp
    (fn [aexp]
      (car (cdr aexp))))

  (is (= (first-sub-exp '()) nil))
  (is (= (first-sub-exp '(+)) nil))
  (is (= (first-sub-exp '(+ 1)) 1))
  (is (= (first-sub-exp '(+ 1 2)) 1)))

(with-test
  (def second-sub-exp
    (fn [aexp]
      (car (cdr (cdr aexp)))))

  (is (= (second-sub-exp '()) nil))
  (is (= (second-sub-exp '(+)) nil))
  (is (= (second-sub-exp '(+ 1)) nil))
  (is (= (second-sub-exp '(+ 1 2)) 2))
  (is (= (second-sub-exp '(+ 1 2 3)) 2)))

(with-test
  (def value-prefix
    (fn [nexp]
      (cond (atom? nexp) nexp
            (eq? (operator nexp) '+) (+ (value-prefix (first-sub-exp nexp))
                                         (value-prefix (second-sub-exp nexp)))
            (eq? (operator nexp) '*) (* (value-prefix (first-sub-exp nexp))
                                        (value-prefix (second-sub-exp nexp)))
            :else (int (java.lang.Math/pow (value-prefix (first-sub-exp nexp))
                                           (value-prefix (second-sub-exp nexp)))))))

  (is (= (value-prefix 1) 1))
  (is (= (value-prefix '(+ 1 3)) 4))
  (is (= (value-prefix '(+ 1 (* 2 2))) 5))
  (is (= (value-prefix '(+ 1 (pow 3 4))) 82)))

This is nice. We have changed values language by changing the helper functions without changing its own structure.

This way, we are able to demonstrate that we can represent the arithmetic expression 1 + 1 in several ways:

  • (+ 1 1) to make it feel lispy
  • (1 + 1) to make it feel ‘normal’ and
  • (1 1 +) if we wanted to accommodate friends that read from right to left more naturally

After all, they mean the same thing but in different ways.

Parental Controls – Braces as Numbers

Now, another example: Let’s say we wanted to represent numbers differently but wanted, as reasonable human beings, to maintain the idea of plus, minus, multiplication and division.

So, instead of using the numeral…

  • 0, I would like to use () (a list of zero items)
  • 1, I would like to use (()) (a list of one items)
  • 2, I would like to use (() ()) (a list of two items)
  • 3, I would like to use (() () ()) (a list of three items)
  • and so on…

In terms of API, I would like then to be able to write 1 + 1 as follows:

(+ '( () ) '( () ))
;; => 2

Clearly, the idea of addition or subtraction doesn’t change just because I’ve changed how I represent my numbers, right?

Our function to represent the operator + was

  (def + (fn [n m] 
                (cond (zero? m) n
                      :else (add1 (+ n (sub1 m))))))

To make our dream of (fairly unreadable) parenthesis number crunching real, all we have to do is rewrite our helper functions zero?, add1 and sub1 and we’re off to the races:

(with-test
  (def zero? 
    (fn [n] (null? n)))

  (is (= (zero? '()) true))
  (is (= (zero? '(())) false)))

(with-test
  (def add1
    (fn [n]
      (cons '() n)))
  (is (= (add1 '()) '(())))
  (is (= (add1 '(())) '(() ())))
  (is (= (add1 '(() ())) '(() () ()))))

(with-test
  (def sub1
    (fn [n] (cdr n)))

  (is (= (sub1 '()) '()))
  (is (= (sub1 '(())) '()))
  (is (= (sub1 '(() ())) '(()))))

(with-test
  (def + (fn [n m] 
            (cond (zero? m) n
                  :else (add1 (+ n (sub1 m))))))

  (is (= (+ '() '()) '()))
  (is (= (+ '(()) '())) '(()))
  (is (= (+ '(() ()) '(())) '(() () ()))))

Anyways, that’s what I’ve taken away from the chapter.

My Journey Into Deep Work – Retrospective Week 17

I spent this week in Mallorca with my parents. Stil I got to take time to sit down and get 13 (!) hours of deep work done.

Here’s my recap:

What went well

  • Still very inspired by “Amateurs vs. Pros
  • Feel like I’ve made good progress
  • 1 Focus for the week: Work through “The Little Schemer” in Clojure. Finished Chapter 1 – 4.
  • Wrote and published 4 blogposts
  • Daily Yoga and especially Pranayama exercises
  • Had an idea for a meditation video
  • Very happy with my Psycho-Cybernetic Imagineering script rehearsals
  • Cooking good food with the family
  • Have a fireplace at home
  • Have a minimal but functional work-space set up

What went okay

  • Didn’t spend a lot of time outside, especially when there’s the mediterranean just across from me.
  • “Give and Take” is getting harder to read
  • Access to internet only sometimes
  • Still jumping a lot from idea to idea
  • Didn’t celebrate/appreciate/acknowledge my progress
  • Didn’t edit the blogposts
  • Lost interest in RedditGrowth while it still could become something valuable

What went bad

  • Have procrastinated on writing the meet up summary
  • Too reserved talking about my feelings with my parents

Action items

  • Take a daily walk
  • Celebrate successes and progress more often
  • Edit posts before publishing
  • Make a decision if I want to continue reading “Give and Take”

My Journey Into Deep Work – Retrospective Week 16

I managed to work deeply for 9.5 hours. I also traveled over the weekend, so my week was a bit shot.

Also, I have decided to extend my retros to look at my overall life not just deep work. Since I am independent right now, I want to make sure I maximize my time spent and minimize distractions.
I figured that “Life Retro” combined with action items to improve myself every week is a good idea.

What went well

  • Using my Psycho-Cybernetics Imagineering script
  • It was easy to get into work-flow
  • With everything considered, 9.5 hours is still great!
  • Traveled to Mallorca to see my parents 🙂
  • Had good insight into my “stop-and-go” way of working
  • Had a good mentoring session with my friend Philip on Friday
  • Overall, I have a more positive outlook on my life than last week
  • Inspired by reading “Give and Take” [1]
  • Inspired by “Amateurs vs. Pros” [2]
  • Got up most days before 9am
  • Getting back into a daily Yoga and Meditation routine
  • I fixed my vaporizer myself
  • Feeling less distracted
  • Got to hang out with Nina for an afternoon 🙂
  • Went out to a cool bar on Friday with friends
  • Made bubbles again
  • Cooked delicious food with my father

What went okay

  • I didn’t follow up on my Whiteboard experiment
  • I haven’t followed up on the Meetup idea I had
  • There was a lot of organizational stuff this week
  • Waiting for my accountant on paperwork
  • The dating-pool in Berlin is rough. Didn’t do well to my self-image
  • Didn’t hear back from someone I needed information from

What went bad

  • I still don’t have one specific idea/topic that I want to deep-dive into. Instead I jump around from idea to idea.
  • Jumping between tasks and ideas is exhausting.

Action Items

  • Choose 1 main point of focus for coming week
  • Buy a yoga mat while in Mallorca to continue practice there
  • Create a visible backlog of tasks

[1] https://www.amazon.com/dp/B00AFPTSI0/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1
[2] https://medium.com/the-mission/the-7-differences-between-professionals-and-amateurs-f8efc4840861

Thinking and Sleeping Better With Clojure

To prioritize a list of todo-items, as in my Prioritization-app I recently launched, I needed to get a list of the unique combinations for the user to choose from:

It goes a little bit like this:

Which is more important: A or B?
Which is more important: A or C?
Which is more important: B or C?

I wrote a version in Ruby for the app and then a sketch in Clojure to compare readability and expressiveness.

In my opinion, the Clojure version is much more readable and was far easier to reason about.

Probably because I’m not good to solve problems in Ruby but there might be more than meets the eye here.

Creating Comparisons

Let’s say I have a todo-list of A, B, C and D.

The list of comparisons I want is A ↔ B, A ↔ C, A ↔ D, B ↔ C, B ↔ D and C ↔ D to use later in my app.

From the matrix below, you can see, I want only the unique comparisons (A ↔ B and B ↔ A are interchangeable for my purposes) and skip dupes that compare an element with itself (A ↔ A, B ↔ B, C ↔ C, D ↔ D)

To demonstrate, I only want the top-right triangle of comparisons:

× A B C D
A skip (dupe) A ↔ B A ↔ C A ↔ D
B skip skip (dupe) B ↔ C B ↔ D
C skip skip skip (dupe) C ↔ D
D skip skip skip skip (dupe)

Thinking, Testing and Implementing in Ruby

Now, since I started the app in Ruby, I translated the behavior above as follows:

describe '.unique_combinations' do
    context 'input list is empty' do
      it 'returns an empty list' do
        result = Combinator.unique_combinations([])
        expect(result).to eq([])
      end
    end

    context 'input list only has one element' do
      it 'returns an empty list' do
        result = Combinator.unique_combinations([1])
        expect(result).to eq([])
      end
    end

    context 'input list only has two elements' do
      it 'returns a list with combination a' do
        result = Combinator.unique_combinations([1, 2])
        expect(result).to eq([[1,2]])
      end

      it 'returns a list with combination b' do
        result = Combinator.unique_combinations([:foo, :bar])
        expect(result).to eq([[:foo, :bar]])
      end
    end

    context 'input list only has three elements' do
      it 'returns a list with 3 combinations' do
        result = Combinator.unique_combinations([1, 2, 3])
        expect(result).to eq([[1, 2], [1, 3], [2, 3]])
      end
    end

    context 'input list only has four elements' do
      it 'returns a list with 6 combinations' do
        result = Combinator.unique_combinations([1, 2, 3, 4])
        expect(result).to eq([[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]])
      end
    end
  End
end

…and wrote, after several failed attempts and one sleepless night, the following, working but not very elegant solution. I felt weird about it, because even looking at it now, I can’t fully explain it well. It works, it has reasonable test-coverage. But I’m not proud of it.

def self.unique_combinations(ids)
    return [] if ids.length < 2

    all_combinations = []

    for i1 in ids
      for i2 in ids
        if i1 != i2
          contains = false
          for c in all_combinations
            if c[1] == i1 and c[0] == i2
              contains = true
            end
          end
          unless contains
            all_combinations << [i1, i2]
          end
        end
      end
    end

    return all_combinations
  end

Okay, fine I thought. How would I solve this issue in Lisp?

Thinking, Testing and Implementing in Clojure

I don’t know why but thinking in terms of lists and recursion instead of iteration made it simpler for me.

The top-level function works as follows:

(final-combinations '(A B C D)) 
;; => '((A B) (A C) (A D) (B C) (B D) (C D))

Yup, that’s the list I want.
Thinking about it I had an idea to break the problem further down:

The complete list of unique comparisons, the one I want, is…

  1. the union of each row of one element combined with all the others. That’s A combined with B, C and D
  2. Each row has an increasing offset from the left that starts at 0 for row 1 and increases by 1 for each following row.
  3. I have to subtract the dupes such as A ↔ A because I don’t need them.

For illustration purposes, here is the table again:

× A B C D
A skip (dupe) A ↔ B A ↔ C A ↔ D
B skip skip (dupe) B ↔ C B ↔ D
C skip skip skip (dupe) C ↔ D
D skip skip skip skip (dupe)

Given the above 3 steps, I basically just wrote them out as functions.

I derived the first function, combine which takes x and l and returns a list of all possible combinations, including the ones we’ll later skip.

(with-test 
  (defn combine 
    ([x l] 
     (combine x l '()))
    ([x l acc]
     (cond (empty? l) (reverse acc)
           :else (recur x (rest l) (conj acc (list x (first l)))) )))

  (is (= (combine 1 '()) '()))
  (is (= (combine 1 '(1)) '((1 1))))
  (is (= (combine 1 '(2)) '((1 2))))
  (is (= (combine 1 '(1 2)) '((1 1) (1 2))))
  (is (= (combine 1 '(1 2 3)) '((1 1) (1 2) (1 3)))))

Then, I use combine to create basically the table above but with nothing yet removed.

(with-test
  (defn combine-lists 
    ([l1 l2] 
     (combine-lists l1 l2 '()))
    ([l1 l2 acc]
     (cond (empty? l1) (reverse acc)
           :else (recur (rest l1) l2 (cons (combine (first l1) l2) acc)) )))

  (is (= (combine-lists '() '()) '()))
  (is (= (combine-lists '(1) '(1)) '(((1 1)))))
  (is (= (combine-lists '(1 2) '(1 2)) '(((1 1) (1 2)) 
                                         ((2 1) (2 2))))))

Then I introduce the idea of an offset. To only take those elements in a list that are after a certain offset.

(with-test
  (defn take-rest
    ([offset l]
     (cond (= offset 0) l
           :else (recur (dec offset) (rest l)))))

  (is (= (take-rest 0 '()) '()))
  (is (= (take-rest 1 '()) '()))
  (is (= (take-rest 0 '(1)) '(1)))
  (is (= (take-rest 0 '(1 2)) '(1 2)))
  (is (= (take-rest 1 '(1 2)) '(2)))
  (is (= (take-rest 1 '(1 2 3)) '(2 3)))
  (is (= (take-rest 2 '(1 2 3)) '(3)))
  (is (= (take-rest 2 '(1 2 3 4)) '(3 4))))

Now I can use take-rest and apply it to each row in my matrix…

(defn unique-combinations 
  ([offset ll] 
   (unique-combinations offset ll '()))
  ([offset ll acc]
   (cond (empty? ll) (reverse acc)
         :else (recur (inc offset) (rest ll) (cons (take-rest offset (first ll)) acc)))))

And finally remove the identity items such as A ↔ A

(with-test
  (defn final-combinations 
    [l]
    (filter #((= (first %) (second %)))) (partition 2 (flatten (unique-combinations 1 (combine-lists l l)))))

  (is (= (final-combinations '()) '()))
  (is (= (final-combinations '(1)) '()))
  (is (= (final-combinations '(1 2)) '((1 2))))
  (is (= (final-combinations '(1 2 3)) '((1 2) (1 3) (2 3))))
  (is (= (final-combinations '(1 2 3 4)) '((1 2) (1 3) (1 4) (2 3) (2 4) (3 4)))))

Granted, this code is not yet refactored and in total more lines than the Ruby version, but here is the rub:

It took me 20 minutes to think up and implement the solution in Clojure and 2 days including a sleepless night to do it in Ruby.

I don’t know what that means but I’ll probably use a Lisp like Clojure for problems like this in the future. Just to make sure I get some sleep.