Ruby Range to array that acts like time objects?

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 via http://www.ruby-forum.com/.

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:

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 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

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]

···

On Thu, Sep 13, 2012 at 08:31:44PM +0900, Jermaine O. wrote:

(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.

Hi,

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

Storing actual sequence value 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 via http://www.ruby-forum.com/.

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

Jermaine O. wrote in post #1075780:

···

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 via http://www.ruby-forum.com/\.

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'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:

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 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

--
  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

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 via http://www.ruby-forum.com/\.

@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 via http://www.ruby-forum.com/\.

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

···

On Thu, Sep 13, 2012 at 2:42 PM, Sung Pae <sungpae@gmail.com> wrote:

On Thu, Sep 13, 2012 at 08:31:44PM +0900, Jermaine O. wrote:

(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.

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]

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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 :slight_smile:

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 via http://www.ruby-forum.com/\.

Databases support native time columns and the various Ruby db connection
libraries will serialize these as actual Time objects.

···

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.

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.

···

On Thu, Sep 13, 2012 at 10:07:03PM +0900, Robert Klemme wrote:

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

···

On Thu, Sep 13, 2012 at 1:54 PM, Matthew Kerwin <matthew@kerwin.net.au> wrote:

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.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

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

···

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.

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. :slight_smile:

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.

:slight_smile:

Kind regards

robert

···

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:

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/