TDD Kata 05 – Karate Chop

This is my weekly Kata post. Read the first one to learn what it is all about.

Last week: Word Wrap

In PHP/Behat I learned about scenario outlines, which remove a lot of repetition in feature files. Full result:

Feature: Word Wrap
  In order to display text on tiny screens
  I want to wrap lines of a long text

  Scenario Outline: Wrap text
    When I wrap <text> at <col> columns
    Then I should retrieve the wrapped string <wrapped>

    Examples:
      | text  | col   | wrapped |
      |     | 1   |       |
      | word  | 2   | wo\nrd |
      | word  | 3   | wor\nd |
      | word  | 4   | word |
      | word  | 5   | word |
      | word word | 4 | word\nword |
      | word word | 5 | word\nword |
      | word word | 7 | word\nword |
      | word word | 8 | word\nword |
      | word word | 9 | word word |
      | abcdefg hij klmno p qurstuvwx yz | 4 | abcd\nefg\nhij\nklmn\no p\nqurs\ntuvw\nx yz |

<?php
use Behat\Behat\Context\Context;

class WordwrapContext implements Context
{
    private $result;

    /**
     * @When /^I wrap (.*) at (.*) columns$/
     */
    public function iWrapAtColumns($text, $col)
    {
        $this->result = wrap($text, $col);
    }

    /**
     * @Then /^I should retrieve the wrapped string (.*)$/
     */
    public function iShouldRetrieveTheWrappedString($wrapped)
    {
        $this->assertEquals(str_replace('\n', "\n", $wrapped), $this->result);
    }

    private function assertEquals($expected, $actual)
    {
        if (gettype($expected) != gettype($actual)) {
            throw new LogicException("Expected type " . gettype($expected). ", got " . gettype($actual));
        }
        if ($expected !== $actual) {
            throw new LogicException("Expected " . var_export($expected, true) . ", got " . var_export($actual, true));
        }
    }
}

function wrap(string $text, int $columnSize): string
{
    if (strlen($text) <= $columnSize) {
        return $text;
    }
    $splitPosition = min(strrpos($text, ' '), $columnSize) ?: $columnSize;
    return trim(substr($text, 0, $splitPosition)) . "\n" . wrap(trim(substr($text, $splitPosition)), $columnSize);
}

In Ruby, the Kata made me learn about string indexing, it took me a while to get it to work as intended and it probably is not very idiomatic. Full result:

gem 'minitest', '~> 5.4'
require 'minitest/autorun'

class WordWrapTest < Minitest::Test

    def assert_wrap expected, text, columns
        assert_equal expected, wrap(text, columns)
    end

    def test_single_short_word
        assert_wrap "word", "word", 4
    end

    def test_single_long_word
        assert_wrap "long\nword", "longword", 4
    end

    def test_single_very_long_word
        assert_wrap "very\nlong\nword", "verylongword", 4
    end

    def test_space_before_wrap
        assert_wrap "two\nword\ns", "two words", 4
    end

    def test_space_after_wrap
        assert_wrap "long\nword", "long word", 4
    end

    def test_space_somewhere_else
        assert_wrap "abc\nde\nfghi\njk", "abc de fghi jk", 4
    end
end
    
def wrap text, columns
    if text.length <= columns then
        return text
    end
    break_at = text[0..columns-1].index(" ") || columns
    if text[break_at] == " " then
        line_start = break_at + 1
    else
        line_start = break_at
    end
    text[0..break_at-1] + "\n" + wrap(text[line_start..-1], columns)
end

Fifth Kata: Karate Chop

The ordinary name of “Karate Chop” is “Binary Search”. A binary search algorithm finds the position of a value in a sorted array of values. The kata is described at http://codekata.com/kata/kata02-karate-chop/ where you are encouraged to find five unique approaches.

My personal goals this week

  • Come up with five unique approaches.
  • Use different languages as they fit. I don’t want to settle for techniques yet.