Forum: Ruby Range to array that acts like time objects?

Posted by Jermaine O. (jermaine_o)
on 2012-09-13 13:31
Hello everybody,

I'm looking for a way to create an array out of a range, but does the
counting as like it's a time object. So for instance:

(800..900).to_a (which represents: 08:00 - 09:00)
will generate an array something like this:

#=> [800,801,802,...... 899, 900]

Whereas I would like the output to be like this:

#=> [800,801,802,...... 859, 900]

Basically it should bump to the next major number (hour) after 59
instead of going all the way up to 99.

How should I go about doing this?

Thanks!
Posted by Matthew Kerwin (mattyk)
on 2012-09-13 13:38
(Received via mailing list)
I'd create a class, maybe `class HoursMinutes`, which has @hour and
@minute properties, defines a #<=> comparison method and a #succ that
increments @minute with overflow into @hour.  Then I'd make the range
`HoursMinutes.new(8,0)..HoursMinutes.new(9,0)`

But that's just me.

On 13 September 2012 21:31, Jermaine O. <lists@ruby-forum.com> wrote:
> Whereas I would like the output to be like this:
> --
> Posted via http://www.ruby-forum.com/.
>



--
  Matthew Kerwin, B.Sc (CompSci) (Hons)
  http://matthew.kerwin.net.au/
  ABN: 59-013-727-651

  "You'll never find a programming language that frees
  you from the burden of clarifying your ideas." - xkcd
Posted by Matthew Kerwin (mattyk)
on 2012-09-13 13:55
(Received via mailing list)
Sorry if my answer was a bit air-headed.  Here's a more tangible 
example:

    class HoursMinutes
      attr_accessor :hours
      attr_reader :minutes
      def initialize h,m
        @hours = h
        self.minutes = m
      end
      def <=> o
        r = (@hours <=> o.hours)
        r = (@minutes <=> o.minutes) if r == 0
        r
      end
      def succ
        HoursMinutes.new(@hours, @minutes+1)
      end
      def minutes= m
        while m > 60
          @hours += 1
          m -= 60
        end
        while m < 0
          @hours -= 1
          m += 60
        end
        @minutes = m
      end
      def to_s
        "%d:%02d" % [@hours, @minutes]
      end
    end

    range = HoursMinutes.new(8,0)..HoursMinutes.new(9,0)
    p range.to_a

The spaceship operator will break if `o` doesn't quack like an
HoursMinutes object, and it's not very useful, but it might be a
decent starting place.

On 13 September 2012 21:37, Matthew Kerwin <matthew@kerwin.net.au> 
wrote:
>> I'm looking for a way to create an array out of a range, but does the
>>
>
>
>
> --
>   Matthew Kerwin, B.Sc (CompSci) (Hons)
>   http://matthew.kerwin.net.au/
>   ABN: 59-013-727-651
>
>   "You'll never find a programming language that frees
>   you from the burden of clarifying your ideas." - xkcd



--
  Matthew Kerwin, B.Sc (CompSci) (Hons)
  http://matthew.kerwin.net.au/
  ABN: 59-013-727-651

  "You'll never find a programming language that frees
  you from the burden of clarifying your ideas." - xkcd
Posted by Jermaine O. (jermaine_o)
on 2012-09-13 14:19
Thanks Matthew,

Pretty interesting, I'll definitely give it a try.
You do have one typo, in the minutes method it should be:
while m >= 60 instead of > 60 :-)

I'm still looking for more ways though, how I could fix this more
elegantly, but this is a good starting point. Thanks again!

Matthew Kerwin wrote in post #1075787:
> Sorry if my answer was a bit air-headed.  Here's a more tangible
> example:
>
>     class HoursMinutes
>       attr_accessor :hours
>       attr_reader :minutes
Posted by Sung Pae (Guest)
on 2012-09-13 14:43
(Received via mailing list)
On Thu, Sep 13, 2012 at 08:31:44PM +0900, Jermaine O. wrote:

> instead of going all the way up to 99.
Do you just want to throw away values where the mod(100) values are
greater than 59?

    (800..900).reject { |n| (n % 100) > 59 }
    # => [800, 801, 802, 803, …, 900]

Or do you want to map base-10 numbers to a kind of untyped base-60
format?

    (800..900).map { |n| (n / 60)*100 + (n % 60) }
    # => [1320, 1321, 1322, 1323, 1324, 1325, 1326, 1327, 1328, 1329,
    1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340,
    1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351,
    1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1400, 1401, 1402,
    1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413,
    1414, 1415, 1416, 1417, 1418, 1419, 1420, 1421, 1422, 1423, 1424,
    1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434, 1435,
    1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444, 1445, 1446,
    1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454, 1455, 1456, 1457,
    1458, 1459, 1500]
Posted by Jermaine O. (jermaine_o)
on 2012-09-13 14:46
Hi Sung,

(800..900).reject { |n| (n % 100) > 59 } is just what I need. Sweet!
Thanks a bunch

Sung Pae wrote in post #1075802:
> On Thu, Sep 13, 2012 at 08:31:44PM +0900, Jermaine O. wrote:
>
>> instead of going all the way up to 99.
> Do you just want to throw away values where the mod(100) values are
> greater than 59?
>
>     (800..900).reject { |n| (n % 100) > 59 }
>     # => [800, 801, 802, 803, …, 900]
>
> Or do you want to map base-10 numbers to a kind of untyped base-60
> format?
>
>     (800..900).map { |n| (n / 60)*100 + (n % 60) }
>     # => [1320, 1321, 1322, 1323, 1324, 1325, 1326, 1327, 1328, 1329,
>     1330, 1331, 1332, 1333, 1334, 1335, 1336, 1337, 1338, 1339, 1340,
>     1341, 1342, 1343, 1344, 1345, 1346, 1347, 1348, 1349, 1350, 1351,
>     1352, 1353, 1354, 1355, 1356, 1357, 1358, 1359, 1400, 1401, 1402,
>     1403, 1404, 1405, 1406, 1407, 1408, 1409, 1410, 1411, 1412, 1413,
>     1414, 1415, 1416, 1417, 1418, 1419, 1420, 1421, 1422, 1423, 1424,
>     1425, 1426, 1427, 1428, 1429, 1430, 1431, 1432, 1433, 1434, 1435,
>     1436, 1437, 1438, 1439, 1440, 1441, 1442, 1443, 1444, 1445, 1446,
>     1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454, 1455, 1456, 1457,
>     1458, 1459, 1500]
Posted by Jan E. (jacques1)
on 2012-09-13 14:47
Hi,

What's the background of this, what do you want to do?

Storing actual sequence values in an array rarely makes sense. It's
usually better to do this abstractly with iterators where you only
specify the start and the end and then get the values in between "on
demand".

The class by Matthew already has this feature as it can be used in
ranges.
Posted by Giampiero Zanchi (giampiz)
on 2012-09-13 14:50
maybe you want to use Time?

arr = []
hour1 = Time.gm(2000,"jan",1,8,0)
hour2 = Time.gm(2000,"jan",1,9,0)
while hour1 <= hour2
  arr << hour1.hour.to_s + ":" + hour1.min.to_s.rjust(2,"0")
  hour1 += 60
end

p arr
Posted by Jermaine O. (jermaine_o)
on 2012-09-13 14:59
@Jan E.
Well I'm basically experimenting with a very simple sort of a
availability time checker.

So let's say user A sets its availability from 08:00 - 12:00
Where I'm storing this in the database as: 800..1200 for that user.

It's also possible that he is busy at a certain time, say: 10:00 - 1200
I also store this 'busy time' as: 1000..1200

Now I can get a list of availability hours like so
user_available = (800..1200).to_a
user_busy = (1000..1200).to_a

user_still_available = user_available - user_busy
#=> [800, 801,802.......859, 900]

Other suggestions, better solutions or ideas to go about this are more 
then welcome!

Jan E. wrote in post #1075806:
> Hi,
>
> What's the background of this, what do you want to do?
>
> Storing actual sequence values in an array rarely makes sense. It's
> usually better to do this abstractly with iterators where you only
> specify the start and the end and then get the values in between "on
> demand".
>
> The class by Matthew already has this feature as it can be used in
> ranges.
Posted by Robert Klemme (robert_k78)
on 2012-09-13 15:08
(Received via mailing list)
On Thu, Sep 13, 2012 at 2:42 PM, Sung Pae <sungpae@gmail.com> wrote:
>>
> format?
>     1447, 1448, 1449, 1450, 1451, 1452, 1453, 1454, 1455, 1456, 1457,
>     1458, 1459, 1500]

We can use Fixnum#divmod here

irb(main):004:0> 61.times.map {|x| a,b=x.divmod 60; 800+a*100+b}
=> [800, 801, 802, 803, 804, 805, 806, 807, 808, 809, 810, 811, 812,
813, 814, 815, 816, 817, 818, 819, 820, 821, 822, 823, 824, 825, 826,
827, 828, 829, 830, 831, 832, 833, 834, 835, 836, 837, 838, 839, 840,
841, 842, 843, 844, 845, 846, 847, 848, 849, 850, 851, 852, 853, 854,
855, 856, 857, 858, 859, 900]

For times I'd probably rather use Time though:

irb(main):012:0> start = Time.local 2012,9,13,8
=> 2012-09-13 08:00:00 +0200
irb(main):013:0> 61.times.map {|m| start + m * 60}
=> [2012-09-13 08:00:00 +0200, 2012-09-13 08:01:00 +0200, 2012-09-13
08:02:00 +0200, 2012-09-13 08:03:00 +0200, 2012-09-13 08:04:00 +0200,
2012-09-13 08:05:00 +0200, 2012-09-13 08:06:00 +0200, 2012-09-13
08:07:00 +0200, 2012-09-13 08:08:00 +0200, 2012-09-13 08:09:00 +0200,
2012-09-13 08:10:00 +0200, 2012-09-13 08:11:00 +0200, 2012-09-13
08:12:00 +0200, 2012-09-13 08:13:00 +0200, 2012-09-13 08:14:00 +0200,
2012-09-13 08:15:00 +0200, 2012-09-13 08:16:00 +0200, 2012-09-13
08:17:00 +0200, 2012-09-13 08:18:00 +0200, 2012-09-13 08:19:00 +0200,
2012-09-13 08:20:00 +0200, 2012-09-13 08:21:00 +0200, 2012-09-13
08:22:00 +0200, 2012-09-13 08:23:00 +0200, 2012-09-13 08:24:00 +0200,
2012-09-13 08:25:00 +0200, 2012-09-13 08:26:00 +0200, 2012-09-13
08:27:00 +0200, 2012-09-13 08:28:00 +0200, 2012-09-13 08:29:00 +0200,
2012-09-13 08:30:00 +0200, 2012-09-13 08:31:00 +0200, 2012-09-13
08:32:00 +0200, 2012-09-13 08:33:00 +0200, 2012-09-13 08:34:00 +0200,
2012-09-13 08:35:00 +0200, 2012-09-13 08:36:00 +0200, 2012-09-13
08:37:00 +0200, 2012-09-13 08:38:00 +0200, 2012-09-13 08:39:00 +0200,
2012-09-13 08:40:00 +0200, 2012-09-13 08:41:00 +0200, 2012-09-13
08:42:00 +0200, 2012-09-13 08:43:00 +0200, 2012-09-13 08:44:00 +0200,
2012-09-13 08:45:00 +0200, 2012-09-13 08:46:00 +0200, 2012-09-13
08:47:00 +0200, 2012-09-13 08:48:00 +0200, 2012-09-13 08:49:00 +0200,
2012-09-13 08:50:00 +0200, 2012-09-13 08:51:00 +0200, 2012-09-13
08:52:00 +0200, 2012-09-13 08:53:00 +0200, 2012-09-13 08:54:00 +0200,
2012-09-13 08:55:00 +0200, 2012-09-13 08:56:00 +0200, 2012-09-13
08:57:00 +0200, 2012-09-13 08:58:00 +0200, 2012-09-13 08:59:00 +0200,
2012-09-13 09:00:00 +0200]

Kind regards

robert
Posted by Sung Pae (Guest)
on 2012-09-13 15:10
(Received via mailing list)
On Thu, Sep 13, 2012 at 09:59:44PM +0900, Jermaine O. wrote:
> Well I'm basically experimenting with a very simple sort of a
> availability time checker.
>
> So let's say user A sets its availability from 08:00 - 12:00
> Where I'm storing this in the database as: 800..1200 for that user.

Databases support native time columns and the various Ruby db connection
libraries will serialize these as actual Time objects.
Posted by Sung Pae (Guest)
on 2012-09-13 15:14
(Received via mailing list)
On Thu, Sep 13, 2012 at 10:07:03PM +0900, Robert Klemme wrote:

> We can use Fixnum#divmod here

Indeed. I didn't know about divmod.

> For times I'd probably rather use Time though:

You are correct of course, but it can be encouraging as a beginner to
receive a direct answer rather than the right answer.
Posted by Robert Klemme (robert_k78)
on 2012-09-13 15:43
(Received via mailing list)
On Thu, Sep 13, 2012 at 1:54 PM, Matthew Kerwin <matthew@kerwin.net.au> 
wrote:
>         r = (@hours <=> o.hours)
>         end
>
>     range = HoursMinutes.new(8,0)..HoursMinutes.new(9,0)
>     p range.to_a
>
> The spaceship operator will break if `o` doesn't quack like an
> HoursMinutes object, and it's not very useful, but it might be a
> decent starting place.

Your #succ method does not overflow.  I would make the class immutable
since that has some advantages.  Here's a different solution which
also includes range checking and basic math.

class HoursMinute
  attr_reader :hour, :minute

  def initialize(hr, mn)
    raise ArgumentError, "Out of range: %2d:%02d" % [hr, mn] unless
      (0..23).include?(hr) && (0..59).include?(mn)
    @hour = hr.to_int
    @minute = mn.to_int
  end

  def to_s; "%2d:%02d" % [hour, minute] end

  def succ; self + 1 end

  def +(min)
    hr, mn = (hour * 60 + minute + min.to_int).divmod 60
    self.class.new(hr % 24, mn)
  end

  def -(min) self + -min end

  def hash; hour * 60 + minute end
  def eql?(o) hour == o.hour && minute == o.minute end

  def <=>(o) (hour <=> o.hour).nonzero? || minute <=> o.minute end
 end


Kind regards

robert
Posted by Matthew Kerwin (mattyk)
on 2012-09-13 23:03
(Received via mailing list)
On 13 September 2012 23:43, Robert Klemme <shortcutter@googlemail.com> 
wrote:
>
> Your #succ method does not overflow.  I would make the class immutable
> since that has some advantages.

Actually my #succ did overflow, because the constructor used the
#minutes= method which, with the exception of a modulus error pointed
out earlier, overflowed into @hours.  I agree, though, that making it
immutable would be easier.  However this is all moot since it turns
out it's a question of overlapping time ranges, which can be solved in
more interesting ways.

Jermaine: have you considered other options, such as decreasing the
granularity (e.g. using 10- or 15-minute blocks instead of 1-minute)?
Or combining the available and unavailable ranges without ever
discretising them?

My intuitive approach would be the latter.  If I wasn't about to get
my kids up and ready for school I'd probably hash out another
almost-correct algorithmic outline right now.

Cheers
Posted by Robert Klemme (robert_k78)
on 2012-09-14 08:57
(Received via mailing list)
On Thu, Sep 13, 2012 at 11:02 PM, Matthew Kerwin <matthew@kerwin.net.au> 
wrote:
> On 13 September 2012 23:43, Robert Klemme <shortcutter@googlemail.com> wrote:
>>
>> Your #succ method does not overflow.  I would make the class immutable
>> since that has some advantages.
>
> Actually my #succ did overflow, because the constructor used the
> #minutes= method which, with the exception of a modulus error pointed
> out earlier, overflowed into @hours.  I agree, though, that making it
> immutable would be easier.

Oh, I am sorry, I overlooked that.

>  However this is all moot since it turns
> out it's a question of overlapping time ranges, which can be solved in
> more interesting ways.

Still these types of exercise train our Ruby skills so it wasn't
completely in vain. :-)

Btw, where did it turn out?  I can't see it on the mailing list.

> My intuitive approach would be the latter.  If I wasn't about to get
> my kids up and ready for school I'd probably hash out another
> almost-correct algorithmic outline right now.

:-)

Kind regards

robert
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.