Ruby printing all elements of a hash but last - ruby

Say I have:
hash = {"a" => 1, "b" => 2, "c" => 3}
keys = hash.keys
In order to print all the keys you would do:
keys.each {|x| puts x}
My question is, how do you print all the keys BUT the last one?

I'd do as below using Enumerable#take :
hash = {"a" => 1, "b" => 2, "c" => 3}
hash.take(hash.size-1).each do |k,_|
p k
end
# >> "a"
# >> "b"
Or, as below :
hash = {"a" => 1, "b" => 2, "c" => 3}
hash.keys.take(hash.size-1) # => ["a", "b"]
puts hash.keys.take(hash.size-1)
# >> a
# >> b
update (As asked by OP - Alright now how do I print just the last element explicitly?)
hash = {"a" => 1, "b" => 2, "c" => 3}
hash.keys.last # => "c"
Hash#keys will give you all the keys of that hash as an array. So, now you can call Array#last on that array to get the last element.

hash = {"a" => 1, "b" => 2, "c" => 3}
keys = hash.keys
keys[0..-2]
#=> ["a", "b"]
[0..-2] would index the array from the first to the one before the last
keys[0..-2].each { |x| puts x }
the last element in an array can be retrieved by calling last method on the array
keys.last
#=> "c"

Related

How to invert a hash, maintaining duplicate keys

From an initial hash t:
t = {"1"=>1, "2"=>2, "3"=>2, "6"=>3, "5"=>4, "4"=>1, "8"=>2, "9"=>2, "0"=>1, "7"=>1}
I need to swap the keys and values as follows:
t = {"1"=>1, "2"=>2, "3"=>2, "6"=>3, "5"=>4, "1"=>4, "8"=>2, "9"=>2, "1"=>0, "1"=>7}
While maintaining the structure of the hash (ie, without collapsing duplicate keys).
Then I'll make an array out of this hash.
Is there a way to do this? I tried this:
t.find_all{ |key,value| value == 1 } # pluck all elements with values of 1
#=> [["1", 1], ["4", 1], ["0", 1], ["7", 1]]
But it returns a new array, and the initial hash isn't changed.
The following doesn't work either:
t.invert.find_all{ |key,value| value == 1 }
#=> []
Here's a way to do this:
>> t = {"1" => 1, "2" => 2, "3" => 2, "6" => 3, "5" => 4, "4" => 1, "8" => 2, "9" => 2, "0" => 1, "7" => 1}
Hash#compare_by_identity allows for keys that are duplicates by value but unique by object id:
>> h = Hash.new.compare_by_identity
>> t.each_pair{ |k,v| h[v.to_s] = v.to_i }
The inverse hash of t:
>> h
#=> {"1" => 1, "2" => 2, "2" => 3, "3" => 6, "4" => 5, "1" => 4, "2" => 8, "2" => 9, "1" => 0, "1" => 7}
You can then use find_all to retrieve an array of elements without mutating h:
>> h.find_all{ |k,_| k == "1" }
#=> [["1", 1], ["1", 1], ["1", 1], ["1", 1]]
or keep_if to return the mutated h:
>> h.keep_if{ |k,_| k == "1" }
#=> {"1"=>1, "1"=>1, "1"=>1, "1"=>1}
>> h
#=> {"1"=>1, "1"=>1, "1"=>1, "1"=>1}
Note that this solution assumes you want to maintain the pattern of string keys and integer values in your hash. If you require integer keys, compare_by_identity won't be helpful to you.

How do I change a hash using new hash values?

I have these hashes:
{"a" => 1, "b" => 2, "c" => 3, "k" => 14}
{"b" => 51, "c" => 2, "d" => 8}
I need to write code, so that after manipulation, the result would be:
{"a" => 1, "b" => 51, "c" => 2, "k" => 14}
I tried:
h1.each do |h, j|
h2.each do |hh, jj|
if h == hh
j = jj
end
end
end
but it doesn't work. Also I think this is ugly code, so how would could it be written better/right?
I though I should compare the two hashes, and, if the second key is the same as the first, change the first hash value to the second hash's value.
Just iterate over the entries in h2 and update the corresponding entry in h1 only if it already exists:
h2.each { |k,v| h1[k]=v if h1.include?(k) }
h1 # => {"a"=>1, "b"=>51, "c"=>2, "k"=>14 }
Also, if you want to update the entries as above and also add new entries from h2 you can simply use the Hash#merge! method:
h1.merge!(h2)
h1 # => {"a"=>1, "b"=>51, "c"=>2, "k"=>14, "d"=>8}

Ruby creating Hash custom invert function in Ruby

Ruby class Hash has method "invert" which make "reversal" between keys and values and delete same keys (in our case its: "1=>:a").
h = {a: 1, b: 2, c: 1}
=> {:a=>1, :b=>2, :c=>1}
h.invert
=> {1=>:c, 2=>:b}
How implement custom Hash method "c_invert", which will return very first (not last) pair of duplicated key => value? Exapmle:
> h = {a: 1, b: 2, c: 1}
=> {:a=>1, :b=>2, :c=>1}
> h.c_invert
=> {1=>:a, 2=>:b}
class Hash
def c_invert
Hash[to_a.reverse].invert
end
end
or
class Hash
def c_invert
Hash[to_a.reverse.map(&:reverse)]
end
end
h = {:d =>1,:a=>1, :b=> 2, :c=>1}
Hash[h.map(&:reverse).reverse]
# => {1=>:d, 2=>:b}
h = {a: 1, b: 2, c: 1}
Hash[h.map(&:reverse).reverse]
# => {1=>:a, 2=>:b}

Create an array from a hash with each_with_index

I have an array:
arr = ["a", "b", "c"]
What I want to do is to create a Hash so that it looks like:
{1 => "a", 2 => "b", 3 => c}
I tried to do that:
Hash[arr.each_with_index.map { |item, i| [i => item] }]
but didn't get what I was looking for.
each_with_index returns the original receiver. In order to get something different from the original receiver, map is necessary anyway. So there is no need of an extra step using each or each_with_index. Also, with_index optionally takes the initial index.
Hash[arr.map.with_index(1){|item, i| [i, item]}]
# => {1 => "a", 2 => "b", 3 => c}
Hash[] takes an array of arrays as argument. So you need to use [i, item] instead of [i => item]
arr = ["a", "b", "c"]
Hash[arr.each_with_index.map{|item, i| [i+1, item] }]
#=> {1=>"a", 2=>"b", 3=>"c"}
Just for clarification: [i => item] is the same as writing [{i => item}] so you really produced an array of arrays that in turn contained a single hash each.
I also added a +1 to the index so the hash keys start at 1 as you requested. If you don't care or if you want to start at 0, just leave that off.
arr = ["a", "b", "c"]
p Hash[arr.map.with_index(1){|i,j| [j,i]}]
# >> {1=>"a", 2=>"b", 3=>"c"}

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