CyberSpy

Rantings from a guy with way too much free time

Exercism - Get your Code Deamons Out! Heal Your Body!

2018-02-28 programming Rob Baruch

Exercis(m)

The best way to keep the mind, body, and soul sharp is to exercise! No better way to learn new programming paradigms than following this same precept when learning new concepts in an unfamiliar programming language. There are several sites online that afford noobs practice problems - Hackerrank, kaggle to name a few. But one that I recently came across, exercism.io, aims to provide not just programming exercises, but a community of folks committed to commenting and sharing both code and criticism on implementations to problem sets across many languages.

As an example, I’ve been addicted to solving OCaml problems this week allowing me to better understand many of the less-subtle aspects of the language in a more practical context - not just the two-line pathological examples found in trivial introductory documentation that typcially accompanies language reference materials.

Take a look at the problem that I tackled last night - on the surface it’s pretty straight forward, but making the problem set more interesting, each one comes complete with a test unit that typically contains quirky test-cases which you’re not expecting.

In this challenge, the problem is a simple checksum for things like credit cards and social security cards complying to the Luhn standard.

Here’s my implementation below:


open Core
(* Returns true if the input is a valid Luhn string *)


let valid_digits card = 
        String.to_list card
        |> List.for_all ~f:(fun c -> Char.is_digit c || Char.is_whitespace c)

let valid_length card =
        List.length card > 1  
let double_adj d = 
        let dd = d * 2 in
        match dd >  9 with
        | true -> dd - 9
        | _ -> dd

let flip_over_two (l:int list): (int list) = 
        match (List.length l > 2) with
        | true -> List.rev l
        | false -> l

let valid (card:string): bool =
        match valid_digits card with
        | false -> false
        | true -> let digits = String.strip ~drop:Char.is_whitespace card
        |> String.filter ~f:Char.is_digit
        |> String.to_list
        |> List.map ~f:(fun c -> (int_of_char c) - (int_of_char '0')) 
        |> flip_over_two in
        match valid_length digits with
        | false -> false
        | true ->
        let (dd, _) = List.fold_right digits ~init:([],0) ~f:(fun d (a,i) ->
                match (i mod 2) = 0 with
                | true -> (d::a,i+1)
                | false -> (double_adj(d)::a,i+1)
        ) in 
        let sum = List.fold_right dd ~init:0 ~f:(fun d a -> d +a ) in
                sum mod 10 = 0

Take a look at the test cases. The site uses OUnit2 testing feature and each problem comes with a makefile and associated test harness.

(* Test/exercise version: "1.1.0" *)

open Core
open OUnit2
open Luhn

let assert_valid expected input _test_ctxt = 
  assert_equal ~printer:Bool.to_string expected (valid input)

let tests = [
  "single digit strings can not be valid" >::
    assert_valid false "1";
  "a single zero is invalid" >::
    assert_valid false "0";
  "a simple valid SIN that remains valid if reversed" >::
    assert_valid true "059";
  "a simple valid SIN that becomes invalid if reversed" >::
    assert_valid true "59";
  "a valid Canadian SIN" >::
    assert_valid true "055 444 285";
  "invalid Canadian SIN" >::
    assert_valid false "055 444 286";
  "invalid credit card" >::
    assert_valid false "8273 1232 7352 0569";
  "valid strings with a non-digit included become invalid" >::
    assert_valid false "055a 444 285";
  "valid strings with punctuation included become invalid" >::
    assert_valid false "055-444-285";
  "valid strings with symbols included become invalid" >::
    assert_valid false "055\194\163 444$ 285";
  "single zero with space is invalid" >::
    assert_valid false " 0";
  "more than a single zero is valid" >::
    assert_valid true "0000 0";
  "input digit 9 is correctly converted to output digit 9" >::
    assert_valid true "091";
]

let () =
  run_test_tt_main ("luhn tests" >::: tests)

Not only does the testing framework make for a more challenging problem, it makes for a more disciplined development process. Perusing the test cases prior to writing the solution affords better decision making for algorithms and data representations.

I’ve written nearly two-dozen solutions and have found the site both enjoyable and engaging! Regardless of the language that interests you, I highly recommend taking a crack at the challenge problems - you’ll be better for having put in the time!

– rob

comments powered by Disqus