RPCFN: Average Arrival Time For A Flight (#2)

by on October 8, 2009

Ruby Programming Challenge For Newbies

RPCFN: Average Arrival Time For A Flight (#2)

By Chris Strom

Thank you for the very encouraging response to the first-everRuby Programming Challenge For Newbies (RPCFN)“. The second Ruby challenge is from Chris Strom.

About Chris Strom

Chris StromChris Strom (twitter / blog) in his day job, is the Director of Software Engineering for mdlogix, a small company in Baltimore, Maryland. They develop software that manages clinical research trials and associated data. They primarily code with Ruby on Rails. His background is in web development, mostly in Perl until ~2005 when he made the switch to Ruby.

Chris has this to say about the challenge:

RPCFN is a good idea as reading books and documentation can only take you so far when learning a new language. To really learn, you need to use the language. RPCFN provides a fabulous forum for using Ruby in the form of regular, engaging (but not arcanely difficult) challenges. Better yet, it provides feedback on how to use Ruby well, as each fortnight the best solution to a challenge is chosen. RPCFN is a wonderful introduction to the Ruby language and to the Ruby community. Welcome newbies!

Sponsor

Railsware for premium-quality web applications

This fortnights programming challenge is sponsored by Railsware. Railsware is glad to support the Ruby Programming Challenge and help the Ruby community grow and get stronger.

Railsware is a product development company specializing in Ruby on Rails and UI design creating premium-quality web applications. The company works with startups and established businesses looking to build ecommerce, social networking, specialized business applications and many other products.

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 BDDCasts’ screencasts.

The two persons who win, can’t win again in the next immediate challenge but can still participate.

The Ruby Challenge

RPCFN

You owe a big favor and have agreed to pick up a friend at the airport every Friday night. The airline on which your friend flies is cheap, but terrible with reporting delays and departure/arrival times. You soon realize that the 10pm flight is never on time and is usually late by more than an hour. If the plane has arrived at 11:15pm, 12:03am, 11:30pm, 11:23pm and 11:48pm, what is the average arrival time?

Does the solution still work if your friend changes to a flight arriving 6 hours later? What about 12 hours later?

Program Output

The output should look something like this when run from the console:

>> average_time_of_day(["11:51pm", "11:56pm", "12:01am", "12:06am", "12:11am"])
=> "12:01am"

>> average_time_of_day(["6:41am", "6:51am", "7:01am"])
=> "6:51am"

Hint

  • Your digital ways will not help you, time of day is cyclical.
  • You may need to use the Math and Time classes.

Requirements: This has to be a pure Ruby script, using only the Ruby Standard Libraries (meaning, no external Gems). You do not need to build a gem for this. Pure Ruby code is all that is needed.

How to Enter the Challenge

Read the Challenge Rules. By participating in this challenge, you agree to be bound by these Challenge Rules. It’s free and registration is optional. You can enter the challenge just by posting the following as a comment to this blog post:

  1. Your name:
  2. Email address (will not be published):
  3. Brief description of what you do (will not be published):
  4. Country of Residence:
  5. GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases:
  6. Code works with Ruby 1.8 / 1.9 / Both:

Note:

  • As soon as we receive your GIST URL, we will fork your submission. This means that your solution is frozen and accepted. Please be sure that is the solution you want, as it is now recorded in time and is the version that will be evaluated.
  • All solutions posted would be hidden to allow participants to come up with their own solutions.
  • You should post your entries before midnight of 18th Oct. 2009 (Indian Standard Time). No new solutions will be accepted from 19th to 22nd Oct. 2009.
  • On Monday, 19th 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 on 22nd Oct. 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:

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.

Click here to lend your support to: Support RubyLearning With Some Love and make a donation at www.pledgie.com !

Acknowledgements

Special thanks to:

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 prizes and some are participating for the fun of it.

In the competition

  1. Othmane Benkirane, Morocco – declared winner
  2. Tisho Georgiev, Bulgaria
  3. Pete Campbell, USA
  4. Jonathan Julian, USA
  5. Antonio, Canada
  6. Robison WR Santos, Brazil
  7. Ricardo Duarte, Brazil
  8. Paul Barry, USA
  9. Haris Amin, USA
  10. Charles Feduke, USA – declared winner
  11. Oliver, UK
  12. Bryan Liles, USA
  13. Gunther Diemant, Germany
  14. Valério Farias, Brazil
  15. Vikas Maskeri, India
  16. Jiren Patel, India
  17. Stefan, Germany
  18. Ahmed Al Hafoudh, Slovakia
  19. Tom Voltz, USA
  20. David Jenkins, USA
  21. Michael Lang, USA
  22. Thiago Fernandes Massa, Brazil
  23. Tim Rand, USA
  24. Milan Dobrota, Serbia
  25. Mike Hodgson, Canada
  26. Brad O’Connor, Australia
  27. Giordano Scalzo, Italy
  28. Rainer Thiel, New Zealand
  29. Todd Huss, USA
  30. Pankaj Sisodiya, India
  31. Loïc Paillotin, USA
  32. Chuck Ha, USA
  33. Josh Baxley, USA
  34. Javier Blanco Gutiérrez, Spain
  35. Sogo Ohta, Japan
  36. Daniel Wanek, USA
  37. Himansu Desai, USA
  38. John McDonald, USA
  39. Ben Miller, UK
  40. Sriram Varahan, India
  41. Conner Peirce, USA
  42. Ben Marini, USA

Just for Fun

  1. Michael Kohl, Austria
  2. Peter Cooper, UK

The Winners

Winners

Congratulations to the winners of this Ruby Challenge. They are:

Previous Challenge

RPCFN: Shift Subtitle (#1) by Fabio Akita.

Next Challenge

RPCFN: Short Circuit (#3) by Gautam Rege.

Update

  • This Challenge is now closed. Chris Strom has a working solution to this problem. This is not a “perfect” or the sole “correct” solution, but just one way of doing it.
  • Chris Strom has written a blog post that talks about the most common “issues” faced by Ruby beginners.
  • The (#3) challenge by Gautam Rege, India is scheduled for 1st Nov. 2009.
  • The (#4) challenge by Michael Kohl, Austria is scheduled for 1st Dec. 2009.
  • The (#5) challenge by Peter Cooper, UK is scheduled for 1st Jan. 2010.

Technorati Tags: , , , , ,

Posted by Satish Talim

Follow me on Twitter to communicate and stay connected

{ 171 comments… read them below or add one }

Milan Dobrota October 8, 2009 at 2:21 pm

Do we have to build a gem? If we put the code on gist, how are we going to make a directory structure?

Reply

Chris Strom October 8, 2009 at 2:46 pm

You do not need to build a gem for this. Pure Ruby code is all that is needed.

Reply

Kalle Lindström October 8, 2009 at 3:50 pm

Looking at your example, how can the average of 11:51pm, 11:56pm, 12:01am, 12:06am, 12:11am be 12:01am? Shouldn’t it be 9:37am?

To illustrate, the sum of those times in 24 hr time is 23:51 + 23:56 + 00:01 + 00:06 + 00:11 = 48:05
48:05 = 2885 minutes
2885 / 5 = 577
So the average time is 557 minutes which is 09:37am if you round up, or 09:36am if you truncate. (Which one should we do, by the way?)

Reply

Chris Strom October 8, 2009 at 4:07 pm

Ah, therein lies the challenge :)

If you do a straight addition of one minute before midnight and one minute after midnight, you’ll end up with 24:00 — averaging that, you’ll wind up with an answer of noon. Clearly the average of a minute before midnight (23:59) and a minute after midnight (00:01) is midnight, not noon.

The question is, how to get the right answer?

Reply

Brad O'Connor October 8, 2009 at 4:08 pm

It’s an interesting (and slightly tricky) problem (although this challenge seems a lot easier than #1). The issue is obviously that the times cross midnight and we need to include midnight in the range of times that we are averaging (i.e. we need to see the times as 23:51, 23:56, 24:01, 24:06, 24:11).

The tricky part is when there are wide ranges of times, where do we consider the start and finish of the interval to be. Do we assume that the first time given is the start time and the last is the end time? Is it a requirement of the script that the times be in order? As this hasn’t been specified in the challenge I suppose we will have to make our own assumptions about the best way to deal with this.

Reply

David Jenkins October 9, 2009 at 10:16 pm

As you pointed out, we think definitely have to assume that the first time in the array is the earliest time, and the last time is the latest, otherwise there are definitely 2 different answers per array.

Reply

David Jenkins October 12, 2009 at 6:16 pm

that should have read “…I think we definitely have to assume that the first time in the array is the earliest time…”

I’m getting dyslexic in my old age!

Reply

Felipe Giotto October 8, 2009 at 4:19 pm

Should we consider the plane arriving earlier?? If so, you’ll have to define the “limit” between the plane being late or not. Example: The plane should arrive at 11:00pm. But, it arrives at 10:50pm. Probably it’s early, right? Now, consider this: The plane should arrive at 11:00pm. But, it arrives at 3:00pm. Is it 8h early or is it 16h late?? I think it’s 16h late, but you should define this “limit” as a rule for this challenge, otherwise any competitor will have his/her own solution!

Reply

Chris Strom October 8, 2009 at 5:04 pm

But every competitor should have their own unique solution!

Solutions will be judged based on how well they answer the question exactly as stated, how well they apply to slight shifts (similar to those in the problem), even how close they come to being completely generalizable (i.e. the wide variances you describe or the order of the times as Brad posits).

But mostly, we’re looking for some beautiful Ruby :)

If you come up with an elegant solution, given several constraints, we may very well choose that over an ugly solution with only one or two constraints. Of course, if you come up with an elegant solution that covers all cases without constraints, you’re even more likely to win. But I can’t say if that’s even possible.

Reply

Rhyhann October 8, 2009 at 6:07 pm

Solution (#1):

1. Your name: Othmane Benkirane
4. Country of Residence: Morocco
5. GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases: http://gist.github.com/205002 . Works with the given test case. Neither uses Math nor Time.
6. Code works with Ruby 1.8 / 1.9 / Both: Both

Reply

Michael Kohl October 8, 2009 at 6:15 pm

Pretty good, but would suggest to pay attention to output formatting (e.g. 12:1am)

Reply

Chris Strom October 8, 2009 at 6:47 pm

Love the idea of basing around the epoch, but, for those of us whose system’s times are GMT, shifts occur:

>> average_time_of_day ["6:41am", "6:51am", "7:01am"]
=> “01:51am”

The $am_pm global is always going to be false, so the strftime will always use the second option in the ternary — that’s for reviewer? Inline comments and/or test included would have helped.

All in all, very nice.

Reply

Rhyhann October 11, 2009 at 1:57 am

Hi,

I have made some changes to my script. Will you review all the codes in 09/18 or have you already picked mine up ?

Reply

Satish Talim October 11, 2009 at 6:28 am

We have already picked up all the submitted solutions and they are under review.

Reply

Dave Lilley October 19, 2009 at 2:56 pm

Please take the comments as something to build upon and keep on coding.

Reply

brainfck October 8, 2009 at 7:36 pm

Are we allowed to make use of require ‘time’ ?

Reply

chris October 8, 2009 at 8:07 pm

There are no restrictions on which libraries you may use. So yes, you may require ‘time’.

Reply

Tisho Georgiev October 8, 2009 at 10:12 pm

Solution (#2):

Hey guys,

Here’s my solution to the challenge:
https://gist.github.com/79f50506f6b7a8a553ce

Short, sweet and to the point. I’ve commented some parts for extra obviousness. Tested on Ruby 1.8.7, 1.9.1, 1.9.2. Gives the correct results when tested against the provided examples and even gives the correct average when your friend decides to make you wait for another 12 hours :)

Fun challenge, can’t wait for the next one,
Based in Sofia, Bulgaria

Reply

Chris Strom October 19, 2009 at 3:00 pm

I think the time local variable inside the inject block would have been better named date_time or date. Adding 1 to a time object normally adds 1 second, not one day. It took me a couple of reads to figure out what was happening in there as a result.

Reply

p.campbell October 8, 2009 at 11:48 pm

Solution (#3):

https://gist.github.com/0b30034d3f1e56fb4add

The code has lots of descriptive comments that describe the solution. In it I create two arrays, AM_TIMES and PM_TIMES. I would have liked to make these point to the objects in the original TIMES array and be able to modify the TIMES values when AM_TIMES or PM_TIMES is updated, but I don’t think this is allowed in Ruby. So instead I have to make copies instead (less efficient but more readable I guess).

I live in the USA.

Reply

Chris Strom October 19, 2009 at 3:05 pm

I like :) The approach and comments were solid. A bit of a nitpick… the each_index should have been a map! — idiomatic Ruby calls for a bang operator when there is a side effect / change. Also, it would have been nice to have the test suite included.

Reply

p.campbell October 19, 2009 at 6:45 pm

Thanks Chris, I appreciate the feedback. Sorry for not including the test suite.

FWIW, you can also try this test data to see if the order of times is important: ’12:10am’, ’11:50pm’, ’12:10am’. The correct solution should be ’12:03am’, not ’08:03am’.

Reply

p.campbell October 19, 2009 at 6:57 pm

To clarify your ‘map!’ comment, I believe you meant that I should make this change:

# Original line, modifies contents of AM_TIMES array
am_times.each_index { |t| am_times[t] += TWENTYFOUR_HOURS }

# Replacement code that uses a method name containing ‘!’ to indicate that the original array contents are being modified. They synonym #COLLECT! could also be used and is probably more common (at least it would be to me)
am_times.map! { |am_time| am_times += TWENTYFOUR_HOURS }

Thanks again for the feedback.

Reply

Jonathan Julian October 9, 2009 at 12:50 am

Solution (#4):

Name: Jonathan Julian
Country of Residence: USA
GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases: https://gist.github.com/562ae4d9557bc351d451
Code works with Ruby 1.8 / 1.9 / Both: Both

Reply

Chris Strom October 19, 2009 at 3:06 pm

Lovely to have tests included. Good documentation. The cutoff was a reasonable constraint, but obviously it fails for times that cross it (9pm and midnight, or noon and 11:58pm.

Reply

kainage October 9, 2009 at 2:51 am

Solution (#5):

1. Name: Antonio
4. Country of Residence: Canada
5. GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases: http://gist.github.com/205447
6. Code works with Ruby 1.8 / 1.9 / Both: 1.8

Reply

Chris Strom October 19, 2009 at 3:08 pm

The parse_time call is not side-effect free. The @times instance variable is initialized in the average_time_of_day method, but is altered by the parse_time method. It’s not a problem in this case, but if any other methods start mucking with @times, trouble will ensue.

The divmod was nice and the second sprintf was more concise, though definite props for using % instead of sprintf.

Some comments and/or tests would have helped to follow the flow the code — especially the conditional in parse_time.

Reply

Robison WR Santos October 9, 2009 at 5:02 am

Solution (#6):

[Description]: This code takes in consideration that the user passes the list of time wondering to know which is the best time to take his/her friend at the airport. This list must have the lesser known time the flight has ever landed in the first place, so it’s possible to assume some base to make all the computation.

With that (the lesser value is the first value), what is done is:
- Pass all the value to its float value
- Get the first value and uses it as the calculation base
- Calculate the time difference between this base and the other values
- Sum all these differences, so we get the amount of time between all the values
- Calculate the average difference
- Sum this average with the base time
- Return the formatted average time

[Country]: Brazil
[Code]: https://gist.github.com/fbce018487644b33eb74
[Works]: Tested only with ruby 1.8

Reply

Jeff Savin October 19, 2009 at 3:10 pm

Very short and sweet. Easy to read code, plus well commented and worked on every test except:
["12:01am", "11:59pm"] which output 12:00pm and which I believe should be 12:00am.
Elegant solution, however.

Reply

Michael Kohl October 19, 2009 at 3:12 pm

I like this a lot, probably because it uses the same approach as my solution (average of deltas). Shame it doesn’t pass Jeff’s test, that’s easy enough to avoid…

Reply

Chris Strom October 20, 2009 at 6:40 am

Nice! But, ['11:10pm','11:15pm','12:03am','11:30pm','11:48pm'] should average out to the same thing that ['11:10pm','11:15pm','12:03am','11:30pm','11:48pm'] — order shouldn’t matter. That’s a function of assuming the first value in the list is the lowest.

That aside, great comments and I very much appreciate the inclusion of test cases.

Reply

Ricardo Duarte October 9, 2009 at 5:14 am

Solution (#7):

# Brief description:
1. Parse strings
2. Add 24 hours at times in the past
3. Use division and ceil to find middle element in array
4. Format output as example

# Country of Residence:
Brazil

# GIST URL:
http://gist.github.com/205549

# Code works with Ruby 1.8 / 1.9 / Both:
Both

Reply

Chris Strom October 19, 2009 at 3:15 pm

I like the approach of shifting things around Time.now. An inject rather than the each would have been more idiomatic Ruby. The code could have benefited from comments to aid in readability. A test harness would have been nice. Overall a nice approach / good solution, except that it picked a median value, not an average.

Reply

Paul Barry October 9, 2009 at 5:21 am

Solution (#8):

I live in Baltimore, MD. Here’s my solution:

https://gist.github.com/b094867867d9d1d2d5ee

Reply

Chris Strom October 19, 2009 at 3:18 pm

Very much like the solution. The use of plus_or_minus_one_day with closest was inspired.

Reply

Jeff Savin October 20, 2009 at 12:29 pm

For some reason, the strftime(“%l:%M%p”) comes across as a number 1 instead of the letter I. Once I changed that, it worked fine.

Reply

hamin October 9, 2009 at 5:38 am
Michael Kohl October 19, 2009 at 3:19 pm

Nice solution, but the method will always return nil. Unfortunately this lost it quite a few points in my book…

Reply

Chris Strom October 20, 2009 at 6:41 am

Good comments. Test cases would have helped illustrating your thinking a bit more. As Jeff mentions, flirting with the boundary conditions causes problems (the average of midnight and 9pm was 10:30am). As the comments mention, the solution might have been generalized to handle this case. It would have been interesting to see that :)

I would suggest replacing the “each” block with an “inject” block instead (sum = inject(0)…). Also, some comments before the conditional in the each block would have aided in comprehension.

All, in all … nice!

Reply

Charles Feduke October 9, 2009 at 6:06 am

Solution (#10):

1. Charles Feduke
4. USA
5. https://gist.github.com/5b371226faf83af50d7e
6. Tested 1.8.6 (on Windows)

Reply

Michael Kohl October 19, 2009 at 3:21 pm

This is some beautiful Ruby, definitely one of my favorite solutions so far! I’m impressed!

Reply

Chris Strom October 20, 2009 at 6:42 am

Nice :)

It would have been more idiomatic Ruby to compute seconds as:

seconds = times.map {|time| Time.parse(time).to_i}.sort

Reply

Oliver October 9, 2009 at 7:16 am

Solution (#11):

name: Oliver
country: UK
Description: My solution is all in a single ruby file for simplicity. I assumed that the times would be in order, earliest arrival time first. I also assumed that the range between the earliest and latest arrival time is never more than 24hours.
code: https://gist.github.com/b9c01abfc0c209113eb4

Reply

Chris Strom October 19, 2009 at 3:22 pm

The epoch does not start at 01:00 everywhere — just for your timezone. Thus your solution does not work for me in the eastern time zone. You would need to add something like “getgm” to get this to work without the timezone adjustment.

Additional inline comments would have helped — specifically why this line:

if time.hours < arrival_times[i-1].hours

I believe this is the cause the incorrect output for things like ["11:51am", "11:56am", "12:00pm", "12:06pm", "12:11pm"] (output was 7:12pm) and %w{12:00pm 06:00pm} (output was 9:00am).

Nice job encapsulating the ArrivalTime class and monkey patching the Array class. Good overall approach.

Reply

Bryan Liles October 9, 2009 at 7:18 am

Solution (#12):

The challenge was vague. My solution is correct considering there is no way to determine the date of a time.
My name is Bryan Liles and I’m from the USA.
http://gist.github.com/205635

TATFT!

Reply

Dave Lilley October 19, 2009 at 3:26 pm

Failed my malformed time, no comments for those who may need to maintain code, Good use of unit testing and the breaking down of tasks.

Reply

Chris Strom October 20, 2009 at 7:08 am

A bit too much indirection, I think. I am unclear why the convert_to_24h method doesn’t just return seconds — the convert_to_seconds method has to parse the output from this method (which is itself RegExp parsed).

The solution breaks down on the midnight boundary condition.

Reply

Gunther Diemant October 9, 2009 at 7:33 am

Solution (#13):

Country of Residence: Germany
GIST URL: http://gist.github.com/205429
Code works with Ruby 1.8.6, 1.8.7 and 1.9.1

Reply

Jeff Savin October 19, 2009 at 3:27 pm

Very nice comment introduction laying out strategy and thoughts. Code worked like a charm, passed every single one of my tests. A little more code than some of the other solutions but it was more readable than some of the others as well. Overall, very nice.

Reply

p.campbell October 19, 2009 at 8:22 pm

What is the ‘correct’ average time for ['11:00pm', '01:00am']? Both ’12:00pm’ and ’12:00am’ could be considered correct, based upon your initial assumptions I guess.

Reply

Jeff Savin October 20, 2009 at 8:47 am

I would say that the correct average time for something like ['11:00pm', '01:00am'] would be 12:00am, midnight, although you are right, depending on certain assumptions, 12:00pm could be correct. I tend to think that since both times are closer to 12:00am, that this would be the average. In case such as ['12:00pm', '12:00am'] where they are 12 hours apart, both 6:00am and 6:00pm would be correct as both times are the same distance apart. Having said all that, what do I know?? lol

Reply

Chris Strom October 20, 2009 at 7:41 am

Nice — passes all of my tests and looks like nice, idiomatic Ruby. Some comments above the partition in average_time_of_day, as well as above the best_average method, describing strategy would have aided in readability. I prefer to have tests as well, but all in all, well done!

Reply

Valério Farias October 9, 2009 at 9:59 am

Solution (#14):

I made a simple method, using the class Time.
The gist link is: https://gist.github.com/5cb7ba3254b511deaab7

Update: Solution (#14) with a tiny modification.

I don’t know if I can do modifications in the code. But if you consider this possibility, I added a new line command to sort the array list in the beginning of the method.

https://gist.github.com/5cb7ba3254b511deaab7/ce8a71d43a5caa97ad3d7e2bcc823facb5c255f8

I’m from Brazil

The code works with Ruby 1.8

Reply

Jeff Savin October 19, 2009 at 3:28 pm

Concise code, but didn’t get expected results on my tests.

Reply

Chris Strom October 21, 2009 at 7:22 am

No need for the bang sorts. The first two lines could have been done as:

array_in_minutes = list.sortcollect{ |item| (Time.parse(item).hour * 60) + (Time.parse(item).min) }.sort

Nice, short and sweet. Would have been nice to include a test harness, but decent comment. The method returns a median, not an average, which explains some unexpected results.

Reply

Michael Kohl October 9, 2009 at 12:12 pm
p.campbell October 19, 2009 at 8:10 pm

I get this error when trying to load the code (windows or OSX, ruby 1.8.6 patchlevel 287):

TypeError: wrong argument type Symbol (expected Proc)
from ./kohl.rb: 10 in ‘average_time_of_day’
….

Reply

Michael Kohl October 20, 2009 at 11:41 am

My code uses Symbol#to_proc, which means it needs at least 1.8.7 to run (or you use Facets with 1.8.6). I use 1.9.1 pretty much exclusively nowadays and don’t usually care about 1.8.6 compatibility, it’s time to move on. :-)

Reply

Vikas Maskeri October 9, 2009 at 12:58 pm
Jeff Savin October 19, 2009 at 3:29 pm

Not sure what happened here, but I get following error when running code:
>ruby avg_time_of_day.rb
avg_time_of_day.rb:6:in `average_time_of_day’: undefined method `end_with?’ for “01am”:String (NoMethodError)
from avg_time_of_day.rb:4:in `each’
from avg_time_of_day.rb:4:in `average_time_of_day’
from avg_time_of_day.rb:29
>Exit code: 1

Reply

Chris Strom October 21, 2009 at 7:21 am

You initialize the y array, but never use it. Comment you have aided readability / comprehension. I am not sure why the conditional checks am and the hour is less than 10. Why not count 10 and 11? What about 12am? Speaking of 12, if the total hours is 12am or 12pm, the method will output blank before the colon (e.g. :34 instead of 12:34). The average_hours mod twelve caused this. Probably just needed to output 12 when that value was zero.

All in all, well commented code. I personally would have appreciated tests, but the comments help to illustrate things.

Reply

jiren October 9, 2009 at 1:33 pm

Solution (#17):

Country of Residence: India
GIST URL : https://gist.github.com/b0dde1717ef9f15ef9c4
test cases:
print AverageTimeFinder.new.average_time_of_day(“10:0pm”,["6:41am", "6:51am", "7:01am"])

Code works with Ruby 1.8 / 1.9 / Both: yes

-add all difference of actual time – delayed time , if comes negative then add to 24. means no need to convert all time to 24 hours format.
-find average all difference.
-add average to actual time is average time of the day

Reply

Jeff Savin October 19, 2009 at 3:30 pm

Jiren’s solution flew through my repertoire of tests with flying colors getting them all right. The added checking for correctly formatted times was a bonus and code is easy to read.

Reply

Michael Kohl October 19, 2009 at 3:31 pm

Good code, readable and you can see that the author put some thought into it (e.g. check_format method). My only complaint is that the code could be a bit more idiomatic, e.g. no print “string” + method call + “\n” etc.

Reply

Chris Strom October 21, 2009 at 7:20 am

Nice. Has issues around the boundary, but that’s to be expected, given the first argument:

AverageTimeFinder.new.average_time_of_day(“10:0pm”, %w{6:00pm 12:00am})
=> “9:00am”

Idiomatic ruby is to raise an exception in the check_format method, not print an error and exit (same goes for the else block in the average_time_of_day method).

I would also suggest that a more ruby-way of handling the sum inside average_time_of_day is with an inject block rather than defining the local sum variable outside of the each block.

Aside from those quibbles, nice :)

Reply

Stefan October 9, 2009 at 1:40 pm

Solution (#18):

* Your name: Stefan
* Country of Residence: Germany
* GIST URL: https://gist.github.com/71001d595386112ed45c
* Code works with Ruby 1.8 / 1.9 / Both: Tested with Ruby 1.8.6, but it should also work with 1.9

Reply

Michael Kohl October 19, 2009 at 3:32 pm

Concise and elegant, the only thing I would change is making REGULAR_ARRIVAL_TIME a default parameter of average_time_of_day, so that a user could use the method without having to edit around in the source file. :-)

Reply

Stefan October 19, 2009 at 4:33 pm

Yes, this would be better, but I implemented the method as used in the challenge description.

Reply

Michael Kohl October 19, 2009 at 4:56 pm

Easy enough:

def average_time_of_day(ts, d=”10:00pm”)

end

This way you don’t have to call it with a parameter for this challenge, but can if you need to.

Reply

Stefan October 19, 2009 at 7:12 pm

Yep, I know that;-)

Chris Strom October 21, 2009 at 7:18 am

Very nice. Ideally, it would have worked without the constraint of needing a REGULAR_ARRIVAL_TIME, but that is a reasonable constraint to impose.

I do not like altering the input parameters to the method. In Ruby, the convention is to signify that a method alters inputs by ending the method name with a bang (like the map! that does the altering). In this case, there is no need to alter the input — chaining the collect (without the bang) and the inject together would have accomplished the same thing without altering the input.

That aside, it is a nice, simple solution. Easy to understand, especially due to the copious comments.

Reply

alhafoudh October 9, 2009 at 4:47 pm

Solution (#19):

My name: Ahmed Al Hafoudh
Country of Residence: Bratislava, Slovakia, Europe
GIST URL of my Solution: https://gist.github.com/1767d68f93e2873be64d
Code works with both ruby versions:

Reply

Jeff Savin October 19, 2009 at 3:34 pm

Another short solution. This one failed on a couple of my tests below as follows:

a = ["12:01am", "11:59pm"] => “12:00pm” should be am
a = ["11:58pm", "12:00pm"] #=> “5:59am” should be pm

Reply

Chris Strom October 21, 2009 at 7:17 am

A nice, simple solution. The solution does not account for randomized data (as Jeff noted). For example:

average_time_of_day(%w{06:00pm 12:00pm})
=> “3:00am”

Well commented. My preference for the test cases would have been in a test harness (easier to detect failures upon changes). But very nice :)

Reply

Mark October 10, 2009 at 12:54 am

What would be the correct answer for this:

average_time_of_day(["6:00pm","6:00am"])
It is possible to get “12:00pm” or “12:00am” for the average time

Same with:
average_time_of_day(["6:00pm","12:00pm","6:00am","12:00am"])
Now there are 4 possible answers that all work depending on how you solve the problem.

Just wondering what is considered “correct” on this edge case.

Reply

Tom Voltz October 10, 2009 at 3:55 am

Solution (#20):

1. Your name:
Tom Voltz
4. Country of Residence:
United States
5. GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases:
https://gist.github.com/6da8bd7345d7ed7a6040
(includes code and testing of test cases as a separate function)

6. Code works with Ruby 1.8 / 1.9 / Both:
Should work in both. Tested in 1.8

Reply

Jeff Savin October 19, 2009 at 3:36 pm

Had to change: average_time.strftime(‘%l:%M%P’)
to: average_time.strftime(‘%I:%M%p’) and then output looked correct. Not sure if this was a cut and paste error into github, or exactly what happened, but before I made this change, the output was always :00.

After the change, this program passed every one of my tests.

Could have used unit testing, but did write a pretty nice test case framework.

Reply

Tom Voltz October 22, 2009 at 8:34 am

Wow. I’m really impressed with the individual feedback on these. Thanks Jeff. I was mystified why you had to change my average_time.strftime, so I did a little digging.

I saw someone else had a similar problem with the strftime function not providing the expected result.

It turns out that at least on ubuntu, there’s a %P which returns the lowercase ‘am’ and ‘pm’. (I used the cheat sheet gem, cheat strfmtime), which gave me this:
%p – Meridian indicator (“AM” or “PM”)
%P – Meridian indicator (“am” or “pm”)

That works for me in Ruby 1.8.7 on ubuntu.

But if you check the actual *ruby* documentation, %P is not supported. Subtle ‘gotcha’ probably would have caught if I had tested on Windows, or perhaps 1.9. (Here’s the ruby docs http://tinyurl.com/strfmtime )

I used the cheat gem (cheat strfmtime), which lead me astray, though it works on ubuntu!

I can see how I could have used Test:Unit from some of the other solutions. (I’ve used cucumber, and Rspec, but for a single file example, Test::Unit seems to fit the bill.)

This was a really great little exercise, especially seeing the range of responses. Some quite complex solutions in the mix, and some neat tricks as well. Great to look at how others solved a problem, *after* you’ve worked through it yourself. Very good learning experience.

Thanks!

Reply

Chris Strom October 21, 2009 at 7:17 am

Nice solution! It passes all of my unit tests (why not use Test::Unit?). I like the approach — checking two diffs to decide which is closest to the original value is a great idea (better than my solution). The only suggestion I would have (aside from Test::Unit) would be to convert the sum_of_time_differences / each to an inject.

Reply

Tom Voltz October 22, 2009 at 8:46 am

good feedback and thanks to you as well (see above comments.)

Looks like lots of newbies have not yet gotten into the ‘inject’ and ‘map’ idioms, including me.

I did update my gist a few times, to replace some of the ternary stuff like
minimum_time_diff = (diff1.abs < diff2.abs)? diff1:diff2

with more English version like
minimum_time_diff = if (diff1.abs < diff2.abs) then (diff1) else (diff2) end

Not sure which is better, initial version is more concise, but it's hard to 'skim' a ternary expression, the second one is much clearer to read…but once you are used to reading ternary expressions, perhaps the first is better…

My initial gist was 13 lines of code, concise and reasonably clean. ( http://tinyurl.com/yfkszdp ) My refactored version, I renamed several variables for clarity, replaced ternary with if/else, and ended up at 19 lines of code. I'm not sure if the refactoring really improved things…

It would be interesting to see how others might refactor their solutions.

Thanks for all the time you guys put in to helping us learn ruby!! Good stuff.

Cheers

Reply

Tom Voltz October 22, 2009 at 8:54 am

Chris,
Thanks for taking the time to provide individual feedback! It was really a good learning experience to tackle this, and to see the range of approaches.

Clearly I need to get up to speed on Test::Unit. Much simpler than the route I took in order to keep everything in a single file.

Curious what others though about refactoring the code. My first gist was short (about 13 lines of ruby), but used a few ternary statements, which I find hard read, but very handy. (Sort of like reading a double negative to me .. I have to slow down and think more to follow the logic). I also renamed variables to make things more obvious.

Would be interesting to see how others might refactor their code. My refactored code was longer (about 20 lines), but easier to read through.

Thanks for the challenge, and the comments on my code and others!

Tom

Reply

David Jenkins October 10, 2009 at 5:52 am

Solution (#21):

Name: David Jenkins
Country of Residence: USA
GIST URL: https://gist.github.com/44593f6efc6c463880ab
Code works with both Ruby 1.8 and 1.9

Reply

Jeff Savin October 19, 2009 at 3:36 pm

Very concise code, but no comments. Passed all my tests except two as noted below:

a = ["12:01am", "11:59pm"] #=> “12:00pm” should be am
a = ["11:58pm", "12:00pm"] #=> “5:59am” should be pm

Reply

David Jenkins October 19, 2009 at 6:37 pm

FWIW, as I noted in the comments at the top, the program assumes that the first time in the list is the earliest actual time of all times in the list.

Reply

Chris Strom October 21, 2009 at 7:16 am

Tests / inline code comments would had aided in readability / comprehension. Overall, a good solution given the chosen constraint of ordered times. It would have been nice to get things like this working:

average_time_of_day(%w{06:00pm 12:00pm})
=> “3:00am”

The only code suggestion I would have is that initializing a local variable outside of an each block, then summing / concatenating it is more properly accomplished via an inject.

Overall, nice and clean :)

Reply

Michael Lang October 11, 2009 at 12:08 am

Solution (#22):

1. Michael Lang
4. USA
5. https://gist.github.com/ac4e6b87b03b04e5a037
6. Tested only with 1.8

Reply

Jeff Savin October 19, 2009 at 3:38 pm

Wow, very interesting way to achieve results. I honestly wouldn’t have ever thought of attempting to solve the problem this way, and I don’t think anyone else has either.

Program gave good answers on all my tests except one:
a = ["11:58pm", "12:00pm"] In my mind, this test should result in “5:59pm” as the answer.

Reply

Michael Lang October 19, 2009 at 6:37 pm

Yeah, I saw when I was testing that when the two hours where at 180 degree angles, the result could go to either AM or PM and it probably came down to a rounding error which side of the clock the average would fall on.

I’m curious, though. What was so interesting about my solution? I’ve been reading through the others and no two solutions are the same and some are downright creative in their own right, too. Its amazing just how flexible of a language Ruby really is.

Reply

Jeff Savin October 20, 2009 at 8:40 am

Yes, Ruby is flexible. What I liked about your approach was its originality. Yes, there were a lot of unique and original approaches, but yours struck me as being really clever to use a circle and create an imaginary 1440 minute clockface. I mean, there are math guys, and there are MATH guys. You managed to use those old trigonometric functions I haven’t personally used in forever, sine, cosine and I think even arctangent. :) Very inventive.

Reply

Chris Strom October 21, 2009 at 7:15 am

This is how I actually solved the problem. I *think* the problem with some of the values is that degrees increase counter-clockwise whereas as clock increases, well clockwise. Also the atan2 function is not a straight conversion back to degrees.

That aside, I love the approach (obviously) and like the comments in the code.

Reply

Thiago October 11, 2009 at 2:39 am

Solution (#23):

1. Your name: Thiago Fernandes Massa
4. Country of Residence: Brazil
5. GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases: https://gist.github.com/325132c4e1888c495edc
6. Code works with Ruby 1.8 / 1.9 / Both: Both

Reply

Jeff Savin October 19, 2009 at 3:39 pm

If I had to give this program a name, I would call it loop de loop, lol. There are quite a few loops involved, and not very easy to follow. The program passed some of my tests, but failed on others. When midnight was the average time, the program output 0:00am, not really a valid time and I also got 17:59pm. The 17:59 could be correct for a military time, but then I don’t believe you would need the pm. Not bad, but could be better.

Reply

Chris Strom October 21, 2009 at 7:12 am

This solution would have benefits greatly from use of Ruby iterators. Idiomatic ruby almost never uses the while statement and certainly never uses it to loop over an array (array.each {} or array.inject {} would have accomplished the same goal with much less code. If you really want to experience the beauty and power of Ruby, learn the Enumerable module – you won’t regret it :)

The overall approach was solid. It broke down when times were randomized, though:

average_time_of_day(["11:58pm", "12:00am"])
=> “11:59am”

Solid effort.

Reply

Tim Rand October 11, 2009 at 10:01 am

Solution (#24):

Code compatibility: 1.9 works, 1.8 not checked (most likely compatible).
Solution: https://gist.github.com/7e4dd13fbba60b7d0b39

Reply

Jeff Savin October 19, 2009 at 3:46 pm

I liked his beginning explanation and the excellent comments throughout the code. Passed all tests except two, which were an hour off, for some reason.

a = ["12:02am", "12:00pm"] #=> “5:01am” should be “6:01am”
a = ["11:58pm", "12:00pm"] #=> “6:59pm” should be “5:59pm”

Reply

Dave Lilley October 19, 2009 at 3:47 pm

Well thought out, especially how Tim mentions his concerns about the cross over of midnight and so uses midnight as a “peg in the ground” Will it be a help or a hindrance (from other cases it’s lead to some errors). Handled my malformed time and 24 hr format (been testing but not mentioned here by me all the time). V. Good documentation explaining what is happening at each step.

Reply

Chris Strom October 21, 2009 at 7:19 am

Not sure I follow where the “vector” from midnight was used (mentioned in the comments). I may just be confusing terminology, but I expect vectors to be x-y coordinates on a Cartesian plane (or an Array like thing). That aside, a good overall approach. I do think it fixated too much on midnight. Other simple averages did not work:

average_time_of_day(%w{12:00pm 06:00pm})
=> “9:00am”

Good overall use of idiomatic ruby.

Reply

Milan Dobrota October 11, 2009 at 7:37 pm

Solution (#25):

Your name: Milan Dobrota
Country of Residence: Serbia
GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases: clone url: git@gist.github.com:ba70a62a95cbf461c0e8.git gist-ba70a62a or https://gist.github.com/ba70a62a95cbf461c0e8
Code works with Ruby 1.8 / 1.9 / Both: Tested with ruby 1.8.6

Reply

Chris Strom October 19, 2009 at 3:49 pm

The break_circularity method is overlong / hard to follow. It’s great to have the specs describing what it should do, but hard to follow what it actually does.

More judicious use of inject would have helped throughout the code — there are 4 each blocks used in the solution and each builds an array defined outside the each block.

Not sure I follow the “multiple curves”, but it definitely comes into play when averaging the same time:

>> average_time_of_day ["11:10pm", "11:10pm", "11:10pm", "11:10pm", "11:10pm"]
=> ["11:10pm", "11:10pm", "11:10pm", "11:10pm", "11:10pm"]

Aside from those nit-picks, a very interesting approach to the problem. I love having specs to help me understand. Overall summary is very helpful as well.

Reply

Mike Hodgson October 11, 2009 at 11:23 pm

Solution (#26):

My method handles most of the issues discussed above, I think:

https://gist.github.com/5194d2a4659cac5369bd

Reply

Chris Strom October 19, 2009 at 3:49 pm

The assumption of same day was a little confusing — the flights were on different days. Anything around the midnight boundary (such as Dave’s example) won’t work. For example, average_time_of_day(["9:00pm", "12:00am"]) => “10:30am”

That aside, short & sweet. Nice use of inject and strftime. The bang version of downcase! was misused — the regular downcase would have returned the same thing in a more idiomatic Ruby way. Bang versions of methods are reserved for modifying things in-place. For example:

str = “Foo”
str.downcase!

# Now str has changed, str == “foo”

Reply

Brad O'Connor October 12, 2009 at 3:19 am

Hi,

I have a couple of questions about this challenge that I’m a bit unsure about. More Ruby related than specific to this challenge.

Firstly, I’ve noticed that if I want to use the Time class there are some methods available to me without using require ‘time’ (eg Time.now, Time.gm) but other methods (eg Time.parse which I am using in my solution) do need me to have require ‘time’ in my script. This is easy enough for me to deal with but I’m just not quite sure of the reason why, and how I can tell from the rDoc documentation which methods are included by default.

Secondly, I haven’t written a program that I run from the command line like is suggested here before. I presume you mean from within irb. Is it simply a matter of having a program (say flight_time.rb) with the average_time_of_day method then typing require ‘flight_time.rb’ on the command line before calling the method?

I hope this makes sense. Thanks.

Reply

chris October 12, 2009 at 4:28 pm

I believe that the reason some Time methods are immediately available is that they are part of core Ruby written in C. The remainder of the methods (like parse) are pure Ruby and need to be required like any other ruby library. As far as I know, there are no hints to which is which in the documentation, so it’s best to always plan on requiring Time.

The sample output is generated in IRB. A command line executable is not needed, only a method similar to the average_time_of_day method shown in the example.

Reply

Jedediah Smith October 12, 2009 at 12:28 pm

The correct answer to this challenge is “ambiguous as stated”, period.

Reply

Brad O'Connor October 12, 2009 at 3:43 pm

Solution (#27):

OK, here’s my entry.

1. Brad O’Connor
4. Australia
5. https://gist.github.com/7214b8695201d8d53ea0
My solution to this works by taking all of the times that have been entered and determining the shortest interval of time that encompasses all of the entered times, assuming that this is most likely to be the correct answer.
6. Works with Ruby 1.8 and 1.9

This turned out to be very challenging for me, but again I learned a great deal by working the problem through to completion. I’m actually quite proud of the way I solved the ‘midnight problem’. Thanks for reviewing my answer – I hope I have uploaded it correctly.

Reply

Jeff Savin October 19, 2009 at 3:51 pm

Program passed every one of my tests. Nice testing for argument error with regex. There could have been more comments.

Reply

Chris Strom October 21, 2009 at 7:13 am

Very nice! Nice that you took it to the next level and got it working for inputs in any order — passes all of my unit tests!

Checking the distance between between times and opting for the shortest was definitely the way to go.

I would suggest that, in the future, you use “inject” rather than initializing a local variable outside of an each block, then collating a value in that variable inside the each block. It makes for cleaner, easier to read code.

I probably would not have bothered checking the input with the RexExp. It should be the caller’s responsibility to pass in useful data. Still, nice job of failing early :)

Reply

Giordano Scalzo October 12, 2009 at 9:03 pm

Solution (#28):

Your name: Giordano Scalzo
Country of Residence: Italy
My code is on
http://github.com/gscalzo/AverageArrivalTimeForAFlight
Code working with 1.8.6 and 1.9.1 as stated by RunCodeRun
http://runcoderun.com/gscalzo/AverageArrivalTimeForAFlight

Test cases: the code has been written in TDD using Rspec, so in repository you will find the specs to.

I managed the day leap assuming that, if the difference between times in “pm” and “am” is less than 12 hour they are in the same day, otherwise the “am” times are in the next day.

I tried to be synthetic, but still readable: I hope the code isn’t too cryptic ;-)

As usual, I enjoyed a lot ;-)

Regards
-Giordano

Reply

Chris Strom October 19, 2009 at 3:52 pm

Nice! It passes all of my tests.

I would suggest encapsulating all of the adjusted methods inside a first class object — possibly even the Array class. They have similar method signatures and are coupled together, so encapsulating in a class would make sense.

I very much appreciate the tests, but they don’t test the private adjust methods — either tests or comments around those methods would aid in understanding / long term maintainability. Specifically, what does “adjusted” adjust and under what conditions does to_adjust? adjust. They are both complicated enough that they take some noodling through, so comments would aid in readability / comprehension.

But all in all, good show!

Reply

Rainer October 13, 2009 at 5:51 am

Solution (#29):

Your name: Rainer Thiel
Country of Residence: New Zealand

https://gist.github.com/d577209112f1f7220dbd

Regards
Rainer

Reply

Dave Lilley October 19, 2009 at 3:54 pm

From my reading of what this programmer has done, none of our test will work as he is looking for a scheduled time and arrival time pairing, and not taking a set of arrival times and then getting the average time.

Not a lot of comments to explain what’s going on for anyone else to follow.

Reply

Rainer October 21, 2009 at 1:47 pm

I am sorry, but I must object to your cursory response to my submission.

Let me first note that:
i) i did not enter to win this challenge, but at least to be properly assessed on the merits of my entry.
ii) I acknowledge that wrt commenting I probably fell short of what is required for a non-ruby conversant audience. I assume you to be a Ruby expert, not some nitpicking out of your depth business analyst.
iii) I also acknowledge that i didn’t bother with niceties like exactly following the input time format (preferring the more succinct 18h00 to 06:00pm) and simply used a perfectly acceptable format /([0-1][0-9]|2[0-3]):[0-5][0-9]/. Oops, look no further, this doesn’t pass MY TEST!

If you take a closer look at the requirement, it poses the question as to whether the solution will still work if the scheduled arrival time changes.
How can any solution then be valid if it does not take the scheduled arrival time into consideration?
Your tests don’t work because they do not match the problem posed.

I do calculate the average of time differences from the schedule arrival time. My input is NOT a sequence of scheduled time and arrival time pairings, it is a scheduled arrival time followed by a collection of actual arrival times.

I look forward to entering future challenges, but I sure as hell hope that you don’t adjudicate any of my entries again.

Reply

Jeff Savin October 21, 2009 at 2:34 pm

I was unable to run any of my tests against this program.

Reply

Rainer October 22, 2009 at 12:02 am

Ok, in the cold light of morning, i see that i vented my spleen a little too energetically in response to Dave.
@Dave – i think i caused offense and i apologize for that.
But pls bear with me a little further to help me understand things better:
Without specifying a scheduled arrival time (this is my first input argument, documented implicitly in the code that checks input parameters) how can the solution work for anything other than a hard-coded scheduled arrival time?
What if the arrival is at 06h00 the following day?

Reply

Rainer October 22, 2009 at 1:11 am

Ok, i now see i tripped myself up by extending the solution a little beyond exactly what was asked for.
My insistence on the scheduled arrival time as an input argument is because in addition to the average arrival time i also show the average delay time…. and of course the challenge didn’t ask for that. Fair enough.
My real sin is that i didn’t provide my own test cases and i won’t get that wrong again :-)

Reply

Rainer October 22, 2009 at 1:35 am

Come on guys, please give it just one more whirl. Please :-)
I can’t accept yet that my solution is just plain wrong.
Below are command line examples that use the suggested test cases.

[code]
>> newbie2.rb 22:00 23:51 23:56 00:01 00:06 00:11
For scheduled arrival time 22:00, given the following 5 actual arrival times:
=> 23:51 (delay 01 hours and 51 minutes)
=> 23:56 (delay 01 hours and 56 minutes)
=> 00:01 (delay 02 hours and 01 minutes)
=> 00:06 (delay 02 hours and 06 minutes)
=> 00:11 (delay 02 hours and 11 minutes)
The average delay time is 02:01, arriving at 00:01

>> newbie2.rb 06:00 06:41 06:51 07:01
For scheduled arrival time 06:00, given the following 3 actual arrival times:
=> 06:41 (delay 00 hours and 41 minutes)
=> 06:51 (delay 00 hours and 51 minutes)
=> 07:01 (delay 01 hours and 01 minutes)
The average delay time is 00:51, arriving at 06:51
[/code]

The only real difference is that my first input argument is the scheduled arrival time so i can report on the actual delay time. It’s not much code and i used no libraries. There must be at least some little bit of merit to it.

Reply

Chris Strom October 22, 2009 at 5:35 am

a reasonable approach. It would have been easier to review / reuse / modify / test if the solution were in a method.

Idiomatic ruby would have you raise an exception rather than puts a message and exit. The only comment would be that each blocks that build up a value or pair of values is usually easier to maintain in an inject (not an each).

Reply

Rainer October 22, 2009 at 10:18 am

Chris, thanks very much for your comments.

Reply

Todd Huss October 13, 2009 at 9:29 am

Solution (#30):

Your name: Todd Huss
Country of Residence: US
GIST URL of your Solution:

Solution: http://gist.github.com/208964
RSpec tests: http://gist.github.com/208965

Explanation: I implemented under the assumption that the arrival times passed to the method are not sorted in any way (such as ['10:00am', '2:00pm', '10:30am']) so that my algorithm has to infer if it’s a night flight or a day flight based on the proximity of the times. If it’s a night flight then it coerces the AM times to the next day. Obviously this approach falls apart when a flight has arrived at 9am, 3pm, and 3am (the following day). To solve that case would require that the inputs be pre-sorted (so it could infer days from the times) but to my mind that would take away half the fun of the challenge which is making inferences from ambiguous data.

Code works with Ruby 1.8.6, 1.8.7, and 1.9

Reply

Chris Strom October 19, 2009 at 3:55 pm

Nice! It passes all of my unit tests. Probably helps that you included so many tests yourself :) Additional comments inside coerce_split_day_times would aid in comprehension of what / why is going on, but aside from that… nice.

Reply

Peter Cooper October 14, 2009 at 5:01 pm

Solution (#31):

I’m not entering for the prize – just thought I’d bash out something fast for the fun of it.

1. Peter Cooper
3. Editor of Ruby Inside
4. United Kingdom
5. http://gist.github.com/210008
6. Works in Ruby 1.8.7 – not tried elsewhere. Probably won’t work on 1.8.6 due to Enumerable#reduce and Symbol#to_proc

Reply

Jeff Savin October 19, 2009 at 3:56 pm

Doesn’t work in 1.8.6. Thought it was cool to see a solution that didn’t use any libraries and yet remained quite simple. :)

Reply

Satoshi Asakawa October 19, 2009 at 3:57 pm

Enumerable#reduce is nice.

raise unless average_time_of_day(["11:56am", "12:00pm", "12:06pm"]) == “12:00pm”

raised an exception, though. :-P

Reply

Chris Strom October 22, 2009 at 5:36 am

Nice and lean. It didn’t handle noon well, which caused several failures (12pm produces h == 24 on line 10 of the Gist). Also didn’t account for reverse order well:

>> average_time_of_day(%w{6:01am 5:59am})
=> “12:00am”

I dislike the bang version of map — input arguments should not be modified unless the method is a bang method itself.

Love the reduce and the scan + map(:&to_i).

Reply

pankajsisodiya October 15, 2009 at 3:48 am

Solution (#32):

=begin

Hi, This is Pankaj Sisodiya. I Love Programming.

I am very new to Ruby. I can’t write the code for this program in Ruby, in exactly the manner you people need.

I am not aware of classes & syntax of Ruby & so can’t create a function in Ruby which can take n number of parameters.
But i did try to solve your problem.

Let me tell you what i did:

1. set expectedtime (this is fixed)
2. set different arrival times (delayed time)
3. find the difference between each arrival times & expectedtime
4. now sum all the diferences (i don’t how its returning on minutes, from a Riby tutorial I found that when we make difference between two Time in Ruby, it returns us Seconds)
5. take average
6. averagetime = expectedtime + average
7. display averagetime

=end
https://gist.github.com/6b5bed12e18932e5bf9f

Reply

Chris Strom October 22, 2009 at 5:37 am

Welcome to Ruby :)

This was a start. Defining methods with arguments isn’t much more difficult that what you have already got here. You’ll find it much more flexible as well.

Something like…

def average_time_of_day(times)
#…
end

Would be the first step in defining a method. You could them pass in an array of times like this:

average_time_of_day(["11:50am", "12:00pm", "12:10pm"])

The stuff inside the code would then have to do something with the times array — something along the lines of what you did in your short script.

At any rate, a promising start. Welcome!

Reply

loic October 15, 2009 at 6:50 am

Solution (#33):

Name: Loïc Paillotin
Residence: USA, but I’m French.
Gist URL: git clone git@gist.github.com:c43f1cd5c67265798db2.git gist-c43f1cd5 (https://gist.github.com/c43f1cd5c67265798db2/5b11e1bfbb87c96d89fba6d9cebf5cceb58f3516)
I couldn’t download it to check there is no copy paste mistake for some reason, so sorry if it doesn’t work right away.
Compat: Both.

Reply

Chris Strom October 22, 2009 at 5:32 am

You can define the Solution and Array class directly in the loicpaillotin.rb file. You almost never want to iteration over elements in an array with while statements in ruby. One of the iterator methods (each or inject) will product cleaner code without fail.

Overall an interesting approach. You might have been better served coming up with a simple algorithm for excluding widely varying times, but keeping them about for comparison was interested to see :)

Reply

loic October 22, 2009 at 5:39 am

Well, I wanted to have an algorithm able to say that in this case: ["06pm"],["06am"] there is 2 strictly equivalent solutions, hence my script.

I actually discovered inject while reading some solutions ;)

This is actually my first ruby script ever so I agree that it’s not very idiomatic. Thanks a lot for the feedback and the contest!

Reply

Chuck Ha October 15, 2009 at 7:27 am

Solution (#34):

1. Chuck Ha
4. USA
5. https://gist.github.com/2ca8054d76cfddd58795
The code relies heavily on parsing a DateTime into a Time since the epoch. But basically it takes a basetime then looks through each input time to see if it is the next day or the same day, converts each time to time-since-epoch, adds all the times together then divides and converts to get the answer
6. Code works with Ruby 1.9.

Reply

Chris Strom October 22, 2009 at 5:37 am

Nice! It didn’t work for some clock boundaries (6pm & midnight produced “9:00am”), but aside from that, pretty clean and robust.

A couple of other minor notes: I had to change DateTime#minute to DateTime#min to get it to work in Ruby 1.8, but that was easy enough. Accumulating values inside an each block (total_time in your example) is more properly done in Ruby with an inject block.

Reply

whiskeygrimpeur October 16, 2009 at 6:31 am

Solution (#35):

Josh Baxley
USA
https://gist.github.com/66fcf363e17fe518087b

Reply

Jeff Savin October 19, 2009 at 3:59 pm

Program passed all of my tests. Judicial use of comments which gave insight into author’s thinking. And, interesting to see a whole different approach to attacking this problem.

Reply

Chris Strom October 23, 2009 at 5:52 am

Nice! Good comments and a well thought-out, robust solution.

Some ruby suggestions: don’t use the bang versions of methods unless you have to. A method like this should not alter the input parameter — it is an unintended side-effect. More idiomatic ruby would be to create a new array:

parsed_times = times_array.collect{|t| parsedate(t)}.flatten.compact

Also, if you build up an accumulator (e.g. t=[]) inside an each block, consider using the inject iterator instead.

Minor quibbles. Overall a great solution!

Reply

Javier Blanco Gutiérrez October 16, 2009 at 10:02 pm

Solution (#36):

Coding some ruby after work! :-)
Country of Residence: SPAIN
GIST URL of your Solution (i.e. Ruby code) with explanation and / or test cases: http://gist.github.com/211888
Code works with Ruby 1.8 / 1.9 / Both: BOTH

Reply

Jeff Savin October 19, 2009 at 4:00 pm

One of the most concise entries I’ve seen and passed all tests except:

a = ["12:02am", "12:00pm"] #=> “6:01pm” and should be “6:01am”

Not a lot of comments, but almost no code to comment, lol. Well done.

Reply

Javier Blanco Gutiérrez October 21, 2009 at 3:02 pm

;-(
What a pity!! You’re right. I’ll try again in the next Challenge.
Thank you for the feedback!

Reply

Javier Blanco Gutiérrez October 21, 2009 at 6:41 pm

FYI: Fixed!

Reply

Chris Strom October 23, 2009 at 5:53 am

Nice. The only constraint in the solution was that it relied on an expected time of arrival (10pm). Obviously, that meant it didn’t work well with times around that boundary. For example:

average_time_of_day(%w{06:00pm 12:00am})
=> “9:00am”

The only other suggestion I would offer is that accumulators outside of each blocks (e.g. sum) are more properly done in an inject than in an each.

All in all, a good solution with adequate comments and tests even!

Reply

Sogo Ohta October 17, 2009 at 12:26 am

Sogo Ohta,Japan

Solution (#37):

https://gist.github.com/b7165b186f6b02464463

Reply

Jeff Savin October 20, 2009 at 10:20 am

A lot of calculations going on in this program but unfortunately, not all the correct ones. Just about all of my test cases failed including:

a = ["12:00am", "12:00pm"] #=> “12:00am” where I would hope for either “6:00am” or “6:00pm”
a = ["11:58pm", "12:00pm"] #=> “23:59pm” where I was expecting “5:59pm”.

Reply

Chris Strom October 23, 2009 at 5:58 am

Hmm…

average_time_of_day(["11:51am", "11:56am", "12:00pm", "12:06pm", "12:11pm"])
=> “4:48am”

This solution gets the midnight boundary condition right at the expense of the noon one. Good overall approach, but some test cases would have helped build up assurances that things like this weren’t being missed.

I like the use of the all? blocks. Accumulators (times) are more properly calculated in inject blocks than in each block, but that’s something you’ll pick up as you learn more Ruby.

Good show :)

Reply

zenchild October 17, 2009 at 1:39 am

Solution (#38):

1. Your name: Daniel Wanek
4. Country of Residence: USA
5. GIST URL: https://gist.github.com/fe2a737102412aad8319
6. Code works with Ruby: “Both”

Reply

Jeff Savin October 19, 2009 at 4:01 pm

Program passed all of my tests. Nice comments and nicely coded.

Reply

Chris Strom October 23, 2009 at 5:53 am

Program passed all of my tests. Nice comments and nicely coded.

Chris: Nice! Well commented and even includes tests (though not Test::Unit or similar framework). It handles nearly all cases, though does not handle randomized data:

average_time_of_day(%w{12:00am 9:00pm})
=> “10:30am”

Pretty code. Good show!

Reply

zenchild October 24, 2009 at 1:24 am

Shoot… thought I had all my bases covered. Anyway, just for the fun of it I wrote a fix that should take care of that condition. I also added a separate RSPEC file for automated testing.
https://gist.github.com/fe2a737102412aad8319

Thanks for your work Chris!

Reply

Boris October 17, 2009 at 2:28 am

I think the solution is in finding the earliest time in the list and then calculating the average of intervals from the earliest time. Then just add the average to the earliest time found and you got your answer.

Simple (I think).

Reply

p.campbell October 19, 2009 at 8:44 pm

I don’t think its quite that simple. If the times are ’11:00pm’ and ’01:00am’, what is the “earliest time”? How about ’11:00pm’, ’01:00am’, ’11:00am’?

It could be either time depending upon your assumptions. You have to either assume that the input times are ordered, or you’ll have to order them yourself (either min->max or ordered so that the total time window is minimized).

Reply

Himansu Desai October 17, 2009 at 5:06 am

Solution (#39):

I live in the US.
The gist url for my solution is:
https://gist.github.com/242e70d93552198b2844

The solution works with all ruby because it only uses Hash and Set and String

Reply

Jeff Savin October 19, 2009 at 4:02 pm

Wow. The time spent on this entry must have been phenomenal. Not only did he document his algorithms in extreme detail, he offered the most code I’ve seen to solve the problem. Unfortunately, I was unable to test the code to see if it passed.

Reply

Chris Strom October 23, 2009 at 5:59 am

That is crazy complex. When code is growing very, very large, it is sometimes worthwhile to take a step back and ask, “is there a simpler way to solve this?”. This was one of those times, except… it looks like you had some fun with it, so who am I to complain? :)

I do think some test cases might have helped as you explored the algorithm. For instance, when I ask for the average time between noon (12pm) and 6pm, it tells me 3am instead of 3pm. Test cases can help to keep you on the the right track.

Use of some of the Enumerable methods would aid in readability as well (there is more that just each / each_value / each_pair). Inject is great for accumulators (the Hash h in remove_duplicates). The any? method is nice to cut down on algorithm time in places like calculating identical in analyze_segments.

Overall, that was a wild approach!

Reply

John McDonald October 17, 2009 at 8:05 am

Solution (#40):

1. John McDonald
4. USA
5. GIST URL https://gist.github.com/174dfa400be3ba50768d
6. Code works with Ruby 1.8 / 1.9 / Both: yes

Reply

Jeff Savin October 20, 2009 at 9:00 am

Nicely commented with explanation at the beginning of program giving John’s attack. Program worked flawlessly but missed one of my test cases.

a = ["12:02am", "12:00pm"] #=> “6:01pm” where “6:01am” was expected.

Overall, nicely done.

Reply

Chris Strom October 23, 2009 at 5:54 am

Probably was not wise to make the solution depend on the time the script was run (am_or_pm is derived from Time.now). When I ran the code in the morning, it failed the supplied test case:

average_time_of_day(["11:51pm", "11:56pm", "12:01am", "12:06am", "12:11am"])
=> “9:37am”

Aside from that, a decent overall approach. Interesting used of the block version of gsub. I would offer one suggestion as you explore Ruby more — accumulating values inside an each statement (avg_time) is more properly done with an inject.

Reply

Ben Miller October 17, 2009 at 8:22 pm

Solution (#41):

Hello, I have an entry to the challenge. I live in the UK. These challenges (I recently did challenge #1 too, after the closing date, just for fun) have been the most useful thing for me in helping to learn the language and idioms.

So here’s my submission: https://gist.github.com/32751c9971386f6c7d8f

Reply

Jeff Savin October 20, 2009 at 8:58 am

Glad these challenges are not only challenging, but worthwhile in learning the language. I agree. And, another very succinct entry with a few lines of code that passed each of my tests.

Reply

Chris Strom October 21, 2009 at 2:36 pm

Nice! It passes all my tests.

I would suggest that some tests and / or comments would have aided in readability / comprehension. Especially the ternary inside the collect! with the trailing unless conditional.

I would also suggest that you not use the bang version of collect on the input to a method. The convention in Ruby is that any method that alters input values should end with a bang (like collect!). Since average_time_of_day does not end with a bang, it should not, by convention, alter the input parameters. In this case, there is no need to alter them. Not a huge deal in this case because it is a one method exercise, but definitely something to keep in mind in the future.

Reply

Ben October 21, 2009 at 4:31 pm

Ah, yes you are dead right. It was very naughty of me to alter the method inputs! :)

The comments were something I umm-ed and ahh-ed about: I actually removed some before submission because they seemed to be just stating what the Ruby code was doing That’s certainly one of the strengths of Ruby it seems to me – it reads nicely. I totally take your point about commenting my use of the ternary operator though. And a comment which described my strategy generally. In fact, I realise I agree totally with your criticisms. :|

I actually had some unit test but didn’t submit those – I realised we were only submitting a single file but I didn’t think about sticking the test code in the same file!

Cheers
Ben

Reply

Sriram Varahan October 18, 2009 at 5:38 pm

Solution (#42):

Name : Sriram Varahan
Country: India
Gist Url : https://gist.github.com/00df2f1e36fbb0aa647e
Code tested with: ruby 1.8

Reply

Jeff Savin October 20, 2009 at 9:58 am

Yet another way to solve the problem requiring rubygems and parsedate. Sriram did a nice job passing every one of my tests with flying colors. Code was fairly well documented. Nothing more to really say.

Reply

Chris Strom October 23, 2009 at 5:55 am

Good overall approach and implementation. I very much appreciated the copious documentation in comments. I would have loved some Test::Unit test cases, but that’s just me :)

The only quibble with the solution would be that it does not account for randomly ordered data. For example:

Average.new.average_time_of_day(["12:00am", "11:58pm"])
=> “11:59am”

The only ruby coding suggestion I would offer is that accumulators inside each blocks (e.g. times in build_time_from_input) are more properly done via an inject block.

Reply

Leave a Comment

{ 23 trackbacks }

Previous post:

Next post: