Send to KindleRuby Programming Challenge For Newbies
RPCFN: Shift Subtitle (#1)
By Fabio Akita
After a very encouraging response to our poll from YOU, the readers of the RL blog, RL is happy to announce the first-ever fortnightly ( bi-weekly / every 14 days) “Ruby Programming Challenge For Newbies (RPCFN)” in Ruby. Thanks to YOU, the Ruby community, people like Fabio Akita and companies like Locaweb who make all of this possible.
About Fabio Akita
Fabio Akita is a Brazilian Rails enthusiast, also known online as “AkitaOnRails”. He regularly write posts on his own blog and had published the very first book tailored for the Brazilian audience called “Repensando a Web com Rails”.
He is now a full-time Ruby on Rails developer working as Project Manager at Locaweb, Brazil. He’s also the creator of the “Rails Summit Latin America“, the largest international Rails event in South America.
Fabio has this to say about the challenge:
If you’re learning a new language such as Ruby, it is important that you practice it. And the best way to start is by scratching your own itch. Anything goes. It’s not unusual to start by writing simple command line scripts to help out your everyday routine. That’s why I thought of a very trivial exercise in the first challenge. It should demand that you know the basics for a variety of Ruby subjects such as regular expressions, file manipulation, time calculation and so on. The only way to achieve mastery is by practice. So let’s get started!
Sponsor
1st Easy Limited are delighted to have been given the opportunity to support the work of Satish Talim and his team at RubyLearning.
Taking part in the Ruby Programming Challenge? You’re welcome to take advantage of the free Ruby on Rails hosting trials that 1st Easy offer: simply register your details, and a full-featured account is yours to do with as you please for one month. Once the trial is over, you can transfer your work to a paid account, or walk away with no questions asked!
Prizes
- The person with the best Ruby solution (if there is a tie between answers, then the one who posted first will be the winner) will be awarded any one of PeepCode’s Ruby on Rails screencasts.
- The other prize, selected randomly amongst the remaining working Ruby solutions, will be awarded any one of Pragmatic’s The Ruby Object Model and Metaprogramming screencasts.
The two persons who win, can’t win again in the next immediate challenge but can still participate.
The Ruby Challenge

Difficulty: Ruby beginner.
Goals: Basic control over Ruby elements, specially command line scripting.
Description: There are several ways to subtitle a movie nowadays, and one of the most well known format is the SubRip format (http://en.wikipedia.org/wiki/SubRip). It has entries like these:
645 01:31:51,210 --> 01:31:54,893 the government is implementing a new policy... 646 01:31:54,928 --> 01:31:57,664 In connection with a dramatic increase in crime in certain neighbourhoods,
Each line has an increasing integer identification, then comes the time range (start and end time) in the format “hours:minutes:seconds,milliseconds”. The decimal separator used is the comma. Finally there are the subtitles themselves and a line break marks the end of an entry.
Sometimes the timing is shifted for a small amount, 2 or 3 seconds. Then comes the trouble when you need to shift everything a few seconds back or ahead.
The goal is to create a small command line script in Ruby that will read an SRT file, and output another one with the new calculated times.
So, for example, if I want to shift everything 2,500 (2 seconds and 500 milliseconds) ahead, I would start with this:
01:32:04,283 --> 01:32:07,769
and end up with:
01:32:06,783 --> 01:32:10,269
The command line should accept arguments such as:
shift_subtitle --operation add --time 02,110 input_file output_file
This means “--operation” can accept either ‘add’ or ‘sub’ to add or subtract times. The “--time” will accept the amount of time to shift in the format 11,222 where “11″ is the amount of seconds and “222″ the amount of milliseconds.
Requirements: This has to be a pure Ruby script, using only the Ruby Standard Libraries (meaning, no external Gems).
It has to implement “optparse” to parse the command line arguments.
As an observation, bear in mind that the first thing that you might attempt will look like this:
a = Time.at(04,283) b = a + 2.500 puts b.usec => 500283
This is wrong, the proper result should’ve been “783″ (as in the example in the previous section). So it means that you will have to find another way out.
Extras (Optional): If you want:
- It would be interesting to exercise the process of a Gem creation. So you would have to package your script.
- Another thing that would be good is to have RSpec unit tests covering your code, to exercise software development best practices.
(Note that the above two points are optional and not a requirement).
How to Enter the Challenge
It’s free and registration is not required. You can enter the challenge just by posting the following as a comment to this blog post:
- Your name:
- Email address (will not be published):
- Brief description of what you do (will not be published):
- Country of Residence:
- Your Solution (i.e. Ruby code): Prefix your code with <pre> tag and suffix it with </pre> tag.
- Code works with Ruby 1.8 / 1.9 / Both:
- Explanation (if any):
- Test cases (if any):
Note:
- You may provide the URL of your source code, in case it is hosted on GitHub.
- All solutions posted would be hidden to allow users to come up with their own solutions.
- You should post your entries before midnight of 4th Oct. 2009 (Indian Standard Time). No new solutions will be accepted from 5th to 8th Oct. 2009.
- On Monday, 5th Oct. 2009 all the solutions will be thrown open for everyone to see and comment upon.
- The winning entries will be announced on this blog. The winners will be sent their prizes by email.
More details on the RPCFN?
Please refer to the RPCFN FAQ for answers to the following questions:
- What Is The Ruby Programming Challenge For Newbies (RPCFN)?
- How does RPCFN benefit you?
- Best Solution
- Can I Submit A Ruby Programming Challenge Topic?
Donations
RPCFN is entirely financed by RubyLearning and sometimes sponsors, so if you enjoy solving Ruby problems and would like to give something back by helping with the running costs then any donations are gratefully received.
Acknowledgements
Special thanks to:
- Fabio Akita.
- Sponsor 1st Easy Limited, U.K.
- The RubyLearning team, namely Michael Kohl (Austria), Peter Crawford (Italy), Satoshi Asakawa (Japan) and Victor Goff (USA).
- Ruby Inside, Brazil.
- Amanda and Michael’s Ruby Blog
- Adrian
- Charles Feduke
- Fabio Kreusch
- Fernando Quadro
- It’s so shiny
- Jeff Craig
Questions?
Contact Satish Talim at satish.talim@gmail.com OR if you have any doubts / questions about the challenge (the current problem statement), please post them as comments to this post and the author will reply asap.
The Participants
There are two categories of participants. Some are vying for the prize and some are participating for the fun of it. The participants were:
In the competition
- Felipe Giotto, Brazil
- Eduardo, Brazil
- Kalle Lindström, Sweden – declared winner
- Robison WR Santos, Brazil
- Aldric, USA
- Akshay Gupta, India
- Fabio Kreusch, Brazil
- Chris Jones, USA
- Chuck Ha, USA
- Milan Dobrota, Serbia
- Parag Shah, India
- Hugo Figueiredo, Brazil
- John McDonald, USA
- Felipe Elias Philipp, Brazil – declared winner
- Charles Feduke, USA
- Hari Rajagopal, USA
- Brad O’Connor, Australia
- Oliver, UK
- Jacob Lichner, USA
- Todd Huss, USA
- Antonio, Canada
- Sriram Varahan, India
- Giordano Scalzo, Italy
- Phil Kates, USA
Just for Fun
- Michael Kohl, Austria
- Rodrigo Rosenfeld Rosas, Brazil
- Dominik Honnef, Germany
- Mike Hodgson, Canada
The Winners
![]()
Congratulations to the winners of this Ruby Challenge. They are:
- Felipe Elias Philipp from Brazil (his Ruby Challenge solution) – the person with the best Ruby solution. He wins any one of PeepCode’s Ruby on Rails screencasts.
- Kalle Lindström from Sweden (his Ruby Challenge solution) – selected randomly amongst the remaining working Ruby solutions. He wins any one of Pragmatic’s The Ruby Object Model and Metaprogramming screencasts.
Next Challenge
RPCFN: Average Arrival Time For A Flight (#2) by Chris Strom.

- The Challenge is now closed. Fabio Akita has a working solution to this problem. This is not a “perfect” or the sole “correct” solution, but just one way of doing it. Fabio is thankful to Satoshi Asakawa for using one of his ideas in this implementation.
Technorati Tags: Ruby, The Ruby Programming Language, Ruby Programming Challenge For Newbies, Programming, RPCFN, Fabio Akita
Posted by Satish Talim


{ 126 comments… read them below or add one }
Next Comments →
Seems a bit more than the beginner level.
How so? If you break it down into small tasks, it’s a trivial exercise. Think about it: all you have to do is this (more or less):
Tackle it in small pieces and you won’t feel as overwhelmed. Also the step by step description gives you a good idea which classes might be useful (File, RegEx, Time). True, optparse might be a bit confusing at first, but Satish posted very good links for that!
Don’t be discouraged, if you are willing to read some docs, this challenge should easily be solvable with the knowledge presented in the core course!
Ok I will finish the RubyLearning course first, then come back and try these. In week two (when this was posted) we were no where near these concepts.
Nice
And for the students that are going to participate: the subtitle shifting algorithm itself is very easy (you’re going to find several different ways of doing it), but I am more interested in the whole other peripheral stuff such as project organization, command line option parsing, gem spec, testing. Take your time to study all those areas as they are all going to be useful for you in the future.
Fabio you mention that you are interested in solutions that cover project organization, command line option parsing, gem spec and testing. Is a Ruby newbie expected to know all that? What if I just submit my solution that solves the subtitle shifting algorithm problem – will my solution be not accepted?
I understand and appreciate when you say that these areas will be useful for me in the future. But for now?
According to this, I think a Ruby newbie is expected to -learn- this in two weeks
At least the basics. I, for one, intend to start working on this aspect of Ruby/programming which I’ve ignored too long.
I’ve been looking at the challenge for a couple of hours on and off today, and I’ll at least say this: I’ve learned how little I know!
I was a little concerned and frustrated at first, but I remembered we’ve got two weeks to work on this, and once I started breaking down the problem bit by bit, I realized this is manageable. I’m actually getting somewhere sort of. So, anyone who was discouraged, don’t give up!
Anyway, OptionParser is awesome! That’s where I’m starting from in my script. If you haven’t looked closely at it yet, I recommend doing so. Look at the tutorials Satish linked to as well as the standard documentation. Those three combined provide a great jumping off point for your script.
The challenge is classified ‘Beginner’. Ha! Seems like one’s Ruby would have to be sharp in order to cut through the ‘optparse’ jungle.
If this is a challenge for new Rubyists, then I am not sure where I stand, as I thought I was a “newbie”.
That said, I would like to at least attempt it.
Fabio, the file format used is for Windows only I wonder if that matters since I am on Linux.
@Raven if you are talking about the “/” vs “\”, research the documentation of methods such as File.expand_path and File.join to make it independent.
Fabio, you mention in your spec that:
“This has to be a pure Ruby script, using only the Ruby Standard Libraries (meaning, no external Gems). It has to implement “optparse” to parse the command line arguments.”
Isn’t “optparse” an external gem? So is “optparse” essential in our solution?
BTW here’s a neat tutorial on “optparse” -
http://ruby.about.com/od/advancedruby/a/optionparser.htm
http://ruby.about.com/od/advancedruby/a/optionparser2.htm
optparse is within the Ruby Libraries.. therefore not an added gem.
Hehe, my first gem was subtitle related:
http://github.com/nofxx/subtitle_it
It’s ugly but works with a lot of formats, even .ASS files!
Solution (#1):
I am from Austria.
Here’s an example solution (not participating in the challenge):
Solution: https://gist.github.com/9d048d9164fda984e2af
Solution (#2):
I am from Brazil.
My code runs with ruby 1.8.6. After a lot of ‘googling’, I’ve found that with Ruby 1.9, i could just use strftime with %L parameter, being able to get the miliseconds of the timestamp. The script would be simpler. But I wanted to make a script compatible with the older version (also because I don’t have 1.9 installed on this machine
). Anyway, I think it will run in ruby 1.9 too. I’ve never user OptionParser before, maybe my script is a little “ugly”, but, anyway…
There’s three files. One is the “core” (srt.rb), other is the test suite (srt_test.rb) and the other is the “runner” script.
This is my solution (there’s many files): https://gist.github.com/0d72bde27e08332c6df4
Hope you understand and like my script! I’m a big fan of your site!
He did the natural course of thinking. The only thing I would expect is about dealing with the files. He is reading the input all into memory whereas the normal pattern is to stream it line by line. The algorithm itself, while laborious and not the most elegant, does indeed work correctly. The test file itself did not work for me.
Actually, my script seems to be html escaped! When I posted it, there was a lot of ”, now there’s many ‘<’ and ‘>’… Look at the strings for the subtitles, the inheritance of the Test::Unit::TestCase class… The download link is not working too… What should we do now?
Even though the main algorithm may not be the most elegant, the layout of the solution (logic, runner, test) is quite nice.
“It has to implement “optparse” to parse the command line arguments.”
This is not clear to me… Does it mean we should use “optparse” from standard lib, or implement the option parser?
Just use “optparse” from the standard lib.
Solution (#3):
I am from Brazil.
Solution: https://gist.github.com/065f4ed31b94bc72ffd1
Readability is a problem. The method names are as terse as they can be, there’s quite a few “magic numbers” and more regex than need be. Just some of my suggestions for Eduardo.
The code is not readable and very “hacky”. Feels more like a bash script than a Ruby program.
Hello! Congratulations for the event.xD
In case of the commands, the user will input a subtitle first, writing,for example:
input_file “c:\subtitle.str”
and later he will write…
if he’s want change the load file, adding 2,10 for example:
shift_subtitle –operation add — time 02,010
if he’s want save in other file adding 2,10 for example:
output_file “subtitle2.srt” –operation add — time 02,010
I just need to clarify this.
[Im brazilian, my english isnt very good, sorry.xD]
If you’re looking for a good source of srt files, try http://www.opensubtitles.org
Oo?
No, no, I am confused about the commands of the optparser.xD
I have given 2 links to tutorials on “optparse” here -
http://rubylearning.com/blog/2009/09/24/rpcfn-shift-subtitle-1/#comment-119306
Check them out.
I see these tutorials, but my doubt is about what each command has to do(shift_subtitle –operation add –time 02,110 input_file output_file). If you show me some examples will facilitate very much.xD
Thanks
Sure, –operation should receive either ‘add’ or ‘sub’(tract). –time should receive an amount of time to either add or subtract (according to the –operation option).
So, for instance if I do “–operation sub –time 03,540″ this means “subtract 3 seconds and 540 milliseconds from each time in the subtitle file”. Or if I do “–operation add –time 02,400″ this means “add 2 seconds and 400 milliseconds to each range of times in each line of the subtitle file”.
HugoLnx it works like this:
shift_subtitle is the name of your script that you call from the command line with the Ruby interpreter like this: ruby shift_subtitle.rb
Then you have the options –operation and –time. –operation specifies if the script should increase or decrease the time and –time specifies the amount of time. –operation is followed by either “add” or “sub” and –time is followed by the time like this “[seconds],[milliseconds]”
So if you want to add 1 minute you type ruby shift_subtitle.rb –operation add –time 1,000, and if you want to subtract 500 milliseconds you type ruby shift_subtitle.rb –operation add –time 0,500. You need to use optparser to implement this behavior.
Then the final two arguments are the files the script works with. Just as it sounds, input_file is original file that should have it’s timings modified and output file is the new file that is created with the new timings.
So if you have a file called test.srt and you want to increase the time with 5 seconds and save the result in a new file named result.srt you would type: ruby shift_subtitle.rb –operation add –time 5,000 test.srt result.srt
Solution (#4) – Winner:
Country of Residence: Sweden
My Solution (i.e. Ruby code): https://gist.github.com/fa3ec5afe7b19d22c44a
Code works with both Ruby 1.8 and 1.9
Explanation: I decided to read the whole file as a string (as opposed to a line by line based approach) and then do a gsub where I increment/decrement all the time strings. To be able to do arithmetic on the time values I first convert them into milliseconds and then format them to the desired format. Everything else is pretty minimal, there is no error handling or parameter checking for example, but it follows the spec.
The code worked in the first shot with my test file. Very straight forward, though not the best solution, but workable.
I like the use of constants and the idiomatically named to_ms method.
I ran the code. It worked well. I got no errors and got correct output. The code is simple, easy to read and has no tricks.
Solution (#5):
[Description]: Well, I’ve assumed the simple approach to get the solution. Actually what I did can be divided into some tasks:
[Country]: Brazil
[Code]: https://gist.github.com/92e2a8426d44a3d465ab
[Code Works]: Only tested in Ruby 1.8
I think there is no need to use exit! instead of exit in this case.
Nice layout, includes tests, fairly idiomatic Ruby. Not bad at all!
He worried about overall project structure, code readability, documentation, tests. But the algorithm itself is around the same idea as everybody else and not exactly the simplest.
Solution (#6):
Country of Residence: USA
Code works with Ruby 1.8 (don’t know enough to handle UTF-8 SRTs yet).
Code: https://gist.github.com/c60e13f3f43f22823f73
Overall he does the same thing as everybody else: convert the string to milliseconds, make the operation, format back to string. He also reads the entire file to memory instead of streaming it. It’s not bad though.
I like this solution. Good use of option parser, idiomatic Ruby. Just the whole could be improved upon.
Solution (#7):
Submitted for fun.
I’m from Brazil.
Here is my contribution: (actually I don’t expect any prizes, but it seemed fun to write such a solution
)
I really didn’t like the optparse from standard lib because of its bad documentation and lack of some features. When I first read the challenge, I thought I should implement the option parser, and I did it much faster than it took to learn how to use optparse appropriately, but here is the solution that uses optparse:
Code: https://gist.github.com/19b52b0d3aa6441dddda
This code was tested with 1.8, but I believe it should work also on 1.9.
Congratulations to RubyLearning for this initiative
I’m posting a new url with correct formating, since the code in the above link is not understandable:
http://gist.github.com/202717
Thanks, now I understand. ^^
“So if you want to add 1 minute you type ruby shift_subtitle.rb –operation add –time 1,000,”
I thought to add 1 minute would be …
–operation add-time 60,0
Thanks for the clarification.xD
Solution (#8):
From India.
Works on Both 1.8 and 1.9
Solution: https://gist.github.com/b0822d661c735cd3c161
Explanation :
Reads the input_file from STDIN, maps all time pieces with the new time and writes it to the output_file.
Test cases :
* Input_File *
645
01:32:04,283 –> 01:32:07,769
the government is implementing a new policy…
646
01:31:54,928 –> 01:31:57,664
In connection with a dramatic increase
in crime in certain neighbourhoods,
# ruby srt_convert.rb –operation add –time 11,222 Input_file.srt Output_file.srt
* Output_File *
645
01:32:15,505 –> 01:32:18,991
the government is implementing a new policy…
646
01:32:06,150 –> 01:32:08,886
In connection with a dramatic increase
in crime in certain neighbourhoods,
http://github.com/kitallis/RPCFN/tree/master/1/
If I have the time “00:00:00,574″, and I add “2,500″, I should have “00:00:03,074″, right? His algorithm gives me “00:00:03,740″, which I think is not correct. This happens because the Time#usec seems to be misleading, sometimes it gives back a 6 digit number, sometimes it gives back less digits. This guy thought it through, this is different than the others, but he lacks on readability and maintainability.
Solution (#9):
Country: Brazil
My Solution: http://github.com/fabiokr/ruby_learning_challenge_1
Code developed with a Ruby 1.8 environment
For me it’s giving me an error right off the bat:
$ ruby shift_subtitle.rb –operation add –time 2,500 input.srt output.srt
./lib/subtitle.rb:51:in `separate_into_blocks’: undefined method `text’ for nil:NilClass (NoMethodError)
from ./lib/subtitle.rb:39:in `each’
from ./lib/subtitle.rb:39:in `separate_into_blocks’
from ./lib/subtitle.rb:34:in `load_file_data’
from ./lib/subtitle.rb:4:in `initialize’
from shift_subtitle.rb:45:in `new’
from shift_subtitle.rb:45
So, it never actually finishes. It has a lot of unnecessary clutter, specially in the ‘blocks’ thing (which is what is giving me errors).
Solution (#10):
From USA
Solution: http://gist.github.com/195637
Only tested with Ruby 1.8
For me it’s giving a weird result, for example, if I have this input: “00:00:06,673″, and I add 2,500, it’s returning me “12:00:09,173″. If I ignore that, it has the same problem I described earlier about the usec padding: for example “00:09:49,563″ plus 2,5 is “00:09:52,063″ but it is giving me “00:09:52,630″, which is wrong. Again, this the correct direction, but there is this small usec treatment.
Personally, I like using open() method with block and using if statement like this:
if OPTIONS[:operation] || OPTIONS[:time]
instead of:
if OPTIONS[:operation] == nil || OPTIONS[:time] == nil
Solution (#11):
Submitted for fun
Brief description: Basically using String#scan with an appropriate
Regexp to create instances of entries. Offset is being saved using
instances of Time (and taking care of converting microseconds to
milliseconds)
Country of Residence: Germany
Special note: I am truly no newbie anymore, so I shouldn’t be taken
into account when choosing the winner. I just wanted to participate
for the fun of it, and maybe the code helps some of the others.
My Solution: https://gist.github.com/4234c8580a166428e5da
I began on Friday not really knowing much of anything about Ruby (other than some very minor Rails tutorials). I have almost completed this and have so far learned a lot. Definitely the right amount of challenge for me and worth spending the time on.
ruby shift_subtitle.rb –operation add –time 1,000 is supposed to add 1 second and not one minute to the time range.
Btw, what is the best way to test optparser?
On a different note: It would be a good idea if you could organize Rails competitions as well. The students could build different Rails plugins, while reading and learning Rails source code.
To test optparser and my CLI arguments I used RSpec and wrote tests like “should only accept the appropriate #,### format for time”. You can either throw an exception when something is incorrect, then expect it in your test, or immediately exit. I went for the latter, but testing exit can be tricky. I figured out an easy way to mock Kernel and posted it up on my blog (http://www.deploymentzone.com/2009/09/26/ruby-mocking-kernel-exit/).
Is it the best way? I dunno, this is my first foray into Ruby so probably not. But it works so I guess that’s something.
Solution (#12):
Country of Residence: USA
My Solution (i.e. Ruby code): https://gist.github.com/f776754f514f54bbeb26
Code works with Ruby 1.8 and 1.9
A bit too complicated and repetitive for my personal taste, but definitely not bad for a Python coder coming over to Ruby-land.
Worked right off the bat, correct results, but it was just too brute force for my taste.
Ran the code with Ruby 1.9.1 and got the output file well. It was converted correctly.
With Ruby 1.8.6, need to treat the message – undefined method ‘each_char’
Solution (#13):
Country of Residence: Serbia
My Solution: http://github.com/milandobrota/shiftsubtitle
Code Tested with ruby 1.8.6
Explanation: Custom OptionParser using optparse for parsing CL arguments + Shifter for actual time shifting and reading/writing to a SRT file.
Test cases: included with the link
Finally, the first one to actually do the gem and rspec tests. But, it lacked the last mile. He didn’t know about the ‘executable’ property in the gemspec so he did not tailor a proper ‘bin’ executable. The project lacks a ‘Rakefile’ so I could just run ‘rake spec’, and one of the spec files fails though. Again, I am having weird results. It feels like my environment is wrong or something, because I got everything shifted by 20 hours (min, sec, msec were correct though). I don’t have time to investigate, so I am assuming I am doing something wrong. He got the millisecond operations correctly.
Solution (#14):
Country of Residence: India
My Solution: https://gist.github.com/556d5864579da6bb60d7
Code works with Ruby 1.8.6
Feels like this code is incomplete as it tries to use stuff not declared anywhere such as ‘output_file’ and ‘ShiftSubtitle’, I don’t think it will run. The code is poorly indented. Feels like a C programmer taking care of lots of stuff such as stripping strings, taking care of numeric boundaries, but he missed the point.
Doesn’t use optparse and all and is rather verbose…
Solution (#15):
Submitted for fun
Code: https://gist.github.com/52cba60961f3723fddf3
Solution (#16):
Name: Hugo Roque de Figueiredo
Country of Residence: Brazil
Code works with Ruby 1.8
Explanation: http://www.4shared.com/file/136641094/630bdb62/SubTitle_Handler_Documentation_v0010.html
Solution: https://gist.github.com/89220c46effa04a01b95
A bit too long and complicated for my taste…
I think this is the most complicated variation of the “convert2msec/calculate/format2string” formula so far
But it fails to give me the correct results. Actually, it converts just the first time range of the file and ignores all the rest.
I will send my solution today, but I don’t see the message ‘waiting for a moderator’ or something like that. I just want to be sure that my solution is received.xD
Your solution is there. Don’t worry.
Does the “no external gems” restriction also apply to packaging the gem or just the strict functionality? (i.e. Can I use jeweler to create my gem?)
Yes, for gem creation it is ok to use external help, though I would argue that it will only add bloat to this small project
Any good links for manual gem packaging? I’ve always just followed the tools like jeweler and newgem because I didn’t know how to do it myself.
The following two links should answer your questions:
http://www.5dollarwhitebox.org/drupal/creating_a_rubygem_package
http://docs.rubygems.org/read/chapter/20
Solution (#17):
Country: USA
Code works with Ruby 1.8 / 1.9 / : yes!
Explanation: Created as a gem (using windows). Command line arguments are examined for correct usage and program exits with an error if it receives invalid values.
Test cases: didn’t get time to do this.
Code: https://gist.github.com/d900ab61c47bdcd0edc7
For me, it’s giving this error:
$ ruby shift_subtitle.rb –operation add –time 2,500 input output
shift_subtitle.rb:78:in `update_srt’: ’10′ is not a valid sequence number (RuntimeError)
from shift_subtitle.rb:140
Seems like he is not doing the correct regex. I changed line 74 for “if line =~ /^[0-9]+$/”. Then it runs, but the results are not ok. He is having the msec issue. For instance, his results are like “00:09:52,63″ when it should’ve been “00:09:52,063″. He knows about gem executable, which is good. Code is following the labor intensive formula again.
Solution (#18) – Winner:
Country: Brazil
My Solution: http://github.com/felipeelias/shift_subtitle
Code works with Ruby 1.8
Very Nice! This is the best by a large margin so far. He correctly followed the shorter algorithm using ‘Time’, he was aware of the msec issue and correctly did the ‘sprintf’ trick. So this is the first to pass my entire 3,000 lines test file perfectly. He also worried about readability, gem building, but testing was a bit lacking.
Nice, including tests and gemspec.
Solution (#19):
USA
Code tested with Ruby 1.8.6 on Windows (probably works with 1.9)
First real attempt at non-Rails Ruby using RSpec and unit tests… I had to use the ‘construct’ gem for my unit tests because I don’t think I could write a file system mocking framework in Ruby… yet. The actual program requires nothing external to what is provided with Ruby 1.8.6.
Solution: https://gist.github.com/22f423715334c756b3aa
A very complicated way to deal with subtraction. In my example solution I do this:
options[:time] *= -1 if options[:operation] == :subtract
so I can always just add (adding a negative number == subtracting).
Overall this is a pretty nice solution though!
Almost there, so close. He did the usec / 1_000 but forgot the sprintf(“%.3d”) trick to round it up. So he gives me results such as “00:03:16,74″ instead of “00:03:16,074″. The overall structure is not bad. I really expected people to modularize it more though. No gem building, some tests (couldn’t make them run), readable code overall.
Ok, last question. Any advice for dealing with the beginning when you’re removing time? I’m having trouble wrapping my head around what I should do when I come up with negative times at the beginning of the file. I could just drop them if they’re negative values but then I’ll either lose those subtitles or I’ll not have times to put on subtitles at the end. I had a pretty nice setup till I realized this. I don’t really think I’m capable of implementing a time_travel method so the computer can display subtitles before you open the video
Assuming that a lot of movies don’t actually have dialog from the very first second and subtitle shifts are usually small, I’d probably ignore it for this exercise.
Solution (#20):
Country of Residence: USA
Solution: http://github.com/hariis/RPCFN–Shift-Subtitle—1-
Tested with 1.8.6
The program is incorrect. For instance, I am getting results such as “00:11:60,099″, instead of “00:12:00,099″. He is missing boundary conditions on seconds (00:60 should round up to 01:00). No gem building, no tests, readable enough, poorly indented, still following the more brute force approach.
Solution (#21):
From Australia
Solution: https://gist.github.com/20b7ea938d7923944795
Code works with Ruby 1.9. Should also work with Ruby 1.8 but I only have 1.9 installed
This is a bare minimum attempt at the challenge. It will fail very inelegantly if there is anything wrong with the arguments. I also set it to never give a time less than 00:00:00,000 because that would obviously be pointless. I’m sure there is a much more elegant way of doing this and I am looking forward to seeing others’ solutions.
No test cases.
Couldn’t make it run. Didn’t stop to debug it, due to lack of time.
I like this for its simple and straightforward approach but I could not run the program.
Solution (#22):
I’m from the UK.
Source: http://github.com/olivernn/ShiftSubtitle
You can install my solution to the problem from gemcutter using:
gem install shift_subtitle.
I chose to package my script as a gem containing an executable file, once it is installed simply run shift_subtitle -h for the options to pass to it.
I decided to add a start position to my script so that if the subtitles are not quite synced properly then they can be shifted from a particular point.
I have included some unit tests for the classes used in the application too. I have tested this application with Ruby 1.8.6 only.
Almost there. He did a good job on maintainability, project organization, gem building. But, something went wrong. I followed the readme and it did run without any exceptions thrown or anything, but my output.srt came out blank.
Bah! I was having this problem before – it is because I originally had a regex that was checking that the input file was indeed a .srt file, this didn’t work so I removed it at the last minute, I must have forgotten to update the gem on gemcutter though. The correct version should be up there now.
Next time I will be more thorough with the versioning!
Solution (#23):
Not bad, the algorithm is in the right direction. But lacked on project structure. He also fell for the msec issue, giving me “00:03:16,74″ instead of “00:03:16,074″.
Hey Akita, thanks for taking the time to go over my code. This is such a great way to learn, so thanks everybody who is responsible for making this happen!
Regarding my solution … I briefly skimmed the other results and I think the msec issue is in there somewhere, so I’ll find that, but could you elaborate on proper project structure a little bit and some of the things that I could’ve done better in this area?
ps
No time to write tests or package as a gem, but definitely plan on looking into those things as soon as I can!
Again, thanks everybody, these are so great and I’m looking forward to the next challenge!
Well, as I said in the general comments below (http://rubylearning.com/blog/2009/09/24/rpcfn-shift-subtitle-1/comment-page-1/#comment-119436), as this exercise is actually small, having it all in one file may not be considered bad, but it’s always good to have in mind separating concerns. For instance, the smallest solution would be to have at least 2 files: one for the command line optparse stuff and another for the algorithm. Though that would lead you to want to wrap it up in a gem
Your code is particularly well written, no obscure namings, no giant one-liners. It is good as it is. Maybe just one small thing is the accumulating everything in one big string and then writing it down at once to a file at the end. Check out my first general comment below about file streaming.
Just as a comment for the exercise itself, I am being a little picky
Solution (#24):
Country of Residence: USA
Solution: http://github.com/thuss/shift_subtitle
Gem: http://gemcutter.org/gems/thuss-shift_subtitle
Code works with both Ruby 1.8 and 1.9
Explanation: I opted to create the Gem and go for 100% RSpec coverage including testing bin/shift_subtitle script. The gem itself requires no dependencies to use, but running the specs requires rspec and jeweler.
shift_subtitle –operation add –time 1,234 input.srt output.srt
didn’t work. Got an error:
shift_subtitle_cli.rb:45:in `pop’: wrong number of arguments (1 for 0) (ArgumentError)
I edited a bit like this:
arguments.pop(2)` –> `arguments[0], arguments[1]
It then worked well.
He went through the right path in terms of algorithm. Project structure is good. Gem build is ok. Rspec test support is good. I did have to use Satoshi’s (ashbb) fix, then it worked and was able to go through my entire test file flawlessly.
I’ve pushed a new version of the gem with this fix. So apparently the pop(n) method was added in Ruby 1.8.7 (which my new Macbook came with) but is not in Ruby 1.8.6. I even checked the API docs beforehand to make sure it was in both Ruby 1.8.7 and 1.9, but it never occurred to me to check if it was in Ruby 1.8.6:
http://apidock.com/ruby/Array/pop
Hopefully that doesn’t disqualify me!
Hi Todd,
Oh, sorry. It’s my fault. I’ve installed ruby 1.8.6 and 1.9.1 in my pc.
Now I confirmed with ruby 1.9.1. Your solution works well. No errors.
> Hopefully that doesn’t disqualify me!
hahaha. Don’t worry. Your solution is great.
D:\work>ruby -v
ruby 1.9.1p243 (2009-07-16 revision 24175) [i386-mswin32]
D:\work>irb
irb(main):001:0> a = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.pop(2)
=> [4, 5]
Solution (#25):
Nice, worked perfectly right off the bat with the entirety of my test file, including the dreaded msec issue. It’s simple, readable enough. Lacks on project structure.
Solution (#26):
Country: India
Solution: https://gist.github.com/26362d5f62ecc8351a16
Code works with: Ruby 1.8
The script worked right off the bat, passed through my test file. He did the 1st style of milliseconds convert and re-format. Not bad. Well written code overall.
Thanks for taking your time to review the solution.
Thanks to the Ruby Learning team for organizing such a challenge. I got to learn a lot in the process. Looking forward to the next challenge …
Solution (#27):
Your name: Giordano Scalzo
Country of Residence: Italy
My code is on
http://github.com/gscalzo/ShiftSubtitle
Code tried with 1.8, but it should work with 1.9 too
Test cases: the code was written in TDD using Rspec, in repository you will find the specs to.
I created a gem specification too, shift_subtitle.gemspec.
I learned a lot, I’m quite satisfied with the result, but I would have clean it a little more… I’m looking forward to next quiz!
Project with good structure. a little bit of over-engineering having lib/add.rb and lib/sub.rb feels very unnecessary. Gem building works. Reading the code feels like it is going to work or come close, but I had this issue:
$ ruby bin/shift_subtitle –operation add –time 2,500 ../input.srt output.srt
Shifting with following parameters:
Operation: [add]
Time: [2,500]
Input file: [../input.srt]
Output file: [output.srt]
./bin/../lib/../lib/../lib/operation.rb:20:in `as_time’: uninitialized constant Operation::Date (NameError)
from ./bin/../lib/../lib/add.rb:7:in `operation’
from ./bin/../lib/../lib/../lib/operation.rb:7:in `on’
from ./bin/../lib/../lib/operation_performer.rb:25:in `evaluate’
from ./bin/../lib/../lib/operation_performer.rb:14:in `perform’
from ./bin/../lib/../lib/operation_performer.rb:8:in `on’
from ./bin/../lib/../lib/operation_performer.rb:7:in `map’
from ./bin/../lib/../lib/operation_performer.rb:7:in `on’
from ./bin/../lib/shift_subtitle.rb:64:in `processed_lines’
from ./bin/../lib/shift_subtitle.rb:86:in `start’
from bin/shift_subtitle:7
Didn’t look close to debug.
I tried on Windows and on Linux and it worked… on Mac don’t!
It needs
require ‘date’
require ‘time’
in operation.rb
I really have to buy a Mac
Anyway, I had the padding usec bug too… I feel a little dumb
I pushed my corrections.
Giordano look at this challenge positively. We all have had a great chance to interact with the experts and improve our Ruby further.
With Ruby 1.8.6, gem build/install worked well. Ran shift_subtitle command and got the output file well. It was converted correctly. The code is readable, but 5 classes defined are a bit verbose for my taste.
Solution (#28):
Might as well register for the contest even though I sure there’s no way I’m going to win it. People on here are super smart. Thanks for the fun challenge BTW. I’ve been looking for a Ruby quiz that wasn’t super easy or super impossible. This one was just right, every time I hit a wall I was able to find a way around it.
USA
http://github.com/philk/subshifter
Code is all on github, hopefully the documentation makes sense and it works correctly for you guys.
I was having trouble the lines 42-56 of bin/subshifter so I just took the validations off. This code was able to go through my entire test file correctly! He did it through the brute force approach though, but it’s ok. Gem build is ok, with spec, executables, etc. Good test coverage. Good overall project structure.
Gem build / install worked well. But when I ran the command ‘subshifter –operation add –time 1,234 -i input.srt -c output.srt’, it didn’t work as I expected. Please look at bin/subshfter line 42-46 and 49, 50, 52. Are they in the right place? And one more, please confirm the result with ‘-o remove’ case.
Congratulations for everybody that participated. Just taking your time to think of a solution was good enough. I’ve looked through all of the solutions and I have a few comments:
* Nobody seemed to have had problems on the optparse usage, file manipulation, which is good. Some solutions read the whole file in memory before manipulating it. An Srt file is not big, but just for the sake of it, I would suggest going for the ‘streaming’ pattern, as it’s not more difficult. Something like this:
File.open(output_file, "w") do |output| File.readlines(input_file, "r") do |input| output.write some_operation(line) end end* The majority of the solutions were a single file with everything in it. As the solution itself is pretty small maybe it’s not a big problem, but I would recommend structuring your projects a bit more, for instance having a ‘bin’ folder for the executable, ‘lib’ for the libraries, ‘spec’ for the rspec tests and so forth.
* Some of the solutions had gem specs, which is great. But some were not aware that you have the ‘executable’ property. Another good practice maybe to have a rake task to regenerate your spec file to make sure you have the correct version and all the files listed. Github doesn’t allow for I/O scripting within a spec file, so you can’t use “Dir.glob” to dynamically add all your “lib” files, for example, so you have to write it down manually or have a script to regenerate the spec. Look into my linked solution for an example of this approach.
* There were mainly 2 style of solutions for the time shifting itself. The first one was the “parse string time to milliseconds, then calculate, then format back to string”. Most of the solutions went for this approach. This usually leads to the correct answer but is very labor intensive. The second way is to “require ‘time’” and use the Time class. You do Time.parse and then just calculate over it. There is a trick, though, the Time#usec method can be misleading, and when you format back to string you will end up with the wrong answer. Check out my solution in the link at the end of this post for one example. Some people were able to get it correctly in their solutions.
* Finally, code style. I would say that half of the solutions were more “Ruby-like”, some were more “Bash-like”
But seriously, I would take more care of indentation, having more understandable naming for methods and variables. This is very important for maintainability.
Overall, most of the solutions were able to get it through. A few of them went all the way to have tests and gem building. Again, congrats for the effort. I hope it motivated some of you to do research and try new things.
Hm, an idea for the next challenge: Tell everyone to directly use github/gists, saves you from creating the gists yourself. Plus, looks like you actually screwed up: I saw lots of < and " (HTML escape characters, in case it converts them in this comment) in the gists, which probably happened when you simply copy & pasted the comments into gists.
Yes, we all learn with time. I have already changed the same for the #2 challenge onwards -
http://rubylearning.com/blog/ruby-programming-challenge-faq/#rpc5
Hopefully, that would avoid the problems we faced in #1.
Thanks for the suggestion.
Hello,
I guess solution #26 was missed. Can you let me know if there was an issue.
Thanks.
Sriram, Fabio will be evaluating your solution asap. Don’t worry.
Next Comments →
{ 22 trackbacks }