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.

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:

On Mon, Nov 29, 2010 at 4:25 PM, Su Zhang [email protected] wrote:

Looking forward to seeing better solutions :slight_smile:

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 11/29/2010 04:47 PM, Jim B. wrote:

Is there any way to reverse this so that the months precede the
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

On Mon, Nov 29, 2010 at 4:47 PM, Jim B. [email protected]
wrote:

Is there any way to reverse this so that the months precede the
Can anyone point me in the right direction.
I am grateful for any help.


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”]
=> [#, #, “MARCH”, #, #, “FEBRUARY”, #,
“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”, #, #, “FEBRUARY”,
#, #, “JANUARY”, #]

Jesus.

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

2010/11/29 Robert K. [email protected]

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.

On Tue, Nov 30, 2010 at 10:24 AM, Jim B. [email protected]
wrote:

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.

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 Mon, Nov 29, 2010 at 11:47 PM, Jim B. [email protected]
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?

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

best regards -botp

Jim B. 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]], … ]

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

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 Programmers 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
$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?

On Wed, Dec 1, 2010 at 9:46 PM, Ammar A. [email protected] wrote:

This doesn’t do whatever you think it does.

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

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


Phillip G.

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 A. 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?

On Wed, Dec 1, 2010 at 8:30 PM, Mike S. [email protected]
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.

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 Wednesday, December 01, 2010 03:04:42 pm Mike S. wrote:

Ammar A. 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 Wed, Dec 1, 2010 at 11:41 PM, Mike S. [email protected]
wrote:

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.

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

David M. 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 variables as method arguments.

On 01.12.2010 19:30, Mike S. 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.

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

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:

Kind regards

robert

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.