Marmalade
A Ruby library for helping with the boring, mechanical part of Google Code Jam puzzles, letting you focus on solving the problem instead of setting up the scaffolding.
Most Code Jam input files look like something like this:
100 # Number of test cases
5 3 # Some data about case 1, say a number then some numnber of lines to read next
123 # First line to read
456 # Second line to read
789 # Third line to read
9 8 # Start of test case 2
When beginning a puzzle, wouldn't it be nice to focus on what you need to do to solve it rather than messing with reading the file in the right increments, debugging it, and making sure it's doing what you expect? This is where Marmalade comes in. It gives you tools to use in order to quickly get down to the work of solving the actual problem. After all, time is of the essence!
Installation
Install via RubyGems:
$ gem install marmalade
At the moment, the requirements for using it are:
- A *nix environment
- Ruby 1.9
Basic Usage
Once the gem is installed, use the generator to create yourself a project:
$ jam example
This command will create a directory called example
under the current directory and will create a file in the directory called example.rb
, which is an executable Ruby file set up for using Marmalade to do the dirty work for you. Running this file with the --help
option lets you know about a few things it can do out of the box:
$ cd example
$ ./example.rb --help
Options:
--file, -f <s>: Input file to read
--debug, -d: Debug mode
--step, -s: Step through each case
--case, -c <i>: Only run the given case
--parallel, -p: Run the cases in parallel processes
--processes, -r <i>: Specify the number of processes to use
--help, -h: Show this message
Say we have an input file like the one described above. The first line of the file declares how many test cases the file contains. The first number in each test case is called k
and the second is n
, which is the number of lines
to read in for each puzzle. Assuming we have a method called solve_case
that returns the result we're looking for, we can edit example.rb and instruct Marmalade to read that for us like so:
Marmalade.jam do
read_num_cases
test_cases do
read [:k, :n], :type => :int
read :lines, :count => @n
run_case do
puts solve_case(@k, @lines)
end
end
end
Assuming our input file is called sample.txt
, we can now run the puzzle like this:
$ ./example -f sample.txt
Marmalade reads in each line and will assign the values to the instance variables that you specified (in this case, @k
, @n
and @lines
). When the result is ready to be printed, Marmalade overrides puts
to use the proper output format that Code Jam usually requires, much like:
Case #1: 12
Case #2: 55
# and so on...
Using multiple cores
Marmalade supports running test cases over multiple CPU cores by running them in parallel processes. In order to run your cases in parallel, supply the --parallel
command line option to the script. For example:
./example.rb -f input_file.txt --parallel
When this option is supplied, Marmalade will use as many processes as there are CPU cores on your machine. To control how many processes are used, supply the --processes=X
command line option. For instance, to run a test using 4 parallel processes, do the following:
./example.rb -f input_file.txt --processes=4
Note that if you supply a processes count, you do not have to also supply the --parallel
option.
Reading the number of test cases
As shown in the example above, the call to read_num_cases
reads the next line from the file, interpreting it as an integer and assigning it to the @num_cases
instance variable. It is functionally equivalent to the following call:
read :num_cases, :type => :int
@num_cases
is important because it lets the test_cases
block know how many cases to run. If @num_cases
has not been assigned by the time test_cases
is called, then Marmalade will throw an error.
Reading strings
The read
call will interpret lines and values as strings by default. So, to read strings you simply want to omit the :type => :int
declaration on read
.
Splitting lines
Many times, Code Jam puzzles have lines with multiple values that you want to process as an array. For instance if you have a line like the following:
A B C D E
By default, Marmalade will read that line as a single strin. If you'd rather read it as an array, you can use the :split
argument to read
like so:
read :letters, :split => true
The result would be an instance variable @letters
which will be the array ['A', 'B', 'C', 'D', 'E']
. Similarly, if the line were a series of number like this:
1 2 3 4 5 6 7
You can combine :split
with :type
:
read :numbers, :split => true, :type => :int
The @numbers
instance variable would then contain the array [1, 2, 3, 4, 5, 6, 7]
.
Finally, here is how you'd read multiple lines of integer arrays:
read :matrix, :count => 3, :split => true, :type => :int
If you had an input file that looks like this:
1 2 3
4 5 6
7 8 9
Then the @matrix
instance variable will be an array of arrays: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
.
Helper methods
There are two ways to define methods that help you organize your solution code. The first is to simple define the method right within the run_case
block like so:
Marmalade.jam do
read_num_cases
test_cases do
read :words, :split => true
run_case do
def solve_case(arr)
puts_dbg "array is: #{arr.inspect}"
arr.reverse
end
puts solve_case(@words)
end
end
end
The second (and preferable) way is to open the TestCase
class and define methods there. Since code within a run_case
block runs within the context of a TestCase
instance, all methods defined within that class will be available when running the case.
class TestCase
def solve_case(arr)
puts_dbg "array is: #{arr.inspect}"
arr.reverse
end
end
Marmalade.jam do
read_num_cases
test_cases do
read :words, :split => true
run_case do
puts solve_case(@words)
end
end
end
Of course, defining methods in the global context of the file itself will work fine, but then they won't have access to the instance variables read from the file, or the modified puts
or puts_dbg
methods (see below).
Debugging
Marmalade gives you a few ways to help debug as you solve puzzles. Firstly, you can pass --debug
as a command line argument, which enables debug mode. As you go along, you can use puts_dbg
to write debug messages to the console only if debug mode is enabled. This comes in handy when you want to do your final run to generate your submitted output file.
Passing the --step
command line argument will cause Marmalade to pause for user input between every test case. This is particularly handy when you're validating your solution to a puzzle and want to follow your debug output as the program runs through test cases.
Sometimes you hit a tricky test case and want to work on just that case. Passing --case X
will cause it to only process case X and skip all other cases. However, in order to enable this functionality, you must make sure the work in each case is done in a run_case
block. For example, this command:
$ ./example.rb -f sample.txt --case 5
Will run solve_case
only for test case 5.
Updating from 0.2 to 0.3
Two things have changed between v0.2 and v0.3 that may break your previous scripts:
- In version 0.2, Marmalade did not require that test case code was contained in a
run_case
block. This is now required. - Version 0.2 was able to use globally-scoped helper functions, which is now deprecated. For version 0.3, please open the TestCase class or create helper methods directly in the
run_case
block.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request