Help sorting an array

Hi,

I have an array consisting of "Event" objects, and string elements which
contain the names of months.
It looks like this:
[#<Event:0x4d4cf78>, #<Event:0x4d4d470>, "MARCH", #<Event:0x4d4dc08>,
#<Event:0x4d4e100>, "FEBRUARY", #<Event:0x4d4e898>,"JANUARY"]

Currently the events precede the months they are ordered to.

Is there any way to reverse this so that the months precede the
events?
Like this:
["MARCH", #<Event:0x4d4cf78>, #<Event:0x4d4d470>,
"FEBRUARY",#<Event:0x4d4dc08>, #<Event:0x4d4e100>, "JANUARY",
#<Event:0x4d4e898>]

I have been scouring the documentation for ages and have also googled
everything I can think of (e.g. Array#split) to try and find a solution,
but with no luck.

Can anyone point me in the right direction.
I am grateful for any help.

···

--
Posted via http://www.ruby-forum.com/.

class Event
end

arr = [ Event.new, Event.new, "MARCH", Event.new, Event.new, "FEBRUARY",
Event.new, "JANUARY" ]
new_arr = []
tmp = []

arr.each do |elem|
  case elem
  when Event
    tmp << elem
  when String
    new_arr << elem
    new_arr.concat(tmp)
    tmp.clear
  end
end

p arr
p new_arr

Looking forward to seeing better solutions :slight_smile:

···

--
Posted via http://www.ruby-forum.com/.

Borrowing from ActiveSupport's Array#split (I modified it to keep the
token on which we split as part of the previous group, and also to
remove the last group if it's empty):

irb(main):093:0> Event = Struct.new :id
=> Event
irb(main):094:0> a = [Event[1], Event[2], "MARCH", Event[3], Event[4],
"FEBRUARY", Event[5], "JANUARY"]
=> [#<struct Event id=1>, #<struct Event id=2>, "MARCH", #<struct
Event id=3>, #<struct Event id=4>, "FEBRUARY", #<struct Event id=5>,
"JANUARY"]
irb(main):095:0> class Array
irb(main):096:1> def split(&block)
irb(main):097:2> res = inject([]) do |results, element|
irb(main):098:3* if block.call(element)
irb(main):099:4> results.last << element
irb(main):100:4> results <<
irb(main):101:4> else
irb(main):102:4* results.last << element
irb(main):103:4> end
irb(main):104:3> results
irb(main):105:3> end
irb(main):106:2> res.pop if res.last.empty?
irb(main):107:2> res
irb(main):108:2> end
irb(main):109:1> end
=> nil
irb(main):111:0> a.split {|o| o.kind_of? String}.each {|m| el = m.pop;
m.unshift el}.flatten
=> ["MARCH", #<struct Event id=1>, #<struct Event id=2>, "FEBRUARY",
#<struct Event id=3>, #<struct Event id=4>, "JANUARY", #<struct Event
id=5>]

Jesus.

···

On Mon, Nov 29, 2010 at 4:47 PM, Jim Burgess <jack.zelig@gmail.com> wrote:

Hi,

I have an array consisting of "Event" objects, and string elements which
contain the names of months.
It looks like this:
[#<Event:0x4d4cf78>, #<Event:0x4d4d470>, "MARCH", #<Event:0x4d4dc08>,
#<Event:0x4d4e100>, "FEBRUARY", #<Event:0x4d4e898>,"JANUARY"]

Currently the events precede the months they are ordered to.

Is there any way to reverse this so that the months precede the
events?
Like this:
["MARCH", #<Event:0x4d4cf78>, #<Event:0x4d4d470>,
"FEBRUARY",#<Event:0x4d4dc08>, #<Event:0x4d4e100>, "JANUARY",
#<Event:0x4d4e898>]

I have been scouring the documentation for ages and have also googled
everything I can think of (e.g. Array#split) to try and find a solution,
but with no luck.

Can anyone point me in the right direction.
I am grateful for any help.

--
Posted via http://www.ruby-forum.com/\.

I would chose different data structures: if your events have a month where they are scheduled I would make the month an attribute of Event. Then you can sort your Event instances according to any criteria you like - including month. Mixing types in a collection can make handling that collection quite complex - as you are experiencing.

Kind regards

  robert

···

On 11/29/2010 04:47 PM, Jim Burgess wrote:

Hi,

I have an array consisting of "Event" objects, and string elements which
contain the names of months.
It looks like this:
[#<Event:0x4d4cf78>, #<Event:0x4d4d470>, "MARCH", #<Event:0x4d4dc08>,
#<Event:0x4d4e100>, "FEBRUARY", #<Event:0x4d4e898>,"JANUARY"]

Currently the events precede the months they are ordered to.

Is there any way to reverse this so that the months precede the
events?
Like this:
["MARCH", #<Event:0x4d4cf78>, #<Event:0x4d4d470>,
"FEBRUARY",#<Event:0x4d4dc08>, #<Event:0x4d4e100>, "JANUARY",
#<Event:0x4d4e898>]

I have been scouring the documentation for ages and have also googled
everything I can think of (e.g. Array#split) to try and find a solution,
but with no luck.

Can anyone point me in the right direction.
I am grateful for any help.

Hi,

First of all, thank you very much for the replies.
In the mean time I have managed to hack together a small function to do
what I want, but it is too embarrassingly bad to post here and I used
one of the other proposed solutions instead.

I agree that a different data structure would be much less cumbersome,
but I wouldn't know which. What I'm doing is parsing a html document (a
calendar) into an array, and using that array to create widgets in a
FXRuby GUI. The month names serve as the title of a label and out of the
Event objects I then create the appropriate text fields, labels and
group boxes for each event. As a month can have multiple events I found
it easier to do it this way, as otherwise I would have to find some way
of ordering events to month names, which at first glance seemed pretty
complicated.

If anyone has any suggestions of a better way to approach this problem,
I would be very glad to hear them.

Thanks for your help so far.

···

--
Posted via http://www.ruby-forum.com/.

Jim Burgess wrote in post #964771:

Is there any way to reverse this so that the months precede the
events?
Like this:
["MARCH", #<Event:0x4d4cf78>, #<Event:0x4d4d470>,
"FEBRUARY",#<Event:0x4d4dc08>, #<Event:0x4d4e100>, "JANUARY",
#<Event:0x4d4e898>]

I have been scouring the documentation for ages and have also googled
everything I can think of (e.g. Array#split) to try and find a solution,
but with no luck.

If your Event has an accessor which shows the month of that object as a
String, then you could do something like this:

arr = arr.sort_by { |e| e.is_a?(Event) ? [1,e.month] : [0,e] }

However, a heterogenous (mixed) list is not a convenient data structure
to work with, as you've probably found. There may be a better way to
group this data, depending on what you plan to do with it. For example,
you could build a hash of
  month => [array of events for that month].
or a nested array of
  [[month, [events]], [month, [events]], ... ]

···

--
Posted via http://www.ruby-forum.com/\.

if you just want the month precede the event(s), then just reverse the array.

best regards -botp

···

On Mon, Nov 29, 2010 at 11:47 PM, Jim Burgess <jack.zelig@gmail.com> wrote:

Currently the events precede the months they are ordered to.
Is there any way to reverse this so that the months precede the events?

Thanks again for all of the helpful answers.
Every time I post a question here I always end up learning a lot.
Anyway, I took the advice offered and rewrote my program to improve my
data structure.
I now have a list of Month objects which have a date attribute that they
can be sorted by. A Month object can contain an arbitrary amount of
Event objects.
This works a lot better with less code (not surprisingly!)

···

--
Posted via http://www.ruby-forum.com/.

If you've ever read "Real Programmers don't use Pascal" (see
http://www.codeproject.com/KB/scrapbook/realprog.aspx ) you'll know that
"Determined Real Programemrs can write Fortran programs in any language"
Just because you're a Ruby programmer you don't have to use all that OO
stuff.

This algorithm picks out the first label and moves it into a new space
on the left, and then moves the second label to where the first was and
so on. Lastly it removes the vacated slot on the right:

def initialise_globals
  $m1 = "JANUARY"
  $m2 = "FEBRUARY"
  $m3 = "MARCH"

  $m1_index = 0
  $m2_index = 0
  $m3_index = 0
end

def get_array
  $input_array = ["#<Event:0x4d4cf78>", "#<Event:0x4d4d470>", "MARCH",
"#<Event:0x4d4dc08>",
"#<Event:0x4d4e100>", "FEBRUARY", "#<Event:0x4d4e898>","JANUARY"]
  puts " #{$input_array}"
end

def shuffle_all_right
  #insert m3 while we’re at it.
  $input_array.unshift " "
end

def locate_months
  $m1_index = $input_array.index $m1
  $m2_index = $input_array.index $m2
  $m3_index = $input_array.index $m3
end

def bring_months_to_left
  $input_array[0] = $input_array[$m3_index]
  $input_array[$m3_index] = $input_array[$m2_index]
  $input_array[$m2_index] = $input_array[$m1_index]
end

def delete_right_slot
  $input_array.pop
end

def main_program
  initialise_globals
  get_array
  shuffle_all_right
  locate_months
  bring_months_to_left
  delete_right_slot
end

main_program

puts $input_array

Actually I cheated there as I enclosed the #strings in quotes. Anyone
know how to deal with #s?

···

--
Posted via http://www.ruby-forum.com/.

Robert

It's a great pleasure to welcome a new member of the Procedural Ruby
Club! Well done - your program was very nice although a bit nearer to
the Pascal era. (You don't eat quiche, do you?)

Mine was a little more vintage - you can almost see the wire wheels. I
did initally have a good old fashioned loop but just couldn't justify
keeping it.

Was there anything object-oriented? I'm not sure. I used objects and
their methods but only in the manner of built-in data-types and their
built-in operations. The program basically separates data and
procedures, and then leaves them as publicly available for anyone to
ride on.

···

--
Posted via http://www.ruby-forum.com/.

Well this is different, but I'm not sure it's better. :slight_smile:
I suspect your version may be more robust (or at least easier to fix) if the
data doesn't end with a String (month).

ary = [ 1, 2, "MARCH", 3, 4, "FEBRUARY", 5, "JANUARY" ]
p ary

ary_new =
ii = 0
ary.each_with_index do |ee, jj|
if ee.kind_of?( String ) then
ary_new << ee
(ii .. (jj - 1)).each { |i| ary_new << ary[ i ] }
ii = jj + 1
end
end
ary.replace( ary_new )

p ary

···

On Mon, Nov 29, 2010 at 4:25 PM, Su Zhang <zhangsu@live.com> wrote:

Looking forward to seeing better solutions :slight_smile:

I would use another data structure too:

k = ["MARCH", 1, 2,"JANUARY",3,4, "FEBRUARY",5,6,7,"DECEMBER",8,9, "OTHER"]
months = {}
tmp =

k.each do |element|
  if element.kind_of? String
    months[element.to_sym] = tmp
    tmp =
  else
    tmp << element
  end
end

months

This way, you will be able to retrieve the objects as:
          months[:JANUARY] #=> [1,2]

By the way, if you need it that way:

k = ["MARCH", 1, 2,"JANUARY",3,4, "FEBRUARY",5,6,7,"DECEMBER",8,9, "OTHER"]
months_array =
tmp =

k.each do |element|
  if element.kind_of? String
    months_array << element
    months_array = months_array + tmp
    tmp =
  else
    tmp << element
  end
end

months_array

Hope I had helped in a way.

Daniel Gaytán

···

2010/11/29 Robert Klemme <shortcutter@googlemail.com>

On 11/29/2010 04:47 PM, Jim Burgess wrote:

Hi,

I have an array consisting of "Event" objects, and string elements which
contain the names of months.
It looks like this:
[#<Event:0x4d4cf78>, #<Event:0x4d4d470>, "MARCH", #<Event:0x4d4dc08>,
#<Event:0x4d4e100>, "FEBRUARY", #<Event:0x4d4e898>,"JANUARY"]

Currently the events precede the months they are ordered to.

Is there any way to reverse this so that the months precede the
events?
Like this:
["MARCH", #<Event:0x4d4cf78>, #<Event:0x4d4d470>,
"FEBRUARY",#<Event:0x4d4dc08>, #<Event:0x4d4e100>, "JANUARY",
#<Event:0x4d4e898>]

I have been scouring the documentation for ages and have also googled
everything I can think of (e.g. Array#split) to try and find a solution,
but with no luck.

Can anyone point me in the right direction.
I am grateful for any help.

I would chose different data structures: if your events have a month where
they are scheduled I would make the month an attribute of Event. Then you
can sort your Event instances according to any criteria you like - including
month. Mixing types in a collection can make handling that collection quite
complex - as you are experiencing.

Kind regards

       robert

I agree with both Robert and Daniel. I would make the month a field of
the Event, so you can query it, order events by month, etc. And then I
would also go with Daniel's suggestion of having a hash indexed by
month so you can get at the set of events for a month easily when you
need it (for example, when the user clicks on Next Month).

Jesus.

···

On Tue, Nov 30, 2010 at 10:24 AM, Jim Burgess <jack.zelig@gmail.com> wrote:

Hi,

First of all, thank you very much for the replies.
In the mean time I have managed to hack together a small function to do
what I want, but it is too embarrassingly bad to post here and I used
one of the other proposed solutions instead.

I agree that a different data structure would be much less cumbersome,
but I wouldn't know which. What I'm doing is parsing a html document (a
calendar) into an array, and using that array to create widgets in a
FXRuby GUI. The month names serve as the title of a label and out of the
Event objects I then create the appropriate text fields, labels and
group boxes for each event. As a month can have multiple events I found
it easier to do it this way, as otherwise I would have to find some way
of ordering events to month names, which at first glance seemed pretty
complicated.

If anyone has any suggestions of a better way to approach this problem,
I would be very glad to hear them.

Thanks for your help so far.

If you've ever read "Real Programmers don't use Pascal" (see
Real Programmers Don't Use PASCAL - CodeProject ) you'll know that
"Determined Real Programemrs can write Fortran programs in any language"
Just because you're a Ruby programmer you don't have to use all that OO
stuff.

How can you use ruby without "all that OO stuff"? That's one of the
main attractions.

def initialise_globals
$m1 = "JANUARY"
$m2 = "FEBRUARY"
$m3 = "MARCH"

$m1_index = 0
$m2_index = 0
$m3_index = 0
end

Globals? Why? Constants are your friends. These values are already
part of the Date class.

require 'date'

Date::MONTHNAMES

[ nil, "January", "February", "March", "April", "May", "June", "July",
"August", "September", "October", "November", "December" ]

def get_array
$input_array = ["#<Event:0x4d4cf78>", "#<Event:0x4d4d470>", "MARCH",
"#<Event:0x4d4dc08>",
"#<Event:0x4d4e100>", "FEBRUARY", "#<Event:0x4d4e898>","JANUARY"]
puts " #{$input_array}"
end

This doesn't do whatever you think it does.

I guess determined programmers can write VB programs in any language.

Regards,
Ammar

···

On Wed, Dec 1, 2010 at 8:30 PM, Mike Stephens <rubfor@recitel.net> wrote:

If you've ever read "Real Programmers don't use Pascal" (see
Real Programmers Don't Use PASCAL - CodeProject ) you'll know that
"Determined Real Programemrs can write Fortran programs in any language"
Just because you're a Ruby programmer you don't have to use all that OO
stuff.

That's true. However, I would add that the choice of language does not determine the algorithm to use nor how it is implemented. We'll come to that in a minute.

This algorithm picks out the first label and moves it into a new space
on the left, and then moves the second label to where the first was and
so on. Lastly it removes the vacated slot on the right:

Here we see the first indication of a bad implementation: the number of labels is encoded in the algorithm. You cannot use that program with other than three labels. Even worse, the exact labels are hard coded into the program. In other words, your program is overly dependent on the input, or - put differently - your program is not very general and poses too many restrictions on the input.

def initialise_globals
   $m1 = "JANUARY"
   $m2 = "FEBRUARY"
   $m3 = "MARCH"

   $m1_index = 0
   $m2_index = 0
   $m3_index = 0
end

Others have commented on the use of global variables already.

def get_array
   $input_array = ["#<Event:0x4d4cf78>", "#<Event:0x4d4d470>", "MARCH",
"#<Event:0x4d4dc08>",
"#<Event:0x4d4e100>", "FEBRUARY", "#<Event:0x4d4e898>","JANUARY"]
   puts " #{$input_array}"
end

def shuffle_all_right
   #insert m3 while we’re at it.
   $input_array.unshift " "
end

Isn't this "OO stuff" that you did not want to use?

def locate_months
   $m1_index = $input_array.index $m1
   $m2_index = $input_array.index $m2
   $m3_index = $input_array.index $m3
end

Here you are clearly cheating: you use "all that OO stuff". Even worse, by using Array#index you are hiding the fact that you traverse the input Array far more often than necessary.

def bring_months_to_left
   $input_array[0] = $input_array[$m3_index]
   $input_array[$m3_index] = $input_array[$m2_index]
   $input_array[$m2_index] = $input_array[$m1_index]
end

def delete_right_slot
   $input_array.pop
end

Again, "OO stuff".

def main_program
   initialise_globals
   get_array
   shuffle_all_right
   locate_months
   bring_months_to_left
   delete_right_slot
end

main_program

puts $input_array

Actually I cheated there as I enclosed the #strings in quotes. Anyone
know how to deal with #s?

If you want to make the point that using Ruby does not necessitate the usage of "all this OO stuff" a better program (i.e. one that at least avoids issues raised so far) would certainly have given your argument more strength.

I believe the program I wrote whilst not typical of Ruby programs is
quicker to understand than the more dense solutions given by others -
and that's important in programming.

Well, then compare that to the version below. I claim that it uses (mostly) procedural style and yet is easier to read and understand than your version.

# test whether the given element is a month label
def is_label(o)
   String === o
end

# exchange values at positions
def swap(arr, i, j)
   # not using parrallel assignment since
   # we want to avoid all the fancy stuff
   x = arr[i]
   arr[i] = arr[j]
   arr[j] = x
end

# Input: array with Events followed by month
# Output: number of events without month at end
# Side effect: months precede their Events
def realign_months(arr)
   start = 0
   i = 0

   while i < arr.length
     if is_label(arr[i])
       # swap label and first
       swap(arr, i, start)

       # remember begin of next section
       start = i + 1
     end

     i += 1
   end

   # remaining elements without month
   return arr.length - start
end

# we add a field only for easier reading of output
Event = Struct.new :no

arr = [
   Event.new(1),
   Event.new(2),
   Event.new(3),
   "January",
   Event.new(4),
   "February",
   Event.new(5),
   Event.new(6),
   Event.new(7),
   "March",
   Event.new(8),
   Event.new(9),
   "April",
]

p arr

remainder = realign_months(arr)

p arr

if remainder > 0
   $stderr.puts "Last #{remainder} events are misaligned"
end

I would readily admit that it does not exactly do the same thing as your program. You can find this version and a version that maintains original element ordering (such as your version does) here: Change order of elements so labels precede their values · GitHub

Kind regards

  robert

···

On 01.12.2010 19:30, Mike Stephens wrote:

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

No fair. Any VB code looks almost, but not entirely, completely unlike
proper code.

···

On Wed, Dec 1, 2010 at 9:46 PM, Ammar Ali <ammarabuali@gmail.com> wrote:

This doesn't do whatever you think it does.

I guess determined programmers can write VB programs in any language.

--
Phillip Gawlowski

Though the folk I have met,
(Ah, how soon!) they forget
When I've moved on to some other place,
There may be one or two,
When I've played and passed through,
Who'll remember my song or my face.

Ammar Ali wrote in post #965529:

>
This doesn't do whatever you think it does.

You don't think, as a determined real programmer, I would publish code
that I hadn't tested, now do you?

···

--
Posted via http://www.ruby-forum.com/\.

Ammar Ali wrote in post #965529:
> This doesn't do whatever you think it does.

You don't think, as a determined real programmer, I would publish code
that I hadn't tested, now do you?

Go ahead, test it. Sure, the puts works, but what are the actual array
elements going to be? Try this after running get_array:

$input_array.map(&:class)

If you didn't intend those to be actual events, what is the point of defining
them that way?

Oh, and while your original point is true:

Just because you're a Ruby programmer you don't have to use all that OO
stuff.

I'd argue you're not a Ruby programmer, and you've missed the whole point of
Ruby, if you're using that many globals.

···

On Wednesday, December 01, 2010 03:04:42 pm Mike Stephens wrote:

David Masover wrote in post #965565:.

I'd argue you're not a Ruby programmer, and you've missed the whole
point of
Ruby, if you're using that many globals.

As I relayed previously on this channel, I was introduced to Ruby by
Ulrika (about five years ago). On an old laptop I found one of her
programs, and it was written in a simple style with lots of globals. It
just seemed so dead easy to understand.

I believe the program I wrote whilst not typical of Ruby programs is
quicker to understand than the more dense solutions given by others -
and that's important in programming.

For all the people on here deciding now to ditch heavy OO and switch to
the more modern easy-going procedural style of Ruby, I would point out
that using globals is called 'common coupling' and frowned upon versus
'normal coupling' where you pass variable as method arguments.

···

--
Posted via http://www.ruby-forum.com/\.

You're free to abuse the language however you like. I raised a
somewhat dubious eye to your practices, but YMYR. (Your machine your
rules)

~Johnneylee

···

On Wed, Dec 1, 2010 at 11:41 PM, Mike Stephens <rubfor@recitel.net> wrote:

David Masover wrote in post #965565:.

I'd argue you're not a Ruby programmer, and you've missed the whole
point of
Ruby, if you're using that many globals.

As I relayed previously on this channel, I was introduced to Ruby by
Ulrika (about five years ago). On an old laptop I found one of her
programs, and it was written in a simple style with lots of globals. It
just seemed so dead easy to understand.

I believe the program I wrote whilst not typical of Ruby programs is
quicker to understand than the more dense solutions given by others -
and that's important in programming.

For all the people on here deciding now to ditch heavy OO and switch to
the more modern easy-going procedural style of Ruby, I would point out
that using globals is called 'common coupling' and frowned upon versus
'normal coupling' where you pass variable as method arguments.