Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add diamond kata exercise #191

Merged
merged 1 commit into from
Mar 12, 2016
Merged

Conversation

ErikSchierboom
Copy link
Member

This PR adds the diamond kata as an exercise. This kata has become popular as a means to show how property-based testing can help you implement an algorithm correctly. See e.g http://blog.ploeh.dk/2015/01/10/diamond-kata-with-fscheck/.

@NobbZ
Copy link
Member

NobbZ commented Mar 8, 2016

Have you also prepared an example for one of the live tracks?

Erik Schierboom [email protected] schrieb am Di., 8. März 2016
09:13:

This PR adds the diamond kata
http://claysnow.co.uk/recycling-tests-in-tdd/ as an exercise. This kata
has become popular as a means to show how property-based testing can help
you implement an algorithm correctly. See e.g

http://blog.ploeh.dk/2015/01/10/diamond-kata-with-fscheck/.

You can view, comment on, or merge this pull request online at:

#191
Commit Summary

  • Add diamond kata exercise

File Changes

Patch Links:


Reply to this email directly or view it on GitHub
#191.

@ErikSchierboom
Copy link
Member Author

@NobbZ I do, for the F# track.

This is the implementation:

module Diamond

let make letter =     

    let makeLine letterCount (row, letter) = 
        let outerSpaces  = "".PadRight(letterCount - row - 1)
        let innerSpaces = "".PadRight(if row = 0 then 0 else row * 2 - 1)

        if letter = 'A' then sprintf "%s%c%s" outerSpaces letter outerSpaces
        else sprintf "%s%c%s%c%s" outerSpaces letter innerSpaces letter outerSpaces

    let letters = ['A'..letter] |> List.mapi (fun x y -> x, y)

    letters @ (letters |> List.rev |> List.tail)
    |> List.map (makeLine letters.Length)
    |> List.reduce (fun x y -> sprintf "%s\n%s" x y)

These are the tests:

module DiamondTest

open Diamond
open System
open NUnit.Framework

let split (x: string) = x.Split([| '\n' |], StringSplitOptions.None)

let trim (x:string) = x.Trim()

let leadingSpaces (x:string) = x.Substring(0, x.IndexOfAny [|'A'..'Z'|])

let trailingSpaces (x:string) = x.Substring(x.LastIndexOfAny [|'A'..'Z'|] + 1)

[<TestCase('A')>]
[<TestCase('C')>]
[<TestCase('F')>]
[<TestCase('Z')>]
let ``First row contains 'A'`` (letter:char) =
    let actual = make letter
    let rows = actual |> split
    let firstRowCharacters = rows |> Seq.head |> trim

    Assert.That(firstRowCharacters, Is.EqualTo("A"))

[<TestCase('A')>]
[<TestCase('C')>]
[<TestCase('F')>]
[<TestCase('Z')>]
let ``Last row contains 'A'`` (letter:char) =
    let actual = make letter
    let rows = actual |> split
    let lastRowCharacters = rows |> Seq.last |> trim

    Assert.That(lastRowCharacters, Is.EqualTo("A"))

[<TestCase('A')>]
[<TestCase('C')>]
[<TestCase('F')>]
[<TestCase('Z')>]
let ``All rows must have symmetric contour`` (letter:char) =
    let actual = make letter
    let rows = actual |> split
    let symmetric (row:string) = leadingSpaces row = trailingSpaces row

    Assert.That(rows, Is.All.Matches(symmetric))

[<TestCase('A')>]
[<TestCase('C')>]
[<TestCase('F')>]
[<TestCase('Z')>]
let ``Top of figure has letters in correct order`` (letter:char) =
    let actual = make letter

    let expected = ['A'..letter]
    let rows = actual |> split
    let firstNonSpaceLetters =
        rows 
        |> Seq.take expected.Length
        |> Seq.map trim
        |> Seq.map Seq.head
        |> Seq.toList

    Assert.That(expected, Is.EqualTo(firstNonSpaceLetters))

[<TestCase('A')>]
[<TestCase('C')>]
[<TestCase('F')>]
[<TestCase('Z')>]
let ``Figure is symmetric around the horizontal axis`` (letter:char) =
    let actual = make letter

    let rows = actual |> split
    let top = 
        rows
        |> Seq.takeWhile (fun x -> not (x.Contains(string letter)))
        |> List.ofSeq

    let bottom = 
        rows 
        |> Array.rev
        |> Seq.takeWhile (fun x -> not (x.Contains(string letter)))
        |> List.ofSeq

    Assert.That(top, Is.EqualTo(bottom))

[<TestCase('A')>]
[<TestCase('C')>]
[<TestCase('F')>]
[<TestCase('Z')>]
let ``Diamond has square shape`` (letter:char) =
    let actual = make letter

    let rows = actual |> split
    let expected = rows.Length
    let correctWidth (x:string) = x.Length = expected

    Assert.That(rows, Is.All.Matches(correctWidth))

[<TestCase('A')>]
[<TestCase('C')>]
[<TestCase('F')>]
[<TestCase('Z')>]
let ``All rows except top and bottom have two identical letters`` (letter:char) =
    let actual = make letter

    let rows = 
        actual 
        |> split 
        |> Array.filter (fun x -> not (x.Contains("A")))

    let twoIdenticalLetters (row:string) = 
        let twoCharacters = row.Replace(" ", "").Length = 2
        let identicalCharacters = row.Replace(" ", "") |> Seq.distinct |> Seq.length = 1
        twoCharacters && identicalCharacters

    Assert.That(rows, Is.All.Matches(twoIdenticalLetters))

[<TestCase('A')>]
[<TestCase('C')>]
[<TestCase('F')>]
[<TestCase('Z')>]
let ``Bottom left corner spaces are triangle`` (letter:char) =
    let actual = make letter

    let rows = actual |> split

    let cornerSpaces = 
        rows 
        |> Array.rev
        |> Seq.skipWhile (fun x -> not (x.Contains(string letter)))
        |> Seq.map leadingSpaces
        |> Seq.toList

    let spaceCounts = 
        cornerSpaces 
        |> List.map (fun x -> x.Length)

    let expected = 
        Seq.initInfinite id
        |> Seq.take spaceCounts.Length
        |> Seq.toList

    Assert.That(spaceCounts, Is.EqualTo(expected))

kytrinyx added a commit that referenced this pull request Mar 12, 2016
@kytrinyx kytrinyx merged commit 57bf79b into exercism:master Mar 12, 2016
@kytrinyx
Copy link
Member

This is nice, thanks!

@petertseng
Copy link
Member

petertseng commented Oct 18, 2016

Should it be policy that implementing tracks should use the property-based tests, instead of the "check that the output is exactly this" (maybe called value-based tests, and now I see in the linked post is called example-based tests)?

@ErikSchierboom ErikSchierboom deleted the diamond-kata branch October 18, 2016 17:47
@ErikSchierboom
Copy link
Member Author

Well, policy is maybe a bit strict, but I would strongly be in favor that we mention that people should try to solve it using property-based tests (if their language has such a library).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants