One day I stumbled upon this awesome site called “Exercism”. It mainly focuses on improving your skills via Deep Practice & Crowdsourced Mentorship, Strengthening your problem-solving skills by guiding others through the process. I got hooked onto it and then I thought why not share my learning with all. Being a Ruby programmer, my notes are Ruby based.
Things To Keep In Mind
Before tackling any program/problem/challenge you should keep certain points in your mind. Some are general, some are more language specific.
Some questions you might want to ask yourself:
- Am I adhering to the Single Responsibility Principle?
- Is all my code on the same abstraction level?
- Can I combine conditional clauses?
- How does my API look to clients of this code (i.e. how do other classes interact with this class)?
- Do I have duplication?
- What requirements are likely to change? Would I be able to implement such changes painlessly? [Note that it is not that you should normally guess what future requirements are, but it can be helpful on Exercism]
Use Private, that way you limit the public API of any class. It’s considered a good practice to expose as few methods as possible, so you don’t come up with a “fat interface” and has to maintain a lot of methods that probably nobody else uses (this is troublesome when you want to refactor your code but you aren’t sure if somebody is using method x or not.
If you are using in-built methods you should understand them first – how they work, what are the pros & cons, in which use-case you should use them. Don’t assume, dig them up. Some examples:
- p & puts are not the same
p foo does puts foo.inspect , i.e., it prints the value of inspect instead of to_s, which is more suitable for debugging (for example, you can tell the difference between 1, “1” and “2\b1”, which you can’t when printing without inspect). - to_a is super-expensive on big ranges.
- Use string interpolation only when necessary.
123456# Example where string interpolation is neededname = 'Anjali Jaiswal'puts "Welcome #{name}"# Example where string interpolation is not neededputs 'Hello World!'
-
a.map(&:to_i) i.e. Symbol#to_proc : you are passing a reference to the block (instead of a local variable) to a method. Also Symbol class implements the to_proc method which will unwrap the short version into it’s longer variant. Example:
123a = ["2", "3"]a.map(&:to_i) # a(&:to_i.to_proc)#=> a = [2, 3]
Some problems from Exercism
I have described what I learned from individual problem. I am also putting names of the relevant problems so that you can look them up. My code is uploaded on Github. Also I have explained why I used certain in-built Ruby methods. I have highlighted some specific things that you should consider in general.
Accumulate
Two ways to yield a block in ruby : yield & call
1 2 3 4 5 6 7 8 9 |
# yield statement def speak yield end # call statement def speak(&block) puts block.call end |
(Read “ZOMG WHY IS THIS CODE SO SLOW?”)
- Yield returns the last evaluated expression (from inside the block). So in other words, the value that yield returns is the value the block returns.
- If you want to return an array use map/select depending on situation. You don’t need to initialize an array & return it.
Anagram
Anagrams are same if sorted.
select will return element if condition satisfies. So in case of conditions use select over map / each .
Beer-songs
Binary
Use map.with_index so that you do not have to initialize another variable & directly apply inject on the result.
Bob
Start simple. Try to make test pass in a very easy way.
Etl
Use of each_with_object :
1 2 3 4 5 |
input.each_with_object({}) do |(score, letters), result| letters.each do |letter| result[letter.downcase] = score end end |
Grade-school
Initialize an empty hash and declare values as an array. To sort & return a hash use hash.sort.to_h .
Grains
Sometimes solution could be a single word. Check for patterns. For instance, here you don’t have to iterate 64 separate calculations in self.total
. Think about the totals for the first few squares: 1, 3, 7, 15, 31. Now think about those totals in relation to powers of two. Total is a series of (2x-1) where x is 2**(n-1).
Hamming
- raise could be another method. Always look out for single responsibility principle.
- select & count are perfect methods here.
- It is considered a good practice to expose as few methods as possible, so you don’t come up with a “fat interface” and maintain a lot of methods that probably nobody else will use. This is troublesome when you want to refactor your code but you aren’t sure if somebody is using that method or not. In this exercise, the only method being called is self.compute , so this means you could make self.raise_size_mismatch private and avoid exposing it.
Hello-world
You can pass default argument to a Ruby method :
1 2 3 |
def hello(name = 'world') #some data end |
Leap
You can minimize loop:
1 2 3 4 5 6 7 |
if year % 4 == 0 && year % 100 == 0 year % 400 == 0 ? true : false elsif year % 4 == 0 true else false end |
to:
1 |
year % 400 == 0 || year % 4 == 0 && year % 100 != 0 |
But I won’t prefer it, because the former one is more readable. Sometimes you have to gauge which is worthier.
Phone-number
Interesting use of sub:
1 2 3 4 |
'0987654321'.sub(/(\d{3})(\d{3})/, "(\\1) \\2-") # will result in "(098) 765-4321" # Can return a part of string like: '0987654321'[0..2] # will result in '098' |
Raindrops
Assign common values as constant. Avoid unnecessary computing. For example you only need to check if 3, 5 & 7 are factors.
Robot-name
shuffle method of Array (can be applied to a-z but will be futile for 0-999 as it takes lots of memory & computation). Use Default assignment
||=
That’s it for now. Please do let me know how this blog helped you and whether I missed something that should be included. Sayonara for now!