Sorting a Hash

O

Osuka

I'm Having some trouble sorting a hash!, the hash contents are like
this:

1 is Ayumi Hamasaki
2 is Zone
1 is Two-Mix
2 is Shazna
1 is L'arc~en~ciel

Problem is: I want to ordered it in a descending order but using
Hash.invert I lose data 'cos there can't be several keys that are the
same, so any ideas of how to do this

this is the code the comments are there 'cos they del some data
---------------CODE------------------------
jdir="E:/emusic/jrock"
Dir.chdir(jdir)
jlist=Array.new
jlist=Dir.entries(jdir)
jlist.sort!
$serie=Array.new
$jcount=Hash.new()
jlist.each do |entry|
$serie=entry.sub(/ - +[a-zA-Z0-9\-.!=&+~\(\) ']*/,'')
if($jcount.has_key?($serie))
$jcount[$serie]=$jcount[$serie]+1
else
$jcount.store($serie,1)
end
end
#$jcount=$jcount.invert
#$jcount=$jcount.sort
#$jcount.reverse!
$jcount.each {|key, value| print value, " is ", key, "\n" }
---------------CODE------------------------

comments on the regexp are also welcomed
"artist - name(~=live'now-now'~[what])-.mp3" to "artist"
 
O

Osuka

Brian Candler said:
I don't understand. Firstly, do you mean that your hash is like this:

myhash = {
'Ayumi Hamasaki' => 1,
'Zone' => 2,
'Two-Mix' => 1,
'Shazna' => 2,
'L\'Arc~en~ciel' => 1,
}
Sorry about that!! but you got the right idea!!
Secondly, how do you want to sort it? If you just do

myhash.sort

then you will get it sorted alphabetically by the key, i.e.

=> [["Ayumi Hamasaki", 1], ["L'Arc~en~ciel", 1], ["Shazna", 2],
["Two-Mix", 1], ["Zone", 2]]

If you want to sort it by the numeric value, then you can pass in an
explicit block which shows how to compare the values:

myhash.sort {|x,y| x[1] <=> y[1]}

=> [["Ayumi Hamasaki", 1], ["L'Arc~en~ciel", 1], ["Two-Mix", 1],
["Shazna", 2], ["Zone", 2]]

(you can see all the 1's come before the 2's)

Arrg I did tried this and the to_a approach but i forgot something way
too simple! if myhash.sort {|x,y| x[1] <=> y[1]} does return the
sorted array but doesn't actually affects the given array so I have to
assign the returned value to the hash I'm using, how come I miss it!!!
all the problem was here!!

Yes I want it sorted by descending values not alphabetical
[["Shazna", 2],["Zone", 2],["Ayumi Hamasaki", 1], ["L'Arc~en~ciel",
1], ["Two-Mix", 1]]
Essentially the thing to remember is: when you sort a hash, it first gets
converted to an array, where each element is a two-element array of
[key,value] pairs.

You can do this conversion yourself explicitly:

myhash.to_a
Problem is: I want to ordered it in a descending order but using
Hash.invert I lose data 'cos there can't be several keys that are the
same, so any ideas of how to do this

By descending order of what?

Hash.invert doesn't reverse the order of elements, it swaps the keys and the
values!! If you want a reverse alphabetical sort, try:

myhash.sort.reverse

=> [["Zone", 2], ["Two-Mix", 1], ["Shazna", 2], ["L'Arc~en~ciel", 1],
["Ayumi Hamasaki", 1]]

yes values become keys and keys values, that's why it was "working"
but eliminating some new keys ie duplicated ones. doing a reverse then
converting to an array meaned that I could easily get it sorted as I
wanted but I would lose elements because of key duplicates, which its
my fault for not considering that a lot of the values are identical.
Regards,

Brian.

well thanks!! now it works and sorry for a not so clear post.
 
B

Brian Candler

If you want to sort it by the numeric value, then you can pass in an
explicit block which shows how to compare the values:

myhash.sort {|x,y| x[1] <=> y[1]}

=> [["Ayumi Hamasaki", 1], ["L'Arc~en~ciel", 1], ["Two-Mix", 1],
["Shazna", 2], ["Zone", 2]]

(you can see all the 1's come before the 2's)

Arrg I did tried this and the to_a approach but i forgot something way
too simple! if myhash.sort {|x,y| x[1] <=> y[1]} does return the
sorted array but doesn't actually affects the given array so I have to
assign the returned value to the hash I'm using, how come I miss it!!!
all the problem was here!!

Of course, the result of the sort is an array, not a hash. There's no point
trying to put the elements back into a hash, because a hash is _unordered_
by definition. Effectively they would end up in a random order again.
Yes I want it sorted by descending values not alphabetical
[["Shazna", 2],["Zone", 2],["Ayumi Hamasaki", 1], ["L'Arc~en~ciel",
1], ["Two-Mix", 1]]

Ah OK. Actually quite a nice solution is:

myhash.sort { |x,y| y[1] <=> x[1] }

or even better,

myhash.sort { |x,y| y.reverse <=> x.reverse }

The 'reverse' swaps x[0]/x[1], so it sorts by the number before sorting by
the name. Using y...<=>...x gives a sort in reverse order. So this will
group together all the entries by number, sorted in reverse numeric order,
and within groups it will sort by reverse alphabetic order.

If you want reverse numeric and forward alphabetic, you can get a bit more
imaginative with the contents of the comparison function:

myhash.sort { |x,y|
cmp = y[1] <=> x[1]
if cmp != 0
cmp
else
x[0] <=> y[0]
end
}

=> [["Shazna", 2], ["Zone", 2], ["Ayumi Hamasaki", 1], ["L'Arc~en~ciel", 1],
["Two-Mix", 1]]

Cheers,

Brian.
 
O

Osuka

Brian Candler said:
If you want to sort it by the numeric value, then you can pass in an
explicit block which shows how to compare the values:

myhash.sort {|x,y| x[1] <=> y[1]}

=> [["Ayumi Hamasaki", 1], ["L'Arc~en~ciel", 1], ["Two-Mix", 1],
["Shazna", 2], ["Zone", 2]]

(you can see all the 1's come before the 2's)

Arrg I did tried this and the to_a approach but i forgot something way
too simple! if myhash.sort {|x,y| x[1] <=> y[1]} does return the
sorted array but doesn't actually affects the given array so I have to
assign the returned value to the hash I'm using, how come I miss it!!!
all the problem was here!!

Of course, the result of the sort is an array, not a hash. There's no point
trying to put the elements back into a hash, because a hash is _unordered_
by definition. Effectively they would end up in a random order again.
Yes I want it sorted by descending values not alphabetical
[["Shazna", 2],["Zone", 2],["Ayumi Hamasaki", 1], ["L'Arc~en~ciel",
1], ["Two-Mix", 1]]

Ah OK. Actually quite a nice solution is:

myhash.sort { |x,y| y[1] <=> x[1] }

or even better,

myhash.sort { |x,y| y.reverse <=> x.reverse }

The 'reverse' swaps x[0]/x[1], so it sorts by the number before sorting by
the name. Using y...<=>...x gives a sort in reverse order. So this will
group together all the entries by number, sorted in reverse numeric order,
and within groups it will sort by reverse alphabetic order.

If you want reverse numeric and forward alphabetic, you can get a bit more
imaginative with the contents of the comparison function:

myhash.sort { |x,y|
cmp = y[1] <=> x[1]
if cmp != 0
cmp
else
x[0] <=> y[0]
end
}

=> [["Shazna", 2], ["Zone", 2], ["Ayumi Hamasaki", 1], ["L'Arc~en~ciel", 1],
["Two-Mix", 1]]

Cheers,

Brian.

Yes I mistyped there!! I tried to mean the array returned, 'cos the
hash its no more after a sort, I should be more careful with those
words. And thanks a lot Brian I didn't thought of doing an
alphabetical(as secondary rule) sort after the numerical, I guess that
was coming even in the example that I gave on what I wanted I did it
like that.Now it all works and as a little exercise I turned it into a
class(a bit of overkill but...gotta learn)
 
B

Brian Candler

Now it all works and as a little exercise I turned it into a
class(a bit of overkill but...gotta learn)

I don't think it's overkill at all. In Ruby it often makes sense to make a
class even for simple cases.

Your code will probably be clearer because the class will have its own <=>
compare method, so you can sort those objects in their preferred order
without having to write that magic compare block each time.

Cheers,

Brian.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top