Sort hash elements in-place within an array - ruby

I have an array. Some elements are hashes. I want to sort the hashes in-place and leave the array elements in order.
Can I improve this code?
def sort(args)
args.map! do |arg|
arg.is_a?(Hash) ? arg.sort : arg
end
end
For example:
sort [{"b"=>"2", "a"=>"1"}, "x"]
=> [{"a"=>"1", "b"=>"2"}, "x"]

AFAIU your question is how to sort a hash inplace, since you don’t need any modifications to original array but those. To sort a Hash inplace you might use:
hash.keys.sort.each { |k| hash[k] = hash.delete k }
Putting it all together:
def sort args
args.each { |el|
el.keys.sort.each {|k|
el[k] = el.delete k
} if Hash === el
}
end
res = sort [{"b"=>"2", "a"=>"1"}, "x"]
puts res
# ⇒ [{"a"=>"1", "b"=>"2"}, "x"]
Hope it helps.

Related

Understanding the Ruby sort_by method in a simple histogram

While chugging along over at Codecademy, I ran into a bit of confusion with the sort_by method.
For example, what is happening with the { |a, b| b } in my code? I imagine |a, b|, or whatever you want it to be designated as, maps the |key, values| of an array, but what does it mean then to sort my new Hash frequencies by { |key, value| value }?
puts "Let's make a histogram of word counts, please supply text: "
text = gets.chomp
words = text.split(" ")
frequencies = Hash.new(0)
words.each { |word| frequencies[word] += 1 }
frequencies = frequencies.sort_by { |a, b| b }
frequencies.reverse!
frequencies.each { |word, frequency| puts word + " " + frequency.to_s }
Hash#sort_by{|key, value| value} (actually defined as Enumerable#sort_by) means:
Take key and value to be the key and the value of each key-value pair (This much indicated by |key, value|), and
Sort by value (This much indicated by value).
some_hash = {
"a" => 2,
"b" => 3,
"c" => 1
}
some_hash.sort_by { |key, value| value }
Which returns
[["c", 1], ["a", 2], ["b", 3]]
The sort_by method will sort based on a conditional you pass it in the block. So I could've done something like:
some_hash.sort_by { |key, value| key.start_with?("a") && value > 0 }
and this would return any key/value pair that meets those conditions.
Also, you can use sort_by with only one argument:
sort_by { |arg| arg }
Passing two arguments in the pipes allows you to separate the key and values in the hash and sort each of the pairs by the condition you set.
When you sort a hash, you always provide two variables, in this case, a and b, which, like you said, represent the key and the value. When you only sort by the second variable, it means you want to sort your hash based solely on the value.
In your example, when you sort by value and then reverse it, you are sorting your frequencies in order of most frequent to least frequent word.

How to group anagrams together of a array in Ruby

I am trying to write a function called anagram() and return the following output:
Example = ['Creams', 'bart','bAtr', 'bar', 'rabt', 'Cars','creamsery', 'Scar', 'scream']
puts anagram(Example)
OUTPUT:
[["Creams", "scream"] ["bart", "bAtr", "rabt"], ["bar"], ["Cars","Scar"],["creamsery"]]
I know I can use:
Example[i].downcase.char.sort.join
to compare each element in the array, but I have a hard time grouping them all together inside a loop.
What about this:
def anagrams(ary)
h = Hash.new([])
ary.each.with_index { |el, i| h[el.downcase.chars.sort.join] += [i] }
h.map { |key, indexes| indexes.map { |i| ary[i] } }
end
The function saves the indexes in a hash and then returns the corresponding elements.
This approach scans the array at most twice, therefore it's O(n). Even if it is not particularly elegant, it is quite fast.
What about this:
def anagram(ary)
ary.map do |el|
ary.select{ |x| el.downcase.chars.sort.join('') == x.downcase.chars.sort.join('') }
end.uniq
end
Calling .chars on a String will provide you an Array of chars that you can sort and then .join back to a String.
You can try it on the console:
> ary = ['Creams', 'bart','bAtr', 'bar', 'rabt', 'Cars','creamsery', 'Scar', 'scream']
> anagram ary
=> [["Creams", "scream"], ["bart", "bAtr", "rabt"], ["bar"], ["Cars", "Scar"], ["creamsery"]]
Of course, this is not particularly elegant or efficient. I'm eager to learn from others.
Example.group_by{|w| w.downcase.chars.sort}.values
One liner -
array.inject({}){|hash, ele| key = ele.chars.sort.join.downcase; hash[key] = (hash[key] || []) + [ele]; hash }.values
Complexity - O(n * mlogm), where n => number of elements, m => max size of string length.
If value of m is negligible comparing to n then complexity will be O(n)

How do I merge two arrays of hashes based on same hash key value?

So I have two arrays of hashes:
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
How would I concatenate them with the condition that the value of the key c is equivalent in both a and b? Meaning I want to be able to concatenate with the condition of a['c'] == b['c']
This is the result I want to get:
final_array = [{"b"=>123,"c"=>456,"d"=>789}, {"b"=>456,"c"=>555}, {"b"=>222,"c"=>444}]
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
p a.zip(b).map{|h1,h2| h1["c"] == h2["c"] ? h1.merge(h2) : [h1 ,h2]}.flatten
# => [{"b"=>123, "c"=>456, "d"=>789}, {"b"=>456, "c"=>555}, {"b"=>222, "c"=>444}]
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
def merge_hashes_with_equal_values(array_of_hashes, key)
array_of_hashes.sort { |a,b| a[key] <=> b[key] }.
chunk { |h| h[key] }.
each_with_object([]) { |h, result| result << h.last.inject(&:merge) }
end
p merge_hashes_with_equal_values(a + b, 'c')
# => [{"b"=>222, "c"=>444}, {"c"=>456, "d"=>789, "b"=>123}, {"b"=>456, "c"=>555}]
Concatenate the arrays first, and pass it to the method with the hash key to combine on. Sorting that array then places the hashes to merge next to each other in another array, which makes merging a bit easier to program for. Here I chose #chunk to handle detection of continuous runs of hashes with equal keys to merge, and #each_with_object to compile the final array.
Since this method takes one array to work on, the length of the starting arrays does not need to be equal, and the ordering of those arrays does not matter. A downside is that the keys to operate on must contain a sortable value (no nils, for example).
Here is yet another approach to the problem, this one using a hash to build the result:
def merge_hashes_with_equal_values(array_of_hashes, key)
result = Hash.new { |h,k| h[k] = {} }
remainder = []
array_of_hashes.each_with_object(result) do |h, answer|
if h.has_key?(key)
answer[h.fetch(key)].merge!(h)
else
remainder << h
end
end.values + remainder
end
Enumerable#flat_map and Hash#update are the perfect methods for this purpose :
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
p a.zip(b).flat_map{|k,v| next k.update(v) if k["c"] == v["c"];[k,v]}
# >> [{"b"=>123, "c"=>456, "d"=>789}, {"b"=>456, "c"=>555}, {"b"=>222, "c"=>444}]

unable to combine 2 arrays into a hash - ruby

I’m having a few problems creating a hash out of 2 arrays in ruby (1.9.2)
My issue is some of the hash keys are the same and it seems to cause an issue
So my first array (called listkey) contains these 5 items
puts listkey
service_monitor_errorlog
service_monitor_errorlog
wmt_errorlog
wmt_errorlog
syslog
the second ( called listvalue) contains these 5 items
puts listvalue
service_monitor_errorlog.0
service_monitor_errorlog.current
wmt_errorlog.0
wmt_errorlog.current
syslog.txt
what I want is a hash which contains all 5 items e.g.
{
"service_monitor_errorlog"=>"service_monitor_errorlog.0",
"service_monitor_errorlog"=>"service_monitor_errorlog.current",
"wmt_errorlog"=>"wmt_errorlog.0",
"wmt_errorlog"=>"wmt_errorlog.current",
"syslog"=>"syslog.txt"
}
However using the hash zip command
MyHash = Hash[listkey.zip(listvalue)]
I get this hash produced
puts MyHash
{
"service_monitor_errorlog"=>"service_monitor_errorlog.current",
"wmt_errorlog"=>"wmt_errorlog.current",
"syslog"=>"syslog.txt"
}
Can anyone help? I’ve tried all sorts of commands to merge the 2 arrays into a hash but none of them seem to work
Cheers
Mike
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
EDIT
I've just found out hashs have to have unique keys so could anyone help me work out a way to combine the arrays to form a hash with the values as arrays e.g.
{
"service_monitor_errorlog"=>["service_monitor_errorlog.0", "service_monitor_errorlog.current"]
"wmt_errorlog"=> ["wmt_errorlog.0", "wmt_errorlog.current"]
"syslog"=> ["syslog.txt"]
}
In 1.9 I'd probably do this:
listkey.zip(listvalue).each_with_object(Hash.new{|h,k| h[k] = []}) do |(k,v), h|
h[k] << v
end
Example:
a=['a','b','c','a']
b=[1,2,3,4]
a.zip(b).each_with_object(Hash.new{|h,k| h[k]=[]}) { |(k,v), h| h[k] << v }
#=> {"a"=>[1, 4], "b"=>[2], "c"=>[3]}
For your updated question, an (ugly) solution is
the_hash = listkey.zip(listvalue).inject({}) do | a, (k, v) |
a[k] ||= []
a[k] << v
a
end
or (without the inject)
the_hash = {}
listkey.zip(listvalue).each do | k, v |
the_hash[k] ||= []
the_hash[k] << v
end
Answering the answer after the edit. group_by is a bit inconvenient in this case, so let's use facets' map_by, which is a group_by that allows you to decide what you want to accumulate:
require 'facets'
Hash[xs.zip(ys).map_by { |k, v| [k, v] }]
#=> {"syslog"=>["syslog.txt"],
# "service_monitor_errorlog"=>
# ["service_monitor_errorlog.0", "service_monitor_errorlog.current"],
# "wmt_errorlog"=>["wmt_errorlog.0", "wmt_errorlog.current"]}
Please check this code
a=['a','b','c','a']
b=[1,2,3,4]
c=Hash.new
a.each_with_index do |value,key|
#puts key
#puts value
c[value]=b[key]
end
puts c
Output is
{"a"=>4, "b"=>2, "c"=>3}
This means key should be unique

How would you implement this idiom in ruby?

As someone who came from Java background and being a newbie to Ruby,
I was wondering if there is a simple way of doing this with ruby.
new_values = foo(bar)
if new_values
if arr
arr << new_values
else
arr = new_values
end
end
Assuming "arr" is either an array or nil, I would use:
arr ||= []
arr << new_values
If you're doing this in a loop or some such, there might be more idiomatic ways to do it. For example, if you're iterating a list, passing each value to foo(), and constructing an array of results, you could just use:
arr = bars.map {|bar| foo(bar) }
If I'm understanding you correctly, I would probably do:
# Start with an empty array if it hasn't already been set
#arr ||= []
# Add the values to the array as elements
#arr.concat foo(bar)
If you use #arr << values you are adding the entire array of values to the end of the array as a single nested entry.
arr = [*arr.to_a + [*new_values.to_a]]
Start with:
arr ||= []
And then, depending on whether new_values is an array or not
arr += new_values # if array
arr << new_values # if not
arr += [*new_values] # if it could be either
Furthermore, you can get rid of the test on new_values by taking advantage of the fact that NilClass implements a .to_a => [] method and reduce everything to:
arry ||= []
arr += [*new_values.to_a]
But wait, we can use that trick again and make the entire thing into a one-liner:
arr = [*arr.to_a + [*new_values.to_a]]
I don't intend to write an inexcrutable one-liner, but I think this is quite clear. Assuming, as Phrogz, that what you really need is an extend (concat):
arr = (arr || []).concat(foo(bar) || [])
Or:
(arr ||= []).concat(foo(bar) || [])
I would use:
new_values = foo(bar)
arr ||= []
arr << new_values if new_values

Resources