Ruby sort by boolean and number - ruby

I'm using Ruby 1.8.7. I have the following array of hashes. I need to sort by the boolean value first, but those results must be ordered as well in the original order. I basically need to shift all the true hashes to the top of the array but maintain the original ordering.
Any help would be appreciated!
array = [{:id => 1, :accepts => false},
{:id => 2, :accepts => false},
{:id => 3, :accepts => true},
{:id => 4, :accepts => false},
{:id => 5, :accepts => true}]
sorted = array.sort do |x, y|
if x[:accepts] == y[:accepts]
0
elsif x[:accepts] == true
-1
elsif x[:accepts] == false
1
end
end
This sort that I have yields:
5 - true
3 - true
2 - false
4 - false
1 - false
I need it to yield:
3 - true
5 - true
1 - false
2 - false
4 - false

Use sort_by for these things, not sort!
array.sort_by {|h| [h[:accepts] ? 0 : 1,h[:id]]}

This does the job:
array.sort{|a,b| (a[:accepts] == b[:accepts]) ? ((a[:id] < b[:id]) ? -1 : 1) : (a[:accepts] ? -1 : 1)}

array = [{:id => 1, :accepts => false},
{:id => 2, :accepts => false},
{:id => 3, :accepts => true},
{:id => 4, :accepts => false},
{:id => 5, :accepts => true}]
sorted = array.sort do |x, y|
if x[:accepts] ^ y[:accepts]
x[:accepts] ? -1 : 1
else
x[:id] <=> y[:id]
end
end
puts sorted
Or != instead of ^, if you wish.

Well, from your question I deduce you really wanted to group the results by the :accepts value and merge both result sets back into one array. My solution to this would've been:
array.select {|where| where[:accepts] } | array.reject {|where| where[:accepts] }
# => [{:accepts=>true, :id=>3},
# {:accepts=>true, :id=>5},
# {:accepts=>false, :id=>1},
# {:accepts=>false, :id=>2},
# {:accepts=>false, :id=>4}]
This will maintain original order without implying any sorts on the :id key. This means you won't need a helper key to preserve order, and you can preserve order on the result regardless of the transported data.
This may also be useful (and maybe exactly what you need for further evaluations):
array.group_by {|where| where[:accepts] }
# => {false=>[{:accepts=>false, :id=>1},
# {:accepts=>false, :id=>2},
# {:accepts=>false, :id=>4}],
# true=>[{:accepts=>true, :id=>3},
# {:accepts=>true, :id=>5}]}
Again, no artificial sorts involved... group_by is new in 1.8.7.
PS: If you don't want the first code snippet remove duplicates from your array, replace the bar operator with the plus operator. "|" merges two sets according to the theory of sets (union) while "+" concatenates two sets (the result is not really a set but a plain array).

You could add an extra check on the :id key if :accepts is equal, as follows:
array = [{:id => 1, :accepts => false},
{:id => 2, :accepts => false},
{:id => 3, :accepts => true},
{:id => 4, :accepts => false},
{:id => 5, :accepts => true}]
sorted = array.sort do |x, y|
if x[:accepts] == y[:accepts]
if x[:id] == y[:id]
0
elsif x[:id] > y[:id]
1
elsif x[:id] < y[:id]
-1
end
  elsif x[:accepts] == true
    -1
  elsif x[:accepts] == false
    1
  end
end

This is because sorting in Ruby 1.8.7 is not stable
http://redmine.ruby-lang.org/issues/show/1089
http://en.wikipedia.org/wiki/Stable_sort#Stability
All you need to do is have your sort block not return 0:
sorted = array.sort do |x, y|
if x[:accepts] == y[:accepts]
x[:id] <=> y[:id] # not 0
elsif x[:accepts]
-1
else
1
end
end
(no need to explicitly compare a boolean to true and false)

a.sort_by { |x| (x[:accepts] ? 0 : 99999) + x[:id] }
Update: Well, obviously this requires x[:id].respond_to? "+" and additionally there are restrictions on its range relative to the constants.
This is, however, the shortest and probably the fastest answer, if also obviously the most questionable.
The really important lesson is that it illustrates that one should look beyond Array (or whatever) and check Enumerable if that's in (your object).class.ancestors. These questions and their viewers are often after an answer to "what should I learn about Ruby next, I suspect there are other ways".
Regardless of whether this is a good way to sort (admittedly it's questionable) this answer suggests #sort_by and just finding the docs for #sort_by (it's not in Array) will teach a small but important lesson to a beginner.

Related

Compare 2 Hash with values consisting of array

I have 2 hashes, let's say A, B
A: { 'key1' => [a, b], 'key2' => 'c' }
B: { 'key1' => [b, a], 'key2' => 'c' }
What is the best possible way to compare these 2 hashes. The ordering of the array contents does not matter. So in my case, hash A and B are equal
It's not as easy as it seems at first glance.
It is necessary to take into account several nuances:
the number of elements in the hashes may not match;
items with the same key in two hashes can be of different types.
A relatively universal solution can be as follows:
def hashes_comp(hash1, hash2)
return false if hash1.size != hash2.size
hash1.each do |key, value|
if value.class == Array
return false if hash2[key].class != Array || value.sort != hash2[key].sort
else
return false if value != hash2[key]
end
end
true
end
hash_a = {'key1' => ['a', 'b'], 'key2' => 'c'}
hash_b = {'key1' => ['b', 'a'], 'key2' => 'c'}
hash_c = {'key1' => ['a', 'c'], 'key2' => 'c'}
hash_d = {'key1' => ['a', 'b'], 'key2' => 'd'}
hash_e = {'key1' => ['a', 'b'], 'key2' => ['a', 'b']}
hash_f = {'key1' => ['a', 'b'], 'key2' => 'c', 'key3' => 'd'}
hashes_comp(hash_a, hash_b) #=> true
hashes_comp(hash_a, hash_c) #=> false
hashes_comp(hash_a, hash_d) #=> false
hashes_comp(hash_a, hash_e) #=> false
hashes_comp(hash_a, hash_f) #=> false
One can sort the arrays but that can be an expensive operation if the arrays are large. If n equals the size of the array, the time complexity of heapsort, for example, is O(n log(n)). It's faster to replace arrays with counting hashes, the construction of which enjoys a time complexity of O(n).
h1 = { 'k1' => [1, 2, 1, 3, 2, 1], 'k2' => 'c' }
h2 = { 'k1' => [3, 2, 1, 2, 1, 1], 'k2' => 'c' }
def same?(h1, h2)
return false unless h1.size == h2.size
h1.all? do |k,v|
if h2.key?(k)
vo = h2[k]
if v.is_a?(Array)
if vo.is_a?(Array)
convert(v) == convert(vo)
end
else
v == vo
end
end
end
end
def convert(arr)
arr.each_with_object(Hash.new(0)) { |e,g| g[e] += 1 }
end
same?(h1, h2)
#=> true
Here
convert([1, 2, 1, 3, 2, 1])
#=> {1=>3, 2=>2, 3=>1}
convert([3, 2, 1, 2, 1, 1])
#=> {3=>1, 2=>2, 1=>3}
and
{1=>3, 2=>2, 3=>1} == {3=>1, 2=>2, 1=>3}
#=> true
See Hash::new, specifically the case where the method takes an argument that equals the default value.
The guard clause return false unless h1.size == h2.size is to ensure that h2 does not have keys that are not present in h1. Note that the following returns the falsy value nil:
if false
#...
end
#=> nil
In a couple of places I've written that rather than the more verbose but equivalent expresion
if false
#...
else
nil
end
I would definitely agree with Ivan it's not as easy as it initially seems but I figured I would try doing it with recursion. This has the added benefit of being able to compare beyond just hashes.
hash_a = {'key1' => ['a', 'b'], 'key2' => 'c'}
hash_b = {'key1' => ['b', 'a'], 'key2' => 'c'}
hash_c = {'key1' => ['a', 'c'], 'key2' => 'c'}
hash_d = {'key1' => ['a', 'b'], 'key2' => 'd'}
hash_e = {'key1' => ['a', 'b'], 'key2' => ['a', 'b']}
hash_f = {'key1' => ['a', 'b'], 'key2' => 'c', 'key3' => 'd'}
def recursive_compare(one, two)
unless one.class == two.class
return false
end
match = false
# If it's not an Array or Hash...
unless one.class == Hash || one.class == Array
return one == two
end
# If they're both Hashes...
if one.class == Hash
one.each_key do |k|
match = two.key? k
break unless match
end
two.each_key do |k|
match = one.key? k
break unless match
end
if match
one.each do |k, v|
match = recursive_compare(v, two[k])
break unless match
end
end
end
# If they're both Arrays...
if one.class == Array
one.each do |v|
match = two.include? v
break unless match
end
two.each do |v|
match = one.include? v
break unless match
end
end
match
end
puts recursive_compare(hash_a, hash_b) #=> true
puts recursive_compare(hash_a, hash_c) #=> false
puts recursive_compare(hash_a, hash_d) #=> false
puts recursive_compare(hash_a, hash_e) #=> false
puts recursive_compare(hash_a, hash_f) #=> false
I came up with this solution:
def are_equals?(a, b)
(a.keys.sort == b.keys.sort) &&
a.merge(b) { |k, o_val, n_val| [o_val, n_val].all? { |e| e.kind_of? Array} ? o_val.sort == n_val.sort : o_val == n_val }.values.all?
end
How it works.
The first part tests for key equality, using Hash#keys, which returns the array of keys, sorted of course:
a.keys.sort == b.keys.sort
For the second part I used Hash#merge to compare values related to the same key, and can be expanded in this way:
res = a.merge(b) do |k, o_val, n_val|
if [o_val, n_val].all? { |e| e.kind_of? Array}
o_val.sort == n_val.sort
else
o_val == n_val
end
end
#=> {"key1"=>true, "key2"=>true}
It returns a Hash where values are true or false, then checks if all values are true using Enumerable#all?:
res.values.all?
#=> [true, true].all? => true

How do I intersect one hash's keys with another and filter out the matching values?

I got this from the Rails doc:
{1 => 2}.diff(1 => 2) # => {}
{1 => 2}.diff(1 => 3) # => {1 => 2}
{}.diff(1 => 2) # => {1 => 2}
{1 => 2, 3 => 4}.diff(1 => 2) # => {3 => 4}
This is almost perfect but I don't want values that are in the hash passed as parameter and not in the calling hash.
What I want:
{}.diff(1 => 2) # => {}
{a: 1}.diff({a: 1, b: 2}) # => {} instead of {:b => 2}
Also, it must be as efficient as possible. For instance, I don't want to go over the second hash and check that each key that's inside doesn't appear in the first.
Any ideas?
Looking at the source is helpful here.
def diff(h2)
dup.delete_if { |k, v| h2[k] == v }.merge!(h2.dup.delete_if { |k, v| has_key?(k) })
end
Which iterates over every entry in the hash. I'm assuming you don't want to add an unnecessary iteration. So it's easier than the above
def my_diff(h2)
dup.delete_if { |k, v| h2[k] == v }
end
This is pretty easy:
a.select {|k, v| b.key?(k) && b[k] != v }
This is O(n), since both key? and Hash#[] are both O(1).

Compare values from multiple hashes within an array

I know for-loops should be avoided and i guess the better way to iterate through an array is instead of doing
for i in 0..array.size-1 do
puts array[i]
end
do
array.each{ |x|
puts x
}
But what if i had an array of hashes like
array = [{:value0 => 1, :value1 => 0}, {:value0 => 2, :value1 => 1}, {:value0 => 1, :value1 => 2}]
and wanted to check if :value0 is unique in all hashes.. intuitively I would do something like
for i in 0..array.size-1 do
_value_buffer = array[i][:value0]
for j in i+1..array.size-1 do
if _value_buffer == array[j][:value0]
puts "whatever"
end
end
end
Is there a better way to do this?
Why not just get all the values in question and see if they’re unique?
!array.map { |h| h[:value0] }.uniq!
(uniq! returns nil when there are no duplicates)
As what Andrew Marshall says, you can use uniq!, but with a more concise way:
!array.uniq!{|a| a[:value0]}
Here is how I would do it:
2.0.0p195 :001 > array = [{:value0 => 1, :value2 => 0}, {:value0 => 2, :value2 => 1}, {:value0 => 1, :value2 => 2}]
=> [{:value0=>1, :value2=>0}, {:value0=>2, :value2=>1}, {:value0=>1, :value2=>2}]
2.0.0p195 :002 > val0 = array.map { |hash| hash[:value0] }
=> [1, 2, 1]
2.0.0p195 :003 > puts val0.uniq == val0
false
=> nil
I would collect the values of :value0 and then compare them to the array of unique values.

How to work out frequency of certain key's value in an array of hashes?

I have an array of hashes. Each hash has an uses key. Multiple hashes can share the same uses value.
[{uses => 0},{uses => 1},{uses => 2},{uses => 1},{uses => 0},{uses => 1},{uses => 3}]
How can I generate an array of the most frequent uses values, in a descending order?
[1,0,2,3]
Referencing this discussion of frequency of items in a list, we can easily modify this for your task.
> unsorted = [{:uses=>0}, {:uses=>1}, {:uses=>2}, {:uses=>1}, {:uses=>0}, {:uses=>1}, {:uses=>3}].map{|h| h[:uses]}
> sorted = unsorted.uniq.sort_by{|u| unsorted.grep(u).size}.reverse
=> [1, 0, 2, 3]
hs.inject({}) do |histogram, h|
histogram.merge(h[:uses] => (histogram[h[:uses]] || 0) + 1)
end.sort_by { |k, v| -v }.map { |k, v| k }
# => [1, 0, 2, 3]
I always recommend to use Facets, though:
http://rubyworks.github.com/facets/doc/api/core/Enumerable.html
hs.frequency.sort_by { |k, v| -v }.map { |k, v| k }
# => [1, 0, 2, 3]
Here is a one pass solution:
a = [{:uses => 0},{:uses => 1},{:uses => 2},{:uses => 1},{:uses => 0},
{:uses => 1},{:uses => 3}]
# A hash with the frequency count is formed in one iteration of the array
# followed by the reverse sort and extraction
a.inject(Hash.new(0)) { |h, v| h[v[:uses]] += 1;h}.
sort{|x, y| x <=> y}.map{|kv| kv[0]}

Sorting a hash in Ruby by its value first then its key

I am trying to sort a document based on the number of times the word appears then alphabetically by the words so when it is outputted it will look something like this.
Unsorted:
'the', '6'
'we', '7'
'those', '5'
'have', '3'
Sorted:
'we', '7'
'the', '6'
'those', '5'
'have', '3'
Try this:
Assuming:
a = {
'the' => '6',
'we' => '7',
'those' => '5',
'have' => '3',
'hav' => '3',
'haven' => '3'
}
then after doing this:
b = a.sort_by { |x, y| [ -Integer(y), x ] }
b will look like this:
[
["we", "7"],
["the", "6"],
["those", "5"],
["hav", "3"],
["have", "3"],
["haven", "3"]
]
Edited to sort by reverse frequencies.
words = {'the' => 6,'we' => 7,'those' => 5,'have' => 3}
sorted_words = words.sort { |a,b| b.last <=> a.last }
sorted_words.each { |k,v| puts "#{k} #{v}"}
produces:
we 7
the 6
those 5
have 3
You probably want the values to be integers rather than strings for comparison purposes.
EDIT
Oops, overlooked the requirement that it needs to be sorted by the key too. So:
words = {'the' => 6,'we' => 7,'those' => 5,'have' => 3,'zoo' => 3,'foo' => 3}
sorted_words = words.sort do |a,b|
a.last == b.last ? a.first <=> b.first : b.last <=> a.last
end
sorted_words.each { |k,v| puts "#{k} #{v}"}
produces:
we 7
the 6
those 5
foo 3
have 3
zoo 3
When you use the sort method on a hash, you receive two element arrays in your comparison block, with which you can do comparisons in one pass.
hsh = { 'the' => '6', 'we' => '6', 'those' => '5', 'have' => '3'}
ary = hsh.sort do |a,b|
# a and b are two element arrays in the format [key,value]
value_comparison = a.last <=> b.last
if value_comparison.zero?
# compare keys if values are equal
a.first <=> b.first
else
value_comparison
end
end
# => [['have',3],['those',5],['the',6],['we',6]]
Note that the result is an array of arrays because hashes do not have intrinsic order in ruby
Try this:
words = {'the' => 6,'we' => 7,'those' => 5,'have' => 3}
words.sort { |(x_k, x_v), (y_k, y_v)| [y_v, y_k] <=> [x_v, x_k]}
#=> [["we", 7], ["the", 6], ["those", 5], ["have", 3]]
histogram = { 'the' => 6, 'we' => 7, 'those' => 5, 'have' => 3, 'and' => 6 }
Hash[histogram.sort_by {|word, freq| [-freq, word] }]
# {
# 'we' => 7,
# 'and' => 6,
# 'the' => 6,
# 'those' => 5,
# 'have' => 3
# }
Note: this assumes that you use numbers to store the numbers. In your data model, you appear to use strings to store the numbers. I have no idea why you would want to do this, but if you do want to do this, you would obviously have to convert them to numbers before sorting and then back to strings.
Also, this assumes Ruby 1.9. In Ruby 1.8, hashes aren't ordered, so you cannot convert the sorted result back to a hash since that would lose the ordering information, you would have to keep it as an array.
1.9.1
>> words = {'the' => 6,'we' => 7, 'those' => 5, 'have' => 3}
=> {"the"=>6, "we"=>7, "those"=>5, "have"=>3}
>> words.sort_by{ |x| x.last }.reverse
=> [["we", 7], ["the", 6], ["those", 5], ["have", 3]]
word_counts = {
'the' => 6,
'we' => 7,
'those' => 5,
'have' => 3,
'and' => 6
};
word_counts_sorted = word_counts.sort do
|a,b|
# sort on last field descending, then first field ascending if necessary
b.last <=> a.last || a.first <=> b.first
end
puts "Unsorted\n"
word_counts.each do
|word,count|
puts word + " " + count.to_s
end
puts "\n"
puts "Sorted\n"
word_counts_sorted.each do
|word,count|
puts word + " " + count.to_s
end

Resources