Daniel Dyba

Self-directed learning happens here

Getting Started With JsTestDriver

| Comments

While reading Test-Driven JavaScript Development by Christian Johansen, I decided to give Google’s JsTestDriver a spin. JsTestDriver is a testing framework for JavaScript.

The 5,000 Foot View

There are three components involved when using JsTestDriver:

  • The JsTestDriver server
  • The Web Browser
  • The JsTestDriver runner

The JsTestDriver server must be started first from the Terminal. Next, you need to choose the web browser you will use to run your JavaScript tests. You can run your tests in more than one browser but I imagine it will probably get annoying and cluttered. The JsTestDriver runner must be run from your Terminal to see if your tests are passing. With that understanding, let’s move on to getting things set up.

Downloading JsTestDriver

If you have Homebrew on your machine, you can run the command:

1
$ brew install js-test-driver

Setting up your Environment

Installing via HomeBrew will install the JAR file in the directory: /usr/local/Cellar/js-test-driver/1.3.5/libexec/. You need to set the environment variable JSTESTDRIVER_HOME in your .bash_profile file:

1
export JSTESTDRIVER_HOME=/usr/local/Cellar/js-test-driver/1.3.5/libexec

Configuring JsTestDriver

Before you run JsTestDriver, you need to specify a configuration file. The default filename that JsTestDriver recognizes is jstestdriver.conf. Here is what my configuration file looks like:

1
2
3
4
5
6
7
server: http://localhost:9876

load:
  - src/*.js

test:
  - test/*.js

This is a file written in YAML syntax. I specified a random port 9876 for our server. By default, JsTestDriver will listen on port 4224. Because I specified the * character, JsTestDriver will load all the source files from the src folder and test files from the test directory with a .js extension. Be sure to only use spaces in your configuration file; don’t use tabs.

Running the JsTestDriver Server

To run JsTestDriver, you need to type this command in the Terminal:

1
java -jar $JSTESTDRIVER_HOME/JsTestDriver-1.3.5.jar --port 9876

We pass the JAR file to the -jar argument. In our case, the JsTestDriver file is located inside the directory we specified earlier. If you downloaded the JAR file elsewhere, you’ll have to write out the correct path here.

Next, you have to specify the same port you specified in your configuration file.

Selecting a Browser To Capture

In the web browser or browsers on which you want to run your tests, you need to type http://localhost:9876 or whatever port number you decided to use. A page will load with two links as shown below. Choose the link to “Capture This Browser”.

If all goes well, you should see a green band informing you of the state of the JsTestDriver server.

By the way, if you quit running the JsTestDriver server, the browser will inform you of its connection to the server. It will retry connecting to the server…

… before it finally acknowledges that it is dead:

One more thing: if you don’t need to run your tests in the browser for whatever reason, you can skip this step of capturing the browser.

A Simple Test File

We are going to reuse the test spec shown on the JsTestDriver project page. We will call this file greeter_test.js:

1
2
3
4
5
6
7
8
// test/greeter_test.js

GreeterTest = TestCase("GreeterTest");

GreeterTest.prototype.testGreet = function() {
    var greeter = new myapp.Greeter();
    assertEquals("Hello World!", greeter.greet("World"));
}

We also could have written it this way:

1
2
3
4
5
6
7
8
// test/greeter_test.js

TestCase("GreeterTest",
  "Testing a simple greet function": function() {
    var greeter = new myapp.Greeter();
    assertEquals("Hello World!", greeter.greet("World"));
  }
});

Running the JsTestDriver Runner

At this point, we are ready to run the JsTestDriver runner and see the feedback we get. Our test should fail, and JsTestDriver confirms this when we run the command java -jar $JSTESTDRIVER_HOME/JsTestDriver-1.3.5.jar --tests all:

1
2
3
4
5
6
7
8
$ java -jar /usr/local/Cellar/js-test-driver/1.3.5/libexec/JsTestDriver-1.3.5.jar --tests all
setting runnermode QUIET
E
Total 1 tests (Passed: 0; Fails: 0; Errors: 1) (1.00 ms)
  Chrome 26.0.1410.65 Mac OS: Run 1 tests (Passed: 0; Fails: 0; Errors 1) (1.00 ms)
    GreeterTest.testGreet error (1.00 ms): ReferenceError: myapp is not defined
      ReferenceError: myapp is not defined
          at GreeterTest.testGreet (http://localhost:9876/test/test/greeter_test.js:4:23)

Then we write the code to make this test pass:

1
2
3
4
5
6
7
8
9
// src/greeter.js

myapp = {}

myapp.Greeter = function() { };

myapp.Greeter.prototype.greet = function(name) {
    return "Hello " + name + "!";
};

Now when you run the java command to run JsTestDriver, you will see your test passing:

1
2
3
4
5
$ java -jar /usr/local/Cellar/js-test-driver/1.3.5/libexec/JsTestDriver-1.3.5.jar --tests all
setting runnermode QUIET
.
Total 1 tests (Passed: 1; Fails: 0; Errors: 0) (0.00 ms)
  Chrome 26.0.1410.65 Mac OS: Run 1 tests (Passed: 1; Fails: 0; Errors 0) (0.00 ms)

Jstdutil, a Ruby Wrapper

Tired of running the same Java command over and over? Christian Johansen wrote a Ruby wrapper to automate that task. You can find it on Github under the gem name jstdutil.

Let’s include the gem in our Gemfile:

1
2
3
4
5
source "https://rubygems.org"

group :test do
  gem 'jstdutil'
end

Now run bundle install to have bundler grab the gem.

Remember to repeat the same process as when we ran the Java command by hand:

  • Run the server in the background
  • Capture the browser
  • Run the runner

To run the server in the background, we issue the command in the Terminal:

1
$ bundle exec jstestdriver --port 9876

Assuming you’ve captured the browser, you’ll then want to run the runner:

1
$ bundle exec jstestdriver --tests all

The neat thing about using jstdutil is the colored output on the Terminal. The gem also comes with an autotesting feature. If you want to run the JsTestDriver runner every time the files change, use jsautotest:

1
$ bundle exec jsautotest

Conclusion

I hope you now have a better understanding of how to use JsTestDriver. If you found it useful, share it on Twitter or your social networking platform of choice. I’d also love to hear your feedback. Write it below in the comments.

I included a Pitfalls section below to address some common problems when trying to use JsTestDriver.

Pitfalls

Invalid YAML

Make sure you have written valid YAML. You will get parsing errors if you use tabs in your YAML file. The error below occurred because I used tabs to indent before the - character when specifying the directories to load.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ java -jar /usr/local/Cellar/js-test-driver/1.3.5/libexec/JsTestDriver-1.3.5.jar --port 9676
while scanning for the next token
found character     (9 that cannot start any token
org.jvyaml.ScannerException: ScannerException while scanning for the next token we had this found character    (9 that cannot start any token
  at org.jvyaml.ScannerImpl.fetchMoreTokens(ScannerImpl.java:359)
  at org.jvyaml.ScannerImpl.peekToken(ScannerImpl.java:160)
  at org.jvyaml.ParserImpl$21.produce(ParserImpl.java:410)
  at org.jvyaml.ParserImpl.parseStreamNext(ParserImpl.java:796)
  at org.jvyaml.ParserImpl.peekEvent(ParserImpl.java:747)
  at org.jvyaml.ComposerImpl.composeNode(ComposerImpl.java:106)
  at org.jvyaml.ComposerImpl.composeNode(ComposerImpl.java:168)
  at org.jvyaml.ComposerImpl.composeDocument(ComposerImpl.java:95)
  at org.jvyaml.ComposerImpl.getNode(ComposerImpl.java:71)
  at org.jvyaml.BaseConstructorImpl.getData(BaseConstructorImpl.java:88)
  at org.jvyaml.YAML.load(YAML.java:188)
  at org.jvyaml.YAML.load(YAML.java:165)
  at com.google.jstestdriver.config.YamlParser.parse(YamlParser.java:51)
  at com.google.jstestdriver.config.DefaultConfigurationSource.parse(DefaultConfigurationSource.java:56)
  at com.google.jstestdriver.embedded.JsTestDriverBuilder.setConfigurationSource(JsTestDriverBuilder.java:258)
  at com.google.jstestdriver.Main.main(Main.java:64)
Unexpected Runner Condition: ScannerException while scanning for the next token we had this found character   (9 that cannot start any token
 Use --runnerMode DEBUG for more information.

Conflicting Ports

Also make sure that you use the same port in the --port argument when you start the JsTestDriver server as the one you specified in your .conf file, otherwise you’ll get an error about the server not being able to start. The error below occurred because I specified port 6666 in my configuration file but ran JsTestDriver with a different port.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ java -jar /usr/local/Cellar/js-test-driver/1.3.5/libexec/JsTestDriver-1.3.5.jar --port 9876
setting runnermode QUIET
java.lang.RuntimeException: Connection error on: sun.net.www.protocol.http.HttpURLConnection:http://localhost:6666/jstd/gateway
  at com.google.jstestdriver.HttpServer.postJson(HttpServer.java:162)
  at com.google.jstestdriver.action.ConfigureGatewayAction.run(ConfigureGatewayAction.java:70)
  at com.google.jstestdriver.ActionRunner.runActions(ActionRunner.java:81)
  at com.google.jstestdriver.embedded.JsTestDriverImpl.runConfigurationWithFlags(JsTestDriverImpl.java:342)
  at com.google.jstestdriver.embedded.JsTestDriverImpl.runConfiguration(JsTestDriverImpl.java:233)
  at com.google.jstestdriver.Main.main(Main.java:70)
Caused by: java.net.ConnectException: Connection refused
  at java.net.PlainSocketImpl.socketConnect(Native Method)
  at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:351)
  at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:213)
  at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:200)
  at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:432)
  at java.net.Socket.connect(Socket.java:529)
  at java.net.Socket.connect(Socket.java:478)
  at sun.net.NetworkClient.doConnect(NetworkClient.java:163)
  at sun.net.www.http.HttpClient.openServer(HttpClient.java:388)
  at sun.net.www.http.HttpClient.openServer(HttpClient.java:523)
  at sun.net.www.http.HttpClient.<init>(HttpClient.java:227)
  at sun.net.www.http.HttpClient.New(HttpClient.java:300)
  at sun.net.www.http.HttpClient.New(HttpClient.java:317)
  at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:970)
  at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:911)
  at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:836)
  at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1014)
  at com.google.jstestdriver.HttpServer.postJson(HttpServer.java:154)
  ... 5 more
Unexpected Runner Condition: Connection error on: sun.net.www.protocol.http.HttpURLConnection:http://localhost:6666/jstd/gateway
 Use --runnerMode DEBUG for more information.

Sources

  1. Getting Started, https://code.google.com/p/js-test-driver/wiki/GettingStarted
  2. Need Help with Error Message: java.lang.RuntimeException: Could not start browser CommandLineBrowserRunner https://groups.google.com/group/js-test-driver/browse_frm/month/2012-05?fwc=1&pli=1
  3. Jstdutil - A Ruby wrapper over JsTestDriver http://cjohansen.no/en/javascript/jstdutil_a_ruby_wrapper_over_jstestdriver

Writing My First Usage Narrative

| Comments

In an effort to understand the analysis and design of object-oriented systems, I picked up a book by Alistair Cockburn (pronounced COH-burn) called Writing Effective Use Cases. Building object-oriented systems can become quite complex. If you haven’t thoroughly analyzed how your system is supposed to work, you may come to wonder whether or not you are meeting the needs of the stakeholders. This is where use cases come in to play. According to Cockburn, a use case “captures a contract between the stakeholders of a system about its behavior” (Cockburn 1). But if you’ve never written a use case before, you might wonder how you would begin. This is where usage narratives come in to play.

Usage Narratives: Catalysts for Writing Use Cases

Toward the end of the first chapter, Cockburn talks about writing usage narratives as a way to warm up prior to writing use cases. Usage narratives are stories. It is a story with a specific actor/actress in mind. When I say actor/actress (for simplicity, ‘actor’), I mean the main character of the story who will use the system you are designing. To keep the reader engaged in the usage narrative, it helps to be very specific with your actor and to add just enough details to paint a picture of who the actor is. A usage narrative will help you see how your system will be used. It will put you in the right mode of thinking to delve deeper if needed when you write your use cases.

One of the exercises in the chapter asks you to write a usage narrative for the ATM you use. Here is my first run at it:


Usage Narrative: Withdraw Cash

Daniel, a 27-year-old absent-minded engineer who keeps track of his expenditures by saving every receipt, walks up to the ATM. He slides his card in the ATM. He selects “Withdraw Cash”. He selects the amount $100. When prompted by the system to finish his transaction, he selects “Yes”. The system asks if he would like a receipt. He selects “Yes”. The ATM prints out a receipt. Next, the ATM alerts Daniel that it has ejected his card to avoid having him forget to retrieve it. Daniel takes his card and heads home.


Then, I read Cockburn’s example:


Usage Narrative: Getting “Fash Cash”

Mary, taking her two daughters to the day care center on the way to work, drives up to the ATM, runs her card across the card reader, enters her PIN code, selects FAST CASH, and enters $35 as the amount. The ATM issues a $20 and three $5 bills, plus a receipt showing her account balance after the $35 is debited. The ATM resets its screens after each transaction with FAST CASH, so Mary can drive away and not worry that the next driver will have access to her account. Mary likes FAST CASH because it avoid the many questions that slow down the interaction. She comes to this particular ATM because it issues $5 bills, which she uses to pay the day care provider, and she doesn’t have to get out of her car to use it. (Cockburn 18)


After reading his example, I thought: I never got my money! Only after having read his example and then read mine did I notice that my ATM machine never dispensed the $100 I requested! I thought it funny that I described myself as absent-minded before noticing I had forgot to pick up my money from the ATM. I probably revealed more than I had wanted in this example. There’s nothing wrong about the way I wrote my usage narrative; however, I missed some important details. This isn’t a bad thing especially since I realized this quickly enough. The key is that I recognized what was missing upon reading it.

Comparing and Contrasting

Let’s take a closer look at how I could have improved my usage narrative.

To start off, I could have been more specific with the amount of money I received from the ATM. I probably don’t want to get back a $100 bill. I would rather get $20 bills because it is more likely I will get change easily. Another thing I could have done is elaborate on how this ATM provides value to me. I noticed that Cockburn’s example mentions three key reasons why this ATM has value to Mary:

  1. It issues $5 bills. Mary can use the $5 bills to pay the day care provider.
  2. It is accessible. She doesn’t have to get out of her car and walk up to it. She can use it from the driver’s seat of a vehicle.
  3. It dispenses cash quickly. The ATM has a FAST CASH feature that make withdrawals a snap.

My usage narrative did include one reason how this ATM provides value to me although it wasn’t made as clear as Cockburn’s example. I described myself as being absent-minded and one who collects every receipt in order to track my expenditures. This particular ATM does provide a receipt printout which is exactly the feature that meets my needs.

I tried to be specific with the character I used in my example. In particular, because I noted my actor as being absent-minded, it became clear that the ATM needed to have a feature that reminded the actor to retrieve their card after dispensing cash. In that respect, my example is comparable in quality to the detailed description of the actor in Cockburn’s example. Since I noted this ATM reminds its users to retrieve their card, one can argue that I implicitly demonstrated the value of the ATM in my example: by alerting the user when the transaction is complete, the ATM reminds the user to not forget their card at the ATM.

Essential Elements of a Usage Narrative

Now that you’ve had a chance to look at what a usage narrative looks like, I will wrap up with the elements that are essential to writing a usage narrative according to Cockburn. A usage narrative must be brief in order for the reader to comprehend it easily. It must capture the mental state of the actor who will be using the system, that is, “why he wants what he wants or what conditions drive him to act as he does” (Cockburn 18). He goes on to say that “details and motives, or emotional content, are important so that every reader… can see how the system should be optimized to add value to the user” (Cockburn 18). Thus, a usage narrative must:

  • Be brief
  • Capture the mental state of the actor using the system
  • Demonstrate value to the actor

Combining the above elements in a usage narrative will help you see the system in use. Cockburn also reminds us that “the narrative is not the requirements; rather, it sets the stage for more detailed and generalized descriptions of the requirements” (Cockburn 18).

Your Turn

The next time you try writing use cases, consider writing a usage narrative instead to warm you up for the real thing. You could even try writing a usage narrative for the ATM you go to and add it in the comments below.

Bibliography

Cockburn, Alistair. Writing Effective Use Cases. Boston: Addison-Wesley, 2001. Print

Code Kata 4: A TDD Attempt Toward Readable Code

| Comments

Code Kata #4 - Data Munging Part One

Overview

The fourth Code Kata in Dave Thomas’ Code Kata blog asks you to do the following:

write a program to output the day number (column one) with the smallest temperature spread (the maximum temperature is the second column, the minimum the third column).

He provides a weather.dat file for you to download and which you will parse through in order to obtain the solution. In this post, I will only cover the first part of the code kata. I completed the first part of the kata over the course of 3 days working up to 4 hours on one day and thinking about the problem when not in front of the computer screen. Let’s take a look at a solution I developed.

The Completed Code

WeatherReader program atlink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class WeatherReader
  attr_reader :data, :spread

  def read(filename)
    @data = File.readlines(filename).reject(&no_data)
  end

  def date_of_smallest_spread
    @data.map(&spread).find_index(smallest_spread) + 1
  end

  def smallest_spread
    @data.map(&spread).min
  end

  def spread
    @spread ||= ->(line) { (temperature(line, :max) - temperature(line, :min)).abs }
  end

  private

    def temperature(line, kind)
      line.match(regex)[kind].to_i
    end

    def no_data
      ->(line) { line.match(regex).nil? }
    end

    def regex
      %r{
        (?<day> \d+){0}
        (?<max> \d+){0}
        (?<min> \d+){0}

        \g<day>\s+\g<max>\s+\g<min>
      }x
    end
end
RSpec Exampleslink
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
require 'rspec'
require 'spec_helper'

describe WeatherReader do
  describe "Utility methods" do
    let(:weather_reader) { WeatherReader.new }

    before(:each) do
      @data = weather_reader.read("weather.dat")
    end

    it "filters lines that don't contain weather data" do
      @data.should_not include(" MMU June 2002\n")
    end

    context "given a line containing weather data for a day" do
      let(:line) { "   1  88    59    74          53.8       0.00 F       280  9.6 270  17  1.6  93 23 1004.5" }
      it "returns the difference between the maximum temperature and minimum temperature" do
        weather_reader.spread.call(line).should == 29
      end
    end

    context "given lines containing weather data for several days" do
      it "returns the smallest temperature spread" do
        weather_reader.smallest_spread.should == 2
      end

      it "returns the date that had the smallest temperature spread" do
        weather_reader.date_of_smallest_spread.should == 14
      end
    end
  end
end
Contents of the weather.dat file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
(Unofficial, Preliminary Data). Source: <a
href="http://www.erh.noaa.gov/er/box/dailystns.html">www.erh.noaa.gov/er/box/dailystns.html</a>

<pre>
 MMU June 2002

  Dy MxT   MnT   AvT   HDDay  AvDP 1HrP TPcpn WxType PDir AvSp Dir MxS SkyC MxR MnR AvSLP

   1  88    59    74          53.8       0.00 F       280  9.6 270  17  1.6  93 23 1004.5
   2  79    63    71          46.5       0.00         330  8.7 340  23  3.3  70 28 1004.5
   3  77    55    66          39.6       0.00         350  5.0 350   9  2.8  59 24 1016.8
   4  77    59    68          51.1       0.00         110  9.1 130  12  8.6  62 40 1021.1
   5  90    66    78          68.3       0.00 TFH     220  8.3 260  12  6.9  84 55 1014.4
   6  81    61    71          63.7       0.00 RFH     030  6.2 030  13  9.7  93 60 1012.7
   7  73    57    65          53.0       0.00 RF      050  9.5 050  17  5.3  90 48 1021.8
   8  75    54    65          50.0       0.00 FH      160  4.2 150  10  2.6  93 41 1026.3
   9  86    32*   59       6  61.5       0.00         240  7.6 220  12  6.0  78 46 1018.6
  10  84    64    74          57.5       0.00 F       210  6.6 050   9  3.4  84 40 1019.0
  11  91    59    75          66.3       0.00 H       250  7.1 230  12  2.5  93 45 1012.6
  12  88    73    81          68.7       0.00 RTH     250  8.1 270  21  7.9  94 51 1007.0
  13  70    59    65          55.0       0.00 H       150  3.0 150   8 10.0  83 59 1012.6
  14  61    59    60       5  55.9       0.00 RF      060  6.7 080   9 10.0  93 87 1008.6
  15  64    55    60       5  54.9       0.00 F       040  4.3 200   7  9.6  96 70 1006.1
  16  79    59    69          56.7       0.00 F       250  7.6 240  21  7.8  87 44 1007.0
  17  81    57    69          51.7       0.00 T       260  9.1 270  29* 5.2  90 34 1012.5
  18  82    52    67          52.6       0.00         230  4.0 190  12  5.0  93 34 1021.3
  19  81    61    71          58.9       0.00 H       250  5.2 230  12  5.3  87 44 1028.5
  20  84    57    71          58.9       0.00 FH      150  6.3 160  13  3.6  90 43 1032.5
  21  86    59    73          57.7       0.00 F       240  6.1 250  12  1.0  87 35 1030.7
  22  90    64    77          61.1       0.00 H       250  6.4 230   9  0.2  78 38 1026.4
  23  90    68    79          63.1       0.00 H       240  8.3 230  12  0.2  68 42 1021.3
  24  90    77    84          67.5       0.00 H       350  8.5 010  14  6.9  74 48 1018.2
  25  90    72    81          61.3       0.00         190  4.9 230   9  5.6  81 29 1019.6
  26  97*   64    81          70.4       0.00 H       050  5.1 200  12  4.0 107 45 1014.9
  27  91    72    82          69.7       0.00 RTH     250 12.1 230  17  7.1  90 47 1009.0
  28  84    68    76          65.6       0.00 RTFH    280  7.6 340  16  7.0 100 51 1011.0
  29  88    66    77          59.7       0.00         040  5.4 020   9  5.3  84 33 1020.6
  30  90    45    68          63.6       0.00 H       240  6.0 220  17  4.8 200 41 1022.7
  mo  82.9  60.5  71.7    16  58.8       0.00              6.9          5.3
</pre>

TDD is Software Improv

It is unfortunate that I didn’t capture the dynamic interplay between writing/rewriting the tests and writing/rewriting the code in this example. I think I originally heard it from David Chelimsky at SCNA 2011 who said that Test Driven Development is Software Improv. TDD is a sweeping movement involving writing your tests, writing code to pass your tests, refactoring your code, refactoring your tests, critiquing your code to determine how you should write your next test, and critiquing your tests to inform the design of your code. TDD truly is an art form.

Even as I write this post, I understand how difficult it is to capture the TDD process in writing. Have you ever seen Live Improv as a series of photos? For one thing, you won’t catch the humor in the dialogue. You might laugh at the pictures, but you won’t get the jokes unless you had understood the context in which they were delivered; that’s where the dialogue is important. But listening to the dialogue alone isn’t enough. You wouldn’t be able to capture the subtle nuances in the gestures of the performers. The photos wouldn’t capture that because they are minutes, even seconds apart. Now what happens if you see Live Improv as an audience member? You finally get it. In the same way, TDD cannot be expressed as a static blog post. I do not do it justice by simply talking about it; however, I will do my best to inform you what happens in the process of doing TDD.

Debrief

How’d you get there?

At the beginning, I started writing very trivial test cases that would check for the return type of the read method. It would check to make sure that the return values were not nil. Of course, you don’t see these test cases in the final product. That’s because those tests acted like scaffolding that I temporarily used to get me started. After I build the right structure in my program, I wrote other tests that rendered that scaffolding test code obsolete.

TDD is an incremental process. You should only be writing the minimal amount of code to make your tests pass. I think this is where I didn’t do as great a job executing TDD. Once I understood the pattern of the code I needed to write, I went ahead and wrote that as production code. In the real world, the patterns won’t be as obvious and it will take having to write code for every case until you see an overarching picture of the kind of code that would cover the general case.

The example I will cite here is when I test a single line of data to determine if I get the right value for the spread of the maximum and minimum temperature. The next thing I do is write the code that does that for every line of code. Probably the way I should have gone about that was to write a regular expression that was hardcoded to that particular string. Then I should have thrown in another case where there is an asterisk next to the maximum or minimum temperature as is true for day 9 or day 26 in the weather.dat file. When you work on a larger project, it may not be obvious how you can generalize the production code you need to write. Thus, it is a good habit to build your program incrementally when you do TDD.

Programming With Lambdas

Recently, I have been making more of an effort to introduce lambdas in my code. Lambdas and Procs were one of the most nebulous ideas for me when I first started programming in Ruby. Only recently did I start playing with passing lambdas as arguments to methods. Originally, the no_data method wasn’t in my code. It came about during the refactoring phase when I wanted to make the read method more concise (and curiously also wanted to see if I could extract the code block into something more meaningful). The code had read like so:

1
2
3
4
5
def read(filename)
  File.readlines(filename) do |line|
    line.match(regex).nil?
  end
end

Now, there’s nothing wrong with that. However, if I were another programmer trying to read this code, I would be asking myself, “What are you matching against?” So I decided to write this instead:

1
2
3
def read(filename)
  File.readlines(filename).reject(&no_data)
end

To me that reads more clearly than the first example. It may be a little difficult for newer programmers to understand what the syntax is doing. But it is clear that I am rejecting data from the lines of the file I read. Here is the code for the no_data method.

1
2
3
def no_data
  ->(line) { line.match(regex).nil? }
end

The name of the method communicates its function. It defines what data is not; however, I probably could have written a different name for regex. Looking at it right now it doesn’t communicate its intent. Perhaps, I should have written something like this:

1
2
3
def no_data
  ->(line) { line.match(weather_data).nil? }
end

That would have been more helpful to someone who is trying to find out how the no_data method works.

Being Expressive with Regular Expressions

If that same programmer wanted to see what regex would look like, they would see this:

1
2
3
4
5
6
7
8
9
def regex
  %r{
    (?<day> \d+){0}
    (?<max> \d+){0}
    (?<min> \d+){0}

    \g<day>\s+\g<max>\s+\g<min>
  }x
end

Originally, I had written down this expression:

1
2
3
def regex
  /(\d+)\s+(\d+)\s+(\d+)/
end

What is that expression supposed to be doing? It is capturing three groups of numbers that are separated by spaces. But those numbers don’t have any meaning at all! That’s when I decided to use the ‘x’ option to allow spaces in my regular expression. I went ahead and added named subroutines in order to layer the regular expression with meaning. Notice that I named the groups as <day>,<max>,<min>—all names that are relevant to the problem at hand. Unfortunately, those names alone don’t provide much meaning to the problem, I probably should have been a bit more descriptive with the names: “Maximum of what?”, “Minimum of what?” “Are we talking about heights? weights? distances?” A better implementation would have been:

1
2
3
4
5
6
7
8
9
def weather_data
  %r{
    (?<day>             \d+){0}
    (?<max_temperature> \d+){0}
    (?<min_temperature> \d+){0}

    \g<day>\s+\g<max_temperature>\s+\g<min_temperature>
  }x
end

The reason why I didn’t add the names max_temperature, and min_temperature, was because of the way I implemented the temperature method in my program.

Something Smells

Take a look at the following code:

1
2
3
4
5
6
7
8
9
  def spread
    @spread ||= ->(line) { (temperature(line, :max) - temperature(line, :min)).abs }
  end

  private

    def temperature(line, kind)
      line.match(regex)[kind].to_i
    end

Had I changed the names in my regular expressions, I would have had to change the above code to this:

1
2
3
4
5
6
7
8
9
  def spread
    @spread ||= ->(line) { (temperature(line, :max_temperature) - temperature(line, :min_temperature)).abs }
  end

  private

    def temperature(line, kind)
      line.match(regex)[kind].to_i
    end

Why is temperature mentioned twice in a method call? It is adding too much noise. I should have just gotten rid of the temperature method and attempted a better implementation like so:

1
2
3
4
5
6
7
8
9
10
11
  def spread
    @spread ||= ->(line) { (line.match(weather_data)[:max_temperature].to_i - line.match(weather_data)[:min_temperature].to_i).abs }
  end

  private
=begin 
Smells bad
    def temperature(line, kind)
      line.match(regex)[kind].to_i
    end
=end

Even the above implementation is too long to read, but it communicates its intent better than the method I had written.

Take Aways

When doing TDD, you always need to keep in mind the other programmer who will be reading your code. It is not enough to make the program work; it needs to be readable and easy to understand for the rest of your team.

There are other things I didn’t get a chance to cover in this post that would have made it too long: one of those things included the implementation of finding the smallest spread and the date for that spread. I’d like to see your implementation of this code kata. Perhaps there is some other way of implementing this that had not occurred to me. I welcome your insight. I invite you to provide any feedback in the comments below.

String Calculator Kata Without if, Else, Switch, or Case

| Comments

The goal of this kata is to create a calculator that can add the numbers found in a string. I found this kata on Roy Osherove’s website. Here are the instructions for doing this kata:

String Calculator Kata Overview

The Rules

  1. Create a simple String calculator with a method int Add(string numbers)
    1. The method can take 0, 1, or 2 numbers, and will return their sum (for an empty string it will return 0) for example “” or “1”, or “1,2”
    2. Start with the simplest test case of an empty string and move to 1 and two numbers
    3. Remember to solve things as simply as possible so that you force yourself to write tests you did not think about
    4. Remember to refactor after each passing test
  2. Allow the Add method to handle an unknown amount of numbers
  3. Allow the Add method to handle new lines between numbers (instead of commas).
    1. The following input is OK: “1\n2,3” (will equal 6)
    2. The following input is NOT OK: “1,\n” (not need to price it - just clarifying)
  4. Support different delimiters
    1. To change a delimiter, the beginning of the string will contain a separate line that looks like this: “//[delimiter]\n[numbers…]” for example “//;\n1;2” should return three where the default delimiter is ‘;’.

There are more instructions to this kata but that was as far as I was able to get without going too far over the time limit. I imposed a 30 minute time limit to complete this kata.

Constraints Inspire Creative-Thinking

I want to make a very important observation here. I could have solved this kata with programming techniques with which I am familiar; however, familiarity does not breed creativity. If you want to take your creativity to the next level, you need to put constraints. Constraints are your friends. They are the ones that prod you to think about novel ways of doing things. In this kata, I added the constraint of avoiding these keywords:

  • if
  • else
  • switch
  • case

It’s amazing what you’ll discover when you impose these constraints. I began to write my solution in a Lispy sort of fashion as you’ll soon see.

The Completed Kata

The Completed String Calculator Kata
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
require 'rspec'

class StringCalculator

  def add(string)
    string.empty? && (return 0)
    string.end_with?("\n") && (return nil)

    string.start_with?("//") &&
      (@delimiter ||= string.match('^//(.)')[1]) &&
        string.sub!(/^\/\/(.)/, '')

    string.include?("\n") &&
      string.gsub!(/\n/, ' ')

    @delimiter &&
      (return string.split("#{@delimiter}").map(&:to_i).inject(&:+))

    string.split(/[,\s]/).map(&:to_i).inject(&:+)
  end

end

describe StringCalculator do
  let(:calc) { StringCalculator.new }

  it "returns 0 for empty string" do
    calc.add("").should == 0
  end

  it "returns 1 for '1'" do
    calc.add("1").should == 1
  end

  it "returns 3 for '1,2'" do
    calc.add("1,2").should == 3
  end

  it "returns 55 for '1,2,3,4,5,6,7,8,9,10'" do
    calc.add("1,2,3,4,5,6,7,8,9,10").should == 55
  end

  it "returns 6 for '1\\n2,3'" do
    calc.add("1\n2,3").should == 6
  end

  it "returns nil for '1,\\n'" do
    calc.add("1,\n").should be_nil
  end

  context "when changing the delimiter to ';'" do
    it "returns 3 for '//;\\n1;2" do
      calc.add("//;\n1;2").should == 3
    end
  end

  context "when changing the delimiter to ': and terminating the string with '\\n'" do
    it "returns nil for '//:\\n1:\\n" do
      calc.add("//:\n1:\n").should be_nil
    end
  end
end

Lispifying The Solution

Since I wasn’t able to use the if, else, switch, and case keywords, I had to think of a new way of having my program make decisions. I observed that I was using methods that returned booleans such as empty?, start_with?, end_with?, and include?. My mind was screaming at me: “USE AN IF STATEMENT HERE!” I refrained. All I knew was that I wanted my program to make decisions based on whether or not the results of those method calls were true. Then I had my ‘Ah ha!’ moment. Part of what led me to use the && operator in my code came about because I am learning Common Lisp. Here is a snippet of Lisp code that checks to see if an atom belongs to a list:

1
2
3
4
5
6
(defun member? (a lst)
  (cond
    ((null lst) nil)
    (T (or
         (eq a (car lst))
         (member? a (cdr lst))))))

Take a look at the OR statement. If the atom a is equal to the first item of the list in lst, then member? returns true and it exits the function; otherwise it will evaluate the statement member? and pass the atom a and the remaining items in the lst not including the first item. Did you see how you can use an a function call as a statement to be used with an or operator? In my solution, I went ahead and using this logic except I used the && operator to accomplish this. Let’s walk through some of the code in the solution.

A Walkthrough

We begin by recalling the requirements of the string calculator. The first requirement is to return 0 for an empty string. To meet that requirement, I write code to query the string and ask if it is empty?. When I pass an empty string, the response will be true. Since the statement to the left of the && operator is true, the && must take the next step of evaluating the statement to the right.

1
 string.empty? && (return 0)

The statement on the right is a return statement, so we leave the add function with the result of 0 for an empty string. The same logic applies to the next call end_with?.

1
string.end_with?("\n") && (return nil)

If the string ends with a newline character "\n", the && operator is forced to execute the statement to the right.

In the next few lines, I took the && operator further. I know that all Ruby objects return true. With that understanding, I went ahead and chained three statements with the && operator:

1
2
3
string.start_with?("//") &&
  (@delimiter ||= string.match('^//(.)')[1]) &&
    string.sub!(/^\/\/(.)/, '')

Doesn’t that cascading style remind you of Lisp? I think this is really cool! This code covers the case when the user wants to change the delimiter in the string. Instead of using commas and passing a string such as "1,2", the user can now pass a string like this: "//;\n1;2". Passing this kind of string to the add method should change the delimiter from a comma to a semicolon. Let’s suppose we receive the string "//;\n1;2". We see it starts with "//" so the method start_with? returns true. Since we are &&ing our statements together, all of them must return true in order for all three to be executed, otherwise, we will skip to the next portion of the program. The next step involves capturing the new delimiter and caching the results to the @delimiter instance variable. That entire statement returns true because at the end @delimiter has a non-nil value. We move on to the next statement, which removes the delimiter information from the original string so that we can parse the number information.

This next line is my favorite one.

1
2
@delimiter &&
  (return string.split("#{@delimiter}").map(&:to_i).inject(&:+))

Since we received a cached result from the previous chain of statements for @delimiter, @delimiter is a non-nil object which evaluates to true. Since @delimiter evaluates to true, we move to the next statement and evaluate it. I especially like how I passed the methods to_i and + as symbols to the map and inject methods. It saved me space and communicates its intent! We split on the new delimiter, convert each string represented as a number to an integer, and then collect a running sum and return the result.

Take Aways

The most valuable thing I got from having done this kata were the constraints I placed on myself. They pushed me outside of the comfort of the familiar techniques I used and demanded more from me. I did notice the repetition toward the bottom of the add method. I had some difficulty refactoring the code in time. Perhaps next time I perform this kata, I’ll have a DRYer version.

Ruby Monk Problem - Capitalizing an Array With Names (Revisited)

| Comments

Another Look

This is a follow up to my previous blog post on the Ruby Monk problem. The challenge with which I ended that post was to solve the problem with the constraint of using the inject method. Here’s a solution that meets that challenge:

1
2
3
["emperor", "john", "abraham", "norton"].map(&:capitalize).inject do |full_name, name|
  "#{full_name} #{name}"
end

Instead of initializing the inject method with an empty string, I used the map method to apply the capitalize method on each of the elements in the array. Once I have done that, then using method chaining, I can start combining each element in the array in a cumulative manner with the inject method.

Let’s Race

Here’s an interesting question: If I had several thousands elements in the array, which solution would be better in terms of performance? It would probably be more efficient to use the hack in the previous post. In this solution, I am iterating through the array twice: once using the map method and a second time using the inject method.

I use Benchmark to give me an idea of what performance looks like between the two solutions.

Benchmarking the new solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
require 'benchmark'

names = []

10000.times do
  names << "emperor".split(//).shuffle.join
end

Benchmark.bm do |trial|
  trial.report("Map & inject:             ") do
    names.map(&:capitalize).inject do |full_name, name|
      "#{full_name} #{name}"
    end
  end
  trial.report("Inject with initial value:") do
    names.inject('') do |full_name, name|
      "#{full_name} #{name.capitalize}".strip
    end
  end
end

Double-take

Take a look at these results:

Benchmark Results
1
2
3
                            user     system      total        real
Map and inject:             0.310000   0.100000   0.410000 (  0.399476)
Inject with initial value:  0.500000   0.190000   0.690000 (  0.691769)

The new solution is 40% faster than the solution in my previous post! This was not what I expected at all! It seems counter-intuitive that iterating through an array twice is faster than iterating through it once.

Final Note

Yup, I’m guilty of having relied too readily on my assumptions. This will serve as a reminder to keep asking questions that challenge my thinking. What may seem obvious may not be so.

Ruby Monk Problem - Capitalizing an Array of Names

| Comments

The Exercise

In lesson 5 of RubyMonk.com, there’s an exercise with the following objective:

to capitalize the first character of each part of the name as it combined them.

The code they provide is:

1
2
# bust out a beefy reduce on this bad boy:
["emperor", "joshua", "abraham", "norton"]

First Attempt

I worked out the code below but then realized it didn’t solve the problem.

1
2
3
["emperor", "joshua", "abraham", "norton"].inject do |full_name, name|
  "#{full_name} #{name.capitalize}"
end

This ends up with "emperor Joshua Abraham Norton". I didn’t capitalize the first word emperor.

Second Attempt

Ok, no problem. I’ll just capitalize full_name.

1
2
3
["emperor", "joshua", "abraham", "norton"].inject do |full_name, name|
  "#{full_name.capitalize} #{name.capitalize}"
end

No. You can’t do that. That will end up capitalizing just the first word emperor. You will end up with "Emperor joshua abraham Norton". The reason is the block variable full_name remembers the last value calculated inside the block. Because we did not pass an initial value to the inject method, it uses the first value in your collection by default, in this case "emperor". Inside the body of the block, we take "emperor" and capitalize it followed by "joshua" and capitalize that. We put the two together to get the complete string "Emperor Joshua". You can try this out in irb by truncating your array to just two elements, ["emperor", "joshua"].

So far so good. Next, we use this result of the calculation carried out in the body of the block and pass it to the block variable full_name. The block variable name is set to the next item in our collection, which is "abraham". We run through the body of the block again which leads us to capitalize "Emperor Joshua" and "abraham". That gives us "Emperor joshua" and "Abraham". That’s where things go wrong. When you capitalize a string, you are putting all characters except the first character in their lower case form.

A Hack

How then, do you capitalize the first name? By default, inject will use "emperor" as its initial value since I have passed no parameter to the inject method. What if I did try to pass in a parameter? What would that parameter be? What if I passed in an empty string? Ah, in that case, the full_name will be equal to '', the empty string.

1
2
3
["emperor", "joshua", "abraham", "norton"].inject('') do |full_name, name|
  "#{full_name} #{name.capitalize}".strip
end

If I only made a call to "#{full_name} #{name.capitalize}", I would have an extra space in front of the name, " Emperor Joshua Abraham Norton", by the time block iterated through all the items in the array. To get rid of this, I just strip the whitespace. Do you have another of solving this problem with the constraint of using the inject method? I’d love to hear your ideas.

Karate Chop Code Kata - Second Run

| Comments

Several Attempts With No Solution In Sight

The second run at implementing the binary chop method was excruciating. After several attempts, I finally decided to check out what others were doing. Was I blinded to a particular kind of solution I had in mind? Was my understanding of recursion incorrect? I felt I probably had a blind spot that was keeping me from seeing the problem in a new way. After going out on a Google search for the solution, I came across Stephen Doyle’s Blog

Here is his solution:

Stephen Doyle’s Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def chop(target, values)
  # Special handling for zero and single element arrays
  return -1 if values.empty?

  return ((target == values[0]) ? 0 : -1) if values.length == 1

  # Try the bottom half first
  pos = chop(target, values[0, values.length/2])
  return pos if pos != -1

  # Then the upper half ... remember that the returned
  # position is relative to the middle of the array.
  pos = chop(target, values[values.length/2, values.length-1])
  return pos + (values.length/2) if pos != -1

  # Didn't find what we were looking for
  return -1

end

Since I didn’t solve this, I’m going to talk about some of the approaches I took while trying to solve this problem and why they failed.

My Failed Ideas

The Tweaking Approach

I solved the problem once. What if I reused the same ideas but changed things just a bit? Here is what I came up with.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
defchop(int,array_of_int)
  return -1 if array_of_int.empty?

  @max ||= array_of_int.size - 1
  @min ||= 0
  @mid ||= @max / 2 + @min

  return @mid if int == array_of_int[@mid]

  return -1 if @mid == @max

  if int < array_of_int[@mid]
    @max = @mid
  else
    @min = @mid
  end

  chop(int,array_of_int)
end

As you can see, it uses the same idea of markers to indicate which portion of the array I am focusing on. The problem was I used instance variables to maintain state in subsequent calls to the chop method. This was a bad idea because it gave me bogus results in between assertions in my test suite. So I had to do away with instance variables as a way to maintain state.

The If-Else Construct Approach

You can see that from the above attempt, I continued using the if-else construct to determine which path to take in order to find the index of the integer. I used this for EVERY solution thereafter. That If-Else construct stuck to me like a leech. I didn’t even think of the possibility of doing away with it because I thought that was the only way you could traverse a binary tree. One approach I didn’t consider was simply picking one half of the array and carrying out the search until you find nothing; only then would you search for the other half. I kept thinking I MUST know whether it is greater or smaller than the integer found at the midpoint of the array. Later in this post, I discuss why I got stuck to the if-else construct approach.

My Successful Ideas

Even as I was making several attempts at the solution, there was one approach I took that led me closer to the solution.

Passing Smaller Array Slices Approach

What I did figure out was that I needed to call the chop method and pass it a smaller slice of the array. If the integer was less than the integer I found at the midpoint, I would call the chop method like so:

1
chop (int, array[0..mid-1])

If the integer was greater than the integer I found at the midpoint, I would call the chop method like so:

1
chop(int, array[mid + 1..-1])

This approach is one which Stephen Doyle used when solving the problem with recursion. I was on the right track.

The Missing Links

Stepping back and seeing Doyle’s recursive solution, I noticed there were a few things I didn’t understand which hampered my progress toward a solution.

Just Pick a Slice!

I didn’t even know this would have been allowed as part of the spec. Below is a quote from Dave Thomas’ CodeKata website:

A binary chop (sometimes called the more prosaic binary search) finds the position of value in a sorted array of values. It achieves some efficiency by halving the number of items under consideration each time it probes the values: in the first pass it determines whether the required value is in the top or the bottom half of the list of values. In the second pass in considers only this half, again dividing it in to two. It stops when it finds the value it is looking for, or when it runs out of array to search.

What I zeroed in on was this:

…in the first pass it determines whether the required value is in the top of bottom half of the list of values

I thought that the implementation involved the decision-making logic of an if-else construct where you would check if the required value is in the top half or bottom half of the list of values. That was my train of thought. But nowhere in that sentence does it say you have to use an if-else construct. To me, the sentence is misleading. Perhaps I read too much into it which ultimately led to my brain focusing on solutions that only used if-else constructs.

Stephen’s solution involves picking the bottom slice first. If the integer is not in the bottom slice, then the logic will go on to check the top slice until it finds the integer or returns a -1 to indicate it found no matching integer.

The Solution

There are a few things I would change in Stephen’s solution to make things a little more concise. Here is my solution:

Karate Chop Code Kata - Second RunSee on Github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def chop(int, array_of_int)
  return -1 if array_of_int.empty?

  mid = array_of_int.size / 2

  return ((int == array_of_int[mid]) ? mid : -1) if array_of_int.size == 1

  pos = chop(int, array_of_int[0...mid])
  return pos if pos != -1

  pos = chop(int, array_of_int[mid..-1])
  return pos + mid if pos != -1

  return -1
end

Stephen’s solution starts by checking the 0 index of the array. I went ahead and started in the middle. Also, by defining the midpoint with the mid variable, I can condense the portion of the array I pass to the chop method. I also went ahead and used ranges instead of the comma syntax inside the brackets. Rather than use array_of_int[array_of_int / 2, array_of_int.size - 1], I preferred the use of ranges and negative indexing to shorten the parameter I pass.

A Final Note

Dave Thomas hints at a third solution:

…one might use a functional style passing array slices around…

Let’s see how well I fare solving it in that style.

Karate Chop Code Kata - First Run

| Comments

The Idea

Here is my first attempt at Code Kata #2 - Karate Chop, which is an exercise you can find on Dave Thomas’ website Code Kata. Now mind you, I never had a formal introduction to computer algorithms. I had to look at a computer algorithms book to make sure I understood what was meant by the term binary search routine. The idea behind Karate Chop is to write a binary search routine that takes an integer and an array, splits the array in two, determines which half of the array the integer should be on and repeats the process until it finds the integer and returns it or doesn’t find it and returns a -1. In the end I should have a function that can be described as follows:

1
chop(integer, array_of_integers) => integer

The integer it returns should be the index of the array in which I found the matching number. In the event that I do not find the integer in the array, I should get a -1.

The Solution

Karate Chop Code Kata - First RunSee On Github
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def chop(int,array_of_int)
  return -1 if array_of_int.empty?

  min, max = 0, array_of_int.size - 1
  midpoint = max / 2

  while int != array_of_int[midpoint]

    if int < array_of_int[midpoint]
      max = midpoint - 1
    else
      min = midpoint + 1
    end


    midpoint = min + (max - min) / 2
    return -1 if max < min || min > midpoint
  end

  midpoint
end

The Walkthrough

Edge Cases

I implement the binary search routine using a while loop. I address the the edge case when the array is empty. When that hapens, I know that I must return -1 because no integer will ever be present in an empty array.

1
return -1 if array_of_int.empty?

Markers Point You In the Right Direction

In this solution, I use markers to let me know what portion of the array I am focusing on. I start off by initializing my markers. I have a min marker which tells me the first element of the array, and I have a max marker which tells me the last element of the array. The midpoint marker tells me which index is the midpoint of the array. These markers will change during the course of the program as I search through smaller subsections of the array.

1
2
  min, max = 0, array_of_int.size - 1
  midpoint = max / 2

The Search Is On

Now comes the interesting part, the loop construct. I decided to use the while loop to iterate through the array because it was the first solution that came to mind. Only if I found the matching integer would I exit the while loop and return the index of the array in which I found the matching number.

1
while int != array_of_int[midpoint]

Inside the while loop, I check for the condition where the integer I seek is less than the integer I find at the midpoint of the array. If it is less, then I move my max marker to 1 less than the midpoint; if more, then I move my min marker to 1 more than the midpoint. This is easily illustrated with a diagram.

1
2
3
4
5
if int < array_of_int[midpoint]
  max = midpoint - 1
else
  min = midpoint + 1
end

Notice that with each iteration, I am focusing on a smaller portion of the array. After the if-else construct, I advance the midpoint marker to reflect the new midpoint for the smaller portion of the array that I am focusing on.

1
midpoint = min + (max - min) / 2

When Markers Go Wrong

Here is where it gets tricky. A strange thing happens when the integer is not present in the array: the max, min, and midpoint markers go berserk! They no longer begin to make sense. That’s when I know it’s time to jump out of the loop and return a -1 to indicate that there was no matching integer inside the array.

1
return -1 if max < min || min > midpoint

Finally, the last line returns the index of the array that matched the integer I passed to the function.

1
  midpoint

A Final Note

Don’t let my explanation fool you, this was a difficult exercise for me. At first I was thinking about chopping the array in half and then searching through each half iterating through each element one by one to see if I found the integer. That wasn’t the correct way to go about it; it would have been an incorrect implementation of a binary search. When you come across unfamiliar terms, you must start by understanding those terms before you can tackle a problem. My problem was that I didn’t understand the idea of a binary search.

This exercise took me somewhere close to 8 hours. I’m hoping to cut my time in half in the next implementation. Stick around as I try to solve this again using a different implementation.

Using ‘Bundle Install’ in a Rails 3 Generator

| Comments

Problem

I came across Rails issue #3153 on Github which involved someone thinking bundle install didn’t work with Rails generators.

When working with Rails generators it’s important to remember that generators use Yehuda Katz’s Thor gem. The issue arose because the user tried to run the command bundle install from inside the directory in which the generator file had been created.

1
2
3
4
5
6
7
8
class SomegenGenerator < Rails::Generators::Base
 source_root File.expand_path('../templates', __FILE__)

 def install
   gem "unicorn"
   run "bundle install"
 end
end

Solution

Again, the issue is that the command bundle install is being run from inside the directory Issue3153/lib/generators/something/. I’ll let you take a quick look at the Thor source code at lib/thor/actions.rb. As you have now seen, we can use the inside method to run the bundle install command from the root directory of our app Issue3153.

1
2
3
4
5
6
7
8
9
10
11
# Issue3153/lib/generators/something/something_generator.rb
class SomethingGenerator < Rails::Generators::Base
  source_root File.expand_path('../templates', __FILE__)

  def install
    gem "unicorn"
    inside Rails.root do
      run "bundle install"
    end
  end
end

This now works!

Take Aways

  • Rails generators use Thor Actions
  • The bundle install command must be run in the directory where the Gemfile of interest resides