Code Kata 5

·Kata Seventeen: More BusinessProcessing

The rules that specify the overall processing of anorder can be complex too, particularly as they often involve waiting around forthings to happen.

In Kata Sixteen we had a look at the business rulesthat applied when we received payment for various kinds of product. Handlingpayments is just a small part of the overall workflow required to process anorder. For the company whose application we’re looking at, order processinglooks something like:

oIf we accept an order over the web, then we have towait for payment to arrive, unless it’s a credit-card order. In the case ofcredit card orders, we can process the order immediately and ship the goods,but only if the goods are in stock. If they are not currently in stock, we haveto delay processing credit card orders until the become available again.

oWe can receive a check in payment of an existingorder, in which case we ship the goods (unless they are not in stock, in whichcase we hold the check until the goods become available).

oWe can receive a purchase order (PO) for a new order(we only accept these from companies with an established credit account). Inthis case we ship the goods (assuming they are in stock) and also generate aninvoice against the PO. At some point in the future we’ll receive paymentagainst this invoice and the order will be complete.

oAt any time before the goods are shipped the customermay cancel an order.

Each step in this process may occur many days afterthe previous step. For example, we may take an order over the web on Monday,receive a check for this order on Thursday, then ship the goods and deposit thecheck when the item is received from our own suppliers on the followingTuesday.

How can we design an application to support thesekinds of rules? Of course, businesses change the rules all the time, so webetter make sure that anything we come up makes it easy to update the workflow.

·Kata Eighteen: TransitiveDependencies

Let’s write some code that calculates how dependenciespropagate between things such as classes in a program.

Highly coupled code is code where the dependenciesbetween things are dense, lots of things depend on other things. This kind ofprogram is hard to understand, tough to maintain, and tends to be fragile,breaking easily when things change.

There are many different kinds of coupling in code.One of the easiest to work with programatically is \emph{static coupling},where we’re concerned with the relationships between chunks of source code.Simplisticly, we can say that class A is statically coupled to class B if thecompiler needs the definition of B in order to compile

A.Inmany languages, static dependencies can be determined by source

code analysis. Tools such as makedepend (for Cprograms) and JDepend (for Java) look for explicit dependencies in the sourceand list them out.

One of the insidious things about dependencies is thatthey are transitive—if A depends on B and B depends on C, then A also dependson C. So, let’s write some code that calculates the full set of dependenciesfor a group of items. The code takes as input a set of lines where the firsttoken is the name of an item. The remaining tokens are the names of things thatthis first item depends on. Given the following input, we know that A directlydepends on B and C, B depends on C and E, and so on.

  A   B   C
  B   C   E
  C   G
  D   A   F
  E   F
  F   H

The program should use this data to calculate the fullset of dependencies. For example, looking at B, we see it directly depends on Cand E. C in turn relies on G, E relies on F, and F relies on H. This means thatB ultimately relies on C, E, F, G, and H. In fact, the full set of dependenciesfor the previous example is:

  A   B C E F G H
  B   C E F G H
  C   G
  D   A B C E F G H
  E   F H
  F   H

Let’s express this using unit tests. The followingcode assumes that our dependency calculator is a class with an add_direct()method to add the direct dependencies for an item, and a dependencies_for()method to return the full list of dependencies for an item. The code usesRuby’s %w{…} construct, which builds an array of strings from its argument.

  def test_basic
    dep = Dependencies.new
    dep.add_direct('A', %w{ B C } )
    dep.add_direct('B', %w{ C E } )
    dep.add_direct('C', %w{ G   } )
    dep.add_direct('D', %w{ A F } )
    dep.add_direct('E', %w{ F   } )
    dep.add_direct('F', %w{ H   } )
    assert_equal( %w{ B C E F G H },   dep.dependencies_for('A'))
    assert_equal( %w{ C E F G H },     dep.dependencies_for('B'))
    assert_equal( %w{ G },             dep.dependencies_for('C'))
    assert_equal( %w{ A B C E F G H }, dep.dependencies_for('D'))
    assert_equal( %w{ F H },           dep.dependencies_for('E'))
    assert_equal( %w{ H },             dep.dependencies_for('F'))
  end

Stop reading now, and start coding. Once you’ve gotyour code working, try feeding it the following dependencies.

  A B
  B C
  C A

Does it work correctly? If not, ask yourself is thisis a condition you should have considered during testing.

Once you’ve got your code working with all the varioustest cases you can imagine, let’s think for a minute about performance. Say wewere using this code to find all the relationships between the inhabitants ofthe United Kingdom. How would your code perform with 55-60 million items?

·Kata Nineteen – Word Chains

Write a program that solves word-chain puzzles.

There’s a type of puzzle where the challenge is tobuild a chain of words, starting with one particular word and ending withanother. Successive entries in the chain must all be real words, and each candiffer from the previous word by just one letter. For example, you can get from“cat” to “dog” using the following chain.

  cat
  cot
  cog
  dog

The objective of this kata is towrite a program that accepts start and end words and, using words from thedictionary,builds a word chain between them. For added programming fun, return theshortest word chain that solves each puzzle. For example, my Powerbook runningRuby can turn “lead” into “gold” in four steps (lead, load, goad, gold), takingabout 20 seconds. Turning “ruby” into “code” takes six steps (ruby, rubs, robs,rods, rode, code) and 90 seconds, while turning “code” into “ruby” (again insix steps) takes about an hour. Go figure…

Update: It turns out that my original algorithm waspretty dumb: a better approach greatly speeds up search and makes itsymetrical. It now does all the above examples in between 0.5s and 1s.

·Kata Twenty: Klondike

Experiment with different heuristics for playing thesolitaire game Klondike.

The solitaire game Klondike is probably the mostwidely played in the world (particularly if you’re a Window’s user, where ithas been available since Windows 3.1). It’s a simple game.

Cards are dealt face down ontothe seven piles in the tableau. The first pile receives one card, the next two,up to the seventh pile, which not surprisingly has seven cards initially. Thetop card on each pile is turned face-up, and the undealt cards are placed onthe Stock pile, all face down. Here’s apictureof the game (52kb)ifyou haven’t seen it before.

The idea is to build up four piles of cards in theirsuits on the foundation area (one pile for the clubs, one for the diamonds, andso on). The piles must start with the ace and end with the king.

The available moves (in no particular order) are:

oIf the Stock becomes empty, turn the entire discardpile over and make it the new Stock.

oTurn over the top card of the Stock and place itface-up on the Discard pile.

oMove a card from the tableau or discard pile to one ofthe foundation piles. If the foundation pile is empty, only an Ace can beplaced there, otherwise only the next highest card in the appropriate suit canbe placed (so if a foundation pile is currently showing a four of hearts, onlythe five of hearts may be placed there).

oMove the top card of the discard pile to one of thetableau piles. This card must be one less in rank and opposite in color to thecard at the top of the destination tableau.

oMove one or more cards from one tableau pile toanother. If multiple cards are moved, they must be a sequence ascending in rankand alternating in color. The card moved (or the top of the sequence moved)must be one less in rank and opposite in color to the card at the top of thedestination tableau. If the move leaves a face-down card to the top of theoriginal pile, turn it over.

oIf a move leaves a tableau pile empty, an exposed Kingat the top of a tableau or discard pile, or a sequence starting with a King ona tableau pile, may be moved to it.

So, in the game pictured about, a possible first setof moves might be:

6.Move the Queen of Hearts onto the King of Clubs.

7.This leaves the first pile in the tableau empty, sothe combined King/Queen can be moved to it. The card originally beneath theKing is turned over.

8.The Jack of Clubs can be moved on to the Queen ofHearts, and the card beneath the Jack revealed.

The game is won when all cards are moved to thefoundation, and lost when the only remaining moves form an endless loop.

The game is simple to play, but the strategy isn’timmediately obvious. For example, is it always a good idea to move a card fromthe tableau to the foundation, or is it sometimes better to leave it there togive yourself something to build down on? Is it a good idea to make a movewhich leaves a tableau pile empty if you don’t immediately have a King to moveinto the gap? If you have two possible moves which will result in exposing anew tableau card, should you expose the one on the longest or shortest tableau?

This kata is in two parts.

9.Come up with an infrastructure so you can have thecomputer deal and play games of Klondike.

10.Use that infrastructure to experiment with strategiesto see if you can increase the number of times you win (perhaps you couldtabulate the number of times the machine wins a random set of 1,000 games foreach strategy you try).

Objectives

oAt one level, this is an exercise in design—how canthe basic game be modeled in code? Where should the validation of moves belocated (in the cards, in the various piles, in some kind of game overseer, or…)? How can we detect that we’ve lost?

oOnce the basic game is in place, it becomes anexercise in imagination: what strategies can be applied, and how do varioussub-strategies interact?

·Kata Twenty One: Simple Lists

Perhaps there’s more to the humble list of values thanyou might think. Let’s experiment with some basic list processing.

Lists are one of the first data structures we learn asprogrammers. But familiarity doesn’t mean that we can’t learn a little fromthem.

For this kata we’re going to code up threeimplementations of a list that has the following basic interface:

oThe list consists of nodes. Each node has a stringvalue, along with whatever housekeeping the list itself needs.

oNew nodes are added to the end of the list.

oYou can ask the list if it contains a given string. Ifit does, it returns the node containing that string.

oYou can delete a node from the list.

oYou can ask the list to return an array of all itsvalues.

Here’s a basic set of unit tests to illustrate thebehavior.

    list = List.new
    assert_nil(list.find("fred"))
    list.add("fred")
    assert_equal("fred", list.find("fred").value())
    assert_nil(list.find("wilma"))
    list.add("wilma")
    assert_equal("fred",  list.find("fred").value())
    assert_equal("wilma", list.find("wilma").value())
    assert_equal(["fred", "wilma"], list.values())
    list = List.new
    list.add("fred")
    list.add("wilma")
    list.add("betty")
    list.add("barney")
    assert_equal(["fred", "wilma", "betty", "barney"], list.values())
    list.delete(list.find("wilma"))
    assert_equal(["fred", "betty", "barney"], list.values())
    list.delete(list.find("barney"))
    assert_equal(["fred", "betty"], list.values())
    list.delete(list.find("fred"))
    assert_equal(["betty"], list.values())
    list.delete(list.find("betty"))
    assert_equal([], list.values())

For the kata, where the idea is to practice, let’swrite three implementations of the list:

5.A singly linked list (each node has a reference to thenext node).

6.A doubly linked list (each node has a reference to boththe next and previous nodes).

7.Some other implementation of your choosing, exceptthat it should use no references (pointers) to collect nodes together in thelist.

Obviously, we won’t be using predefined libraryclasses as our list implementations…

Objectives

There’s nothing magical or surprising in listimplementations, but there are a fair number of boundary conditions. Forexample, when deleting from the singly-linked list, did you have to deal withthe case of deleting the first element in the list specially?

For this kata, concentrate on ways of removing as manyof these boundary conditions as possible. Then ask yourself: Is the resultingcode, which will contain fewer special cases, easier to read and maintain? Howeasy was it to eliminate these special cases? Were there trade-offs, whereremoving a special case in one area complicated the code in another. Is there asweet-spot when it comes to simplifying code?

点赞