I've been trying Advent of Code challenges to learn Clojure. I've successfully completed Day 2, but I feel like my solution is very clumsy, especially the functions for parsing text input.
Instructions:
Imagine there is a sequence of games played, encoded in a text file, one game per line. Each game consists of an id and a sequence of (semicolon-delimited) draws of colored cubes from a small bag. Each draw is encoded as a (comma-delimited) sequence of numbers of cubes drawn per color.
Example line (game):Game 2: 1 green, 1 blue, 1 red; 11 red, 3 blue; 1 blue, 18 red; 9 red, 1 green; 2 blue, 11 red, 1 green; 1 green, 2 blue, 10 red
The goal is to parse such file for further processing.
My solution:
(defn parse-coloring [coloring] (let [tokens (clojure.string/split coloring #" ") color (second tokens) value (Integer/parseInt (first tokens))] { :color color :value value }))(defn parse-draw [draw] (let [colorings (clojure.string/split draw #", ")] (map parse-coloring colorings)))(defn parse-line [line] (let [matches (re-matches #"^Game (\d+): (.*)$" line) game-id (Integer/parseInt (nth matches 1)) game (nth matches 2) draws (clojure.string/split game #"; ")] { :id game-id :draws (map parse-draw draws)}))(def games (let [input (slurp "C:\\Users\\...\\input.txt") lines (clojure.string/split-lines input)] (map parse-line lines)))I'm looking for a more simple, succinct, idiomatic-clojure solution.
1 Answer1
I think you've nailed it pretty well. The only thing I would do is replacemap withmapv everywhere (simpler to avoid lazy lists). Also, as a big believer in TDD, I've added how I would throw in some unit tests to include "live" documentation of what/how each fn works.
(ns tst.demo.core (:use demo.core tupelo.core tupelo.test) (:require [tupelo.string :as str]))I start off using myfavorite template project and library.
(defn parse-coloring [coloring] (let [tokens (clojure.string/split coloring #" ") color (second tokens) value (Integer/parseInt (first tokens))] {:color color :value value}))(verify (is= (parse-coloring "1 green") {:color "green" :value 1}))So we see the pattern of a unit test for each small function, so a new reader sees sample input/output.
(defn parse-draw [draw] (let [colorings (clojure.string/split draw #", ")] (map parse-coloring colorings)))(verify (is= (parse-draw "1 green, 1 blue, 1 red") [{:color "green" :value 1} {:color "blue" :value 1} {:color "red" :value 1}]))Same for this function...
(defn parse-game [line] (let [matches (re-matches #"^Game (\d+): (.*)$" line) game-id (Integer/parseInt (nth matches 1)) game (nth matches 2) draws (clojure.string/split game #"; ")] {:id game-id :draws (map parse-draw draws)}))(verify (is= (parse-game "Game 1: 1 green, 1 blue, 1 red; 11 red, 3 blue") {:id 1 :draws [[{:color "green" :value 1} {:color "blue" :value 1} {:color "red" :value 1}] [{:color "red" :value 11} {:color "blue" :value 3}]]}))and this one. We're almost done.
(defn separate-games [text] (mapv str/trim (str/split-lines (str/trim text))))(defn parse-input [text] (let [game-strs (separate-games text) games-parsed (mapv parse-game game-strs)] games-parsed))I broke this up into 2 functions to make unit testing easier.
(verify (let [; throw in some gratuitous leading/trailing newlines to check robustness input " Game 1: 1 green, 1 blue, 1 red; 11 red, 3 blue Game 2: 9 green, 9 blue, 1 red; 11 red, 3 blue; 1 blue, 18 red " game-strs (separate-games input) strs-expected ["Game 1: 1 green, 1 blue, 1 red; 11 red, 3 blue" "Game 2: 9 green, 9 blue, 1 red; 11 red, 3 blue; 1 blue, 18 red"]] (is= game-strs strs-expected)So we can verify the line-by-line parsing independently of the other processing.
Now, we verify the whole kit-n-kaboodle for a 2 line file.
(let [games-parsed (parse-input input)] (is= games-parsed [{:draws [[{:color "green", :value 1} {:color "blue", :value 1} {:color "red", :value 1}] [{:color "red", :value 11} {:color "blue", :value 3}]], :id 1} {:draws [[{:color "green", :value 9} {:color "blue", :value 9} {:color "red", :value 1}] [{:color "red", :value 11} {:color "blue", :value 3}] [{:color "blue", :value 1} {:color "red", :value 18}]], :id 2}]))))You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.
