Graph::Function

This gem’s goal is to make it easy to compare the asymptotic performance of two or more functions via graphing.

When I work on katas and exercises I found I often wanted to compare my implementations. After doing so a half dozen times I noticed some patterns, and figured it’d be valuable to capture those into an easier API to work with. While working on a kata I like the immediacy of replotting back on x11, but because of gnuplot’s structure it is just as easy to get images or html canvas graphs.

Disclaimer

Because of the current implementation details: Ruby methods which operate on self will not work, and there is a negligible constant slow down on all functions tested by Comparison because of the use of send(:func). The latter won’t corrupt comparisons, but means you don’t want to use this gem to benchmark functions individually except through Graph::Function::Only.

Installation

Because this gem depends on gnuplot and xquartz, we need to follow their prereq steps:

these will vary by your system, mine is mac

brew install Caskroom/cask/xquartz brew install gnuplot –with-x11

verify you have x11

xpdyinfo | grep version

Now we’re set. Add this line to your application’s Gemfile:

“by gem ‘graph-function’

And then execute:

$ bundle

Or install it yourself as:

$ gem install graph-function

Usage

Setup

To set up, you only need the following:

“by require ‘graph/function’ Graph::Function.configure

If you don’t want to output to x11, just set config.terminal to a different option like gif (or anything else gnuplot respects as a terminal). Output is the file location of output, and is ignored for x11.

“by Graph::Function.configure do |config| config.terminal = ‘gif’ config.output = File.expand_path(‘../your_graph_name.gif’, FILE) config.step = (0..10_000).step(1000).to_a # default value end

In configuration, you can also control the “step” size of x in the plot. Its default value is (0..10_000).step(1000).to_a ([0, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000]) but you can make it as fine or rough grained as you need up to any size.

Graphing

The simplest usage (suitable for a large class of exercises, in my experience) is if you’re comparing two functions that take a single argument of Array[Int] type:

“by c = YourClass.new # this class has #function_name_one & #function_name_two Graph::Function::IntsComparison.of(c.method(:function_name_one), c.method(:function_name_two))

=> will output an xquartz graph

comparison

For more complex use cases, you’ll be creating a Graph::Function::Comparison (or Graph::Function::Only if you want to graph a single function) with some generator of data, and executing #of with Method objects that operate on the same parameter types1. (Note because IntsComparison does not need a generator, .of is a class method instead.)

To generate values of the type needed by your function, use the provided dependency Rantly. There’s great documentation on generating many different kinds of data in their documentation, but here’s an example of comparing two functions that take Hash{String => Integer}:

“by

you must put it in a proc taking size so Graph::Function can increase it

generator = proc {|size| Rantly { dict(size) { [string, integer] } } dict_comparison = Graph::Function::Comparison.new(generator)

Comparison can take any number of Methods, but for now, 2

dict_comparison.of(method(:hash_func_one), method(:hash_func_two))

=> will output an xquartz graph

comparison

If you want to make use of more “real” fake data, Faker is also included, and can be used like so in your generators:

“by

again, we need to parameterize our generator with size

faker_generator = proc {|size| Rantly(size) { call(Proc.new { Faker::Date.backward(14) }) }

using Only here, but anything that takes a generator can take one with Faker

graph = Graph::Function::Only.new(faker_generator) graph.of(method(:custom_types))

=> will output an xquartz graph

faker

The only downside here is that you can’t parameterize Faker, but you could use random generators to mix it up. Using the above example, graph-function won’t pass anything into the faker_generator but the size, so if we want the value to change, we could use Faker::Date.backward(proc { rand(10) }.call).

Check out the spec file to see all of these or see examples.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/mooreniemi/graph-function. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

License

The gem is available as open source under the terms of the MIT License.

Footnotes

1 Why are we constrained to testing the same parameter types? The intent of this library is to graph implementations. Changing parameter types suggests a change in the behavior of the function. That doesn’t make for a very productive comparison.