Attached is my solution (in progress). Before I get onto the analysis
reports, I’m still trying to simplify the data post parsing. But, at
least it’s getting there. 
As you can see from the results, I’m only properly pulling data from
the first day. This is why I enjoy quizzes; I’m sure I tried to fix
this same problem at some point.
#!/usr/bin/env ruby
WHEELBASE_AVG = 100 # inches
INCHES_PER_MILE = 63_360
SECONDS_PER_HOUR = 3600.0
INCHES_PER_SECOND_TO_MPH = 17.6
USAGE = <<ENDUSAGE
Usage:
vehicle_counter [-t time_segment] [-a] data_file
-t,–time the number of minutes per segment (defaults to 60)
-a,–average average time samples across days
(defaults to showing each day indpendently)
ENDUSAGE
ARGS = {
:time_segment => 60,
#:data_file => ‘vehicle_counter.data’
}
UNFLAGGED_ARGS = [ :data_file ]
next_arg = UNFLAGGED_ARGS.first
ARGV.each{ |arg|
case arg
when ‘-t’,’–time’
next_arg = :time_segment
when ‘-a’,’–average’
ARGS[:average] = true
else
if next_arg
if next_arg==:time_segment
arg = arg.to_i
end
ARGS[next_arg] = arg
UNFLAGGED_ARGS.delete( next_arg )
end
next_arg = UNFLAGGED_ARGS.first
end
}
if !ARGS[:data_file] || !ARGS[:time_segment]
puts USAGE
exit
end
class Record
SECONDS_PER_DAY = 3600 * 24
attr_reader :time, :direction, :ms
attr_accessor :speed
def initialize( str, day_offset )
_, @direction, @ms = /([AB])(\d+)/.match( str ).to_a
@ms = @ms.to_i
@time = Time.gm( 2007 ) + ( @ms.to_i / 1000.0 ) + ( day_offset *
SECONDS_PER_DAY )
end
end
Prepare data
raw_data = IO.readlines( ARGS[:data_file] )
day_changes = 0
records = raw_data.inject([]){ |records,line|
record = Record.new( line, ARGS[:average] ? 0 : day_changes )
if (last_record = records.last) && (record.ms < last_record.ms)
day_changes += 1
end
records << record
}
Convert axle pairs to speed
last_record = {}
records.each{ |record|
if last_axle = last_record[ record.direction ]
last_axle.speed = WHEELBASE_AVG / ( record.time -
last_axle.time )
last_axle.speed /= INCHES_PER_SECOND_TO_MPH
last_record[ record.direction ] = nil
else
last_record[ record.direction ] = record
end
}
records.delete_if{ |r| r.speed.nil? }
Figure out which direction gets double hits
possible_directions = records.map{ |r| r.direction }.uniq
double_hit_direction = possible_directions.map{ |dir|
[ records.select{ |r| r.direction == dir }.length , dir ]
}.sort.last.last
Remove extraneous records
require ‘enumerator’
records.each_cons(2){ |r1,r2|
if (r1.direction == double_hit_direction) &&
(r2.direction != double_hit_direction) &&
# 0.02 seconds @ 50mph is ~18 inches
# If the times are this close, it must be a double hit
(r1.time - r2.time).abs < 0.02
r1.speed = nil
end
}
records.delete_if{ |r| r.speed.nil? }
t1 = Time.gm(0)
ms_trigger = 0
slot_count = nil
ms_per_slot = ARGS[ :time_segment ] * 60 * 1000
records << Record.new( ‘B99999999’, 0 )
records.each{ |r|
if r.ms >= ms_trigger
if slot_count
t2 = t1 + ms_per_slot / 1000.0
print "#{t1.strftime(’%H:%M’)}…#{t2.strftime(’%H:%M’)} : "
puts “%4i %s, %4i %s” % possible_directions.map{ |dir|
[slot_count[dir],dir==“A” ? “left” : “right”]
}.flatten
t1 = t2
end
slot_count = Hash[ *possible_directions.map{ |d| [d,0] }.flatten ]
ms_trigger += ms_per_slot
end
slot_count[ r.direction ] += 1
}
Slim2:Code phrogz$ ruby vehicle_counter.rb vehicle_counter.data -t 15
00:00…00:15 : 2 left, 1 right
00:15…00:30 : 1 left, 4 right
00:30…00:45 : 3 left, 2 right
00:45…01:00 : 1 left, 1 right
01:00…01:15 : 0 left, 2 right
01:15…01:30 : 0 left, 1 right
01:30…01:45 : 0 left, 3 right
01:45…02:00 : 1 left, 1 right
02:00…02:15 : 0 left, 1 right
02:15…02:30 : 1 left, 2 right
02:30…02:45 : 0 left, 2 right
02:45…03:00 : 1 left, 1 right
03:00…03:15 : 0 left, 3 right
03:15…03:30 : 0 left, 2 right
03:30…03:45 : 1 left, 1 right
03:45…04:00 : 1 left, 1 right
04:00…04:15 : 1 left, 3 right
04:15…04:30 : 0 left, 1 right
04:30…04:45 : 0 left, 1 right
04:45…05:00 : 1 left, 0 right
05:00…05:15 : 0 left, 5 right
05:15…05:30 : 0 left, 15 right
05:30…05:45 : 1 left, 4 right
05:45…06:00 : 2 left, 8 right
06:00…06:15 : 13 left, 8 right
06:15…06:30 : 18 left, 9 right
06:30…06:45 : 13 left, 9 right
06:45…07:00 : 8 left, 5 right
07:00…07:15 : 18 left, 64 right
07:15…07:30 : 16 left, 62 right
07:30…07:45 : 44 left, 66 right
07:45…08:00 : 44 left, 64 right
08:00…08:15 : 49 left, 59 right
08:15…08:30 : 45 left, 75 right
08:30…08:45 : 46 left, 151 right
08:45…09:00 : 44 left, 169 right
09:00…09:15 : 35 left, 25 right
09:15…09:30 : 32 left, 14 right
09:30…09:45 : 23 left, 16 right
09:45…10:00 : 26 left, 14 right
10:00…10:15 : 29 left, 16 right
10:15…10:30 : 33 left, 15 right
10:30…10:45 : 25 left, 14 right
10:45…11:00 : 23 left, 12 right
11:00…11:15 : 26 left, 32 right
11:15…11:30 : 16 left, 38 right
11:30…11:45 : 35 left, 26 right
11:45…12:00 : 26 left, 26 right
12:00…12:15 : 33 left, 29 right
12:15…12:30 : 25 left, 42 right
12:30…12:45 : 30 left, 27 right
12:45…13:00 : 31 left, 27 right
13:00…13:15 : 27 left, 41 right
13:15…13:30 : 29 left, 32 right
13:30…13:45 : 24 left, 38 right
13:45…14:00 : 31 left, 25 right
14:00…14:15 : 26 left, 41 right
14:15…14:30 : 23 left, 44 right
14:30…14:45 : 28 left, 49 right
14:45…15:00 : 21 left, 46 right
15:00…15:15 : 42 left, 48 right
15:15…15:30 : 55 left, 46 right
15:30…15:45 : 44 left, 55 right
15:45…16:00 : 42 left, 48 right
16:00…16:15 : 41 left, 51 right
16:15…16:30 : 58 left, 41 right
16:30…16:45 : 93 left, 40 right
16:45…17:00 : 102 left, 35 right
17:00…17:15 : 80 left, 41 right
17:15…17:30 : 111 left, 53 right
17:30…17:45 : 114 left, 39 right
17:45…18:00 : 106 left, 39 right
18:00…18:15 : 19 left, 6 right
18:15…18:30 : 21 left, 11 right
18:30…18:45 : 27 left, 7 right
18:45…19:00 : 25 left, 6 right
19:00…19:15 : 9 left, 9 right
19:15…19:30 : 12 left, 7 right
19:30…19:45 : 11 left, 18 right
19:45…20:00 : 3 left, 10 right
20:00…20:15 : 7 left, 11 right
20:15…20:30 : 9 left, 12 right
20:30…20:45 : 9 left, 6 right
20:45…21:00 : 11 left, 8 right
21:00…21:15 : 8 left, 10 right
21:15…21:30 : 7 left, 4 right
21:30…21:45 : 12 left, 11 right
21:45…22:00 : 6 left, 16 right
22:00…22:15 : 10 left, 5 right
22:15…22:30 : 17 left, 5 right
22:30…22:45 : 7 left, 8 right
22:45…23:00 : 16 left, 2 right
23:00…23:15 : 9 left, 2 right
23:15…23:30 : 4 left, 4 right
23:30…23:45 : 4 left, 6 right
23:45…00:00 : 8916 left, 9061 right