abacha / risk_calculator Goto Github PK
View Code? Open in Web Editor NEWThis project forked from mendicant-original/s10-mod
This project forked from mendicant-original/s10-mod
A file called lib/risk_calculator.rb should exist and require all other necessary files in your library. This makes it possible to easily do require "risk_calculator"
and get all of your code loaded at once. In your bin file, you'll probably do: require_relative "../lib/risk_calculator"
module RiskCalculator
class Dijkstra
end
end
The details about this are in the s10-notes about code structure
initialize
method is listed under your private methods. However, Ruby will automatically make initialize
private no matter where you put it in your file. Since it is something that all your instance methods depend on, the standard place to put it is directly above the rest of your instance method definitions (i.e. right above def add_path
in this case). Placing it down under the private
keyword makes it easy to miss for the code reader.Please add source :rubygems
to your Gemfile.
Plus on a side note, in ruby 1.9 it's no longer required to require 'rubygems'
and you appear to use ruby 1.9 =)
The README does not reflect how the code currently operates, and so I cannot easily test to make sure it works as expected. Please update.
It's common outside of Rails to refer to your directory which contains supporting data as the data directory, not the assets directory. This is so common in fact that it's referenced in a draft proposal for a Ruby Packaging Standard. Please re-name the assets folder to data/
To me the following in the dijkstra method seems rather hacky:
(@target ||= {})[node] = node == origin ? 0 : INFINITY
(@prev ||= {})[node] = -1
I'm especially talking about (@target ||= {})
. If I don't missunderstand the code you could just do:
@target = {}
@prev = {}
before the loop which would be a lot more readable.
In Territory:
attr_reader :name
attr_reader :borders
can be written as:
attr_reader :name, :borders
Plus if you define army
yourself you should rather user attr_writer
than attr_accessor
Currently the code in lib/risk_calculator.rb is just a script. It would be better off to make it modular, using the technique outlined in this Practicing Ruby article. In particular, see the code samples with reference RCat::Application
as well as the section on building an executable script.
You can also look at any of the s10-int projects for ideas on how the students built their executables for those.
Your Board.read_from_file
method looks correct, but the name doesn't sound great to me. Consider renaming it to Board.from_file
, simply because this seems to read more naturally. Compare the two examples in context and read them aloud in your head and see which expresses the idea better:
board = Board.read_from_file(filename)
#vs.
board = Board.from_file(filename)
To me the former emphasizes the action and the latter emphasizes the result. Emphasizing the result is better than emphasizing the action when you are returning a value as opposed to simply causing a side effect.
Rather than putting most of your code in your script, you should create a RiskCalculator::Application
class in lib/risk_calculator/application.rb which encapsulates your logic. While the advice in this Practicing Ruby article is probably more than what you need, you can get the idea of the basic structure from skimming through it and looking at how bin/rcat interacts with RCat::Application
Rather than using require "highline/import"
, I'd recommend using require "highline"
and then doing something lik:
io = HighLine.new
io.menu do
#...
end
This will prevent your application from causing HighLine to pollute the global namespace.
Rather than calling this file bin/risk_calculator.rb
it should be called bin/risk_calculator
and use a shebang line to indicate it's a Ruby executable. This makes it possible to run the command in your shell as risk_calculator
instead of risk_calculator.rb
, which will make it feel more like an ordinary UNIX command.
Instead of inheriting from Exception
, it's better to inherit from StandardError
. This is because StandardError
is the class Ruby provides for use as a parent class for custom exceptions, is what a bare rescue matches. The kinds of exceptions which inherit from Exception
typically are things that can't be rescued in a general case, such as syntax errors.
While it's optional, I'd encourage you to write InvalidParameterException = Class.new(StandardError)
instead of defining a bare class definition, simply because it's a bit more aesthetically pleasing and is functionally equivalent.
To get the full benefits of HighLine, it's best to use it for all your console IO needs. That means replacing calls to puts
with calls to HighLine#say
.
Instead of params = _params.split(';')
, consider using a more intuitive name for _params, such as raw_params
or params_text
, yielding something like params = params_text.split(';')
Please remove any unused code rather than leaving it commented out in the source.
In dijkstra.rb/Graph @origin is set but never used. Please remove it.
In general try to keep your methods ~10 lines of code max (usually the smaller the bestter). Smaller methods are way easier to read and to follow. You can usually get the lines of a method down by extracting some sub methods that do one unit of work together. This also helps the reader a lot since descriptive method names act as little comments.
I'm particualarly talking about:
For the dijkstra method, it would be good imo if you could get rid of while
- imo there are more rubyish alternatives to that there. Also I'm not a big fan of break
try if you can get it out keeping the code intact and readable.
In dijkstra.rb there is a class Graph
defined. Imo it's the convention that the snake_case of the file_name should match the ClassName.
t
isn't a very descriptive variable name, from the context it is mostly clearly what you mean, however territory would usually be better.
And for instance:
borders.each do |t|
threw me off. I believe borders should rather be named bordering_territories or something like that. Also there we got the t
(which stands for territory) and another territory
variable in the same method (Board#add_borders). Try to show the difference between the two in the naming.
Remember that code is read many more times than written. :-)
For any Ruby library, it is best to introduce only a single name into the global code loading space. To do so, you should have a structure like:
lib/risk_calculator.rb
lib/risk_calculator/board.rb
lib/risk_calculator/dijkstra.rb
lib/risk_calculator/territory.rb
The lib/risk_calculator.rb file should consist of only require
calls to load external dependencies, and require_relative
calls to load all your project files. These conventions are very standard and are included in the Ruby Packaging Standard draft proposal.
Currently lib/risk_calculator.rb is serving as an executable script which isn't standard, I'll file another ticket about how to fix that.
module RiskCalculator
class Territory
end
end
The details about this are in the s10-notes about code structure
The army
method should probably be armies
or army_strength
or army_size
, or something along those lines.
It's not clear to me why army
returns an absolute value. Is this number ever negative, and if so, why?
module RiskCalculator
class Board
end
end
The details about this are in the s10-notes about code structure
Board#read_board_file
should be lifted up into Board.from_file
I provided an outline of how to do this in this gist. The benefit of moving your file processing logic to a class level factory method is that it makes it so that your Board
object is not so tightly coupled to a single file-based representation. This will make testing as well as using board objects in a different context much, much easier.
In board.rb your initialize method has quite a bit of file handling. while loops aren't really the ruby way, after reading the first line (which is different I suppose) then you can simply do:
f.each_line (...)
You should also think about naming f file
. Plus I'd rather see this file handling in its own method (it's too low level for the rest of the initialize method)
In the Dijkstra algorithm
(@prev ||= {})[node] = -1
From what I know about Dijkstra I guess that this means that there is no previous node yet. If I'm correct then I guess it would be better to use a more obvious value, like nil
or define a constant that communicates this behaviour better.
In the dijkstra I see a lot of this:
(@graph[origin] ||= {})[target] = weight
Which is basically that you want the default value of {}
if a key isn't present, right? Try doing this:
@graph = Hash.new({})
This way you can set any default value that is returned when the key doesn't exist. Should make the code a lot more readable =)
We actually have infinity in ruby, target version imo was 1.9.2 it works on 1.9.3 for me:
Float::INFINITY
For use in your Dijkstra algorithm =)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.