An equivalent .split method for hashes - ruby

I need to split a hash based on multiple arguments, which will be keys, and return as an array of hashes. Basically, I need a method that performs the same job as .split() does for strings, but with multiple delimiters - is there such a thing for hashes?
example:
Input
({ :a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6 },:c, :e)
Output
[ {:a=>1, :b=>2}, {:c=>3, :d=>4}, {:e=>5, :f=>6} ]

To slice before elements use Enumerable#slice_before:
{ :a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6 }.slice_before do |e|
%i[c e].include? e.first
end.map(&:to_h)
Generic implementation (without checks):
λ = lambda do |hash, *delimiters|
hash.slice_before do |e|
delimiters.include? e.first
end.map(&:to_h)
end
λ.({ :a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6 }, :c, :e)
#⇒ [ {:a=>1, :b=>2}, {:c=>3, :d=>4}, {:e=>5, :f=>6} ]
To slice a hash by pieces of the same size, use Enumerable#each_slice:
{ :a=>1, :b=>2, :c=>3, :d=>4, :e=>5, :f=>6 }.each_slice(2).map &:to_h

With pure ruby (MRI < 1.9) it is not possible to fullfill the task. A ruby Hash has no order of the keys in this versions. So you need ActiveSupport::OrderedHash which supports an order of keys. In MRI versions >=1.9 it works directly
# in case hs is of type ActiveSupport::OrderedHash.new
# ds = delimiters
result = []
result_hash = {}
next_del = delimiters.shift
hs.keys.each do |key|
if next_del == key
result << result_hash
result_hash = {}
next_del = delimiters.shift
end
result_hash[key] = hs[key]
end
result << result_hash
result

Related

How to print only the keys which have values?

I have a hash with keys and values as follows:
hash = {"lili" => [] , "john" => ["a", "b"], "andrew" => [], "megh" => ["b","e"]}
As we can see some of the keys have values as empty arrays.
Some keys have array values where there are actual values in the array.
I want to loop over the hash and generate a new hash that includes only those keys which have values in their arrays (not the ones which have empty arrays). How can I do that?
The title says:
PRINT only the keys, but reading the post you are trying to generate a hash subset given a hash.
The solution for the TITLE of the POST:
hash.each {|k,v| p k unless v.empty? }
If you want to generate a new hash subset give the original hash:
hash.reject { |k,v| v.nil? || v.empty? }
If you want to PRINT the subset generated give the original hash:
hash.reject { |k,v| v.nil? || v.empty? }.each { |k,v| p k }
You could use reject to filter out those elements in your hash where the value is an empty array or the value is nil and then iterate to print their content;
{:lili=>[], :john=>[:a, :b], :andrew=>[], :megh=>[:b, :e], :other=>nil}
.reject { (_2 || []).empty? }
.each { puts "#{_1} has the value(s) #{_2}" }
that prints
john has the value(s) [:a, :b]
megh has the value(s) [:b, :e]
Unsure of your desired output or exact data structure, but you can simply use reject along with empty? to remove any hash value that contains an empty array:
hash = {"lili" => [] , "john" => ["a", "b"], "andrew" => [], "megh" => ["b","e"]}
hash.reject {|k, v| v.empty?}
#=> {"john"=>["a", "b"], "megh"=>["b", "e"]}
It should however be noted that this approach will not work if any hash values are nil. To address that situation, I would recommend either using compact to remove any hash elements with nil values prior to using reject OR by testing for either nil? or empty? (and in that order):
hash = {"lili" => [] , "john" => ["a", "b"], "andrew" => [], "megh" => ["b","e"], "other" => nil}
hash.reject {|k, v| v.empty?}
#=> Evaluation Error: Ruby NoMethodError: undefined method `empty?' for nil:NilClass
hash.compact.reject {|k, v| v.empty?}
#=> {"john"=>["a", "b"], "megh"=>["b", "e"]}
hash.reject {|k, v| v.empty? or v.nil?}
#=> Evaluation Error: Ruby NoMethodError: undefined method `empty?' for nil:NilClass
hash.reject {|k, v| v.nil? or v.empty?}
#=> {"john"=>["a", "b"], "megh"=>["b", "e"]}
hash.reject {|k, v| v.empty? || v.nil?}
#=> Evaluation Error: Ruby NoMethodError: undefined method `empty?' for nil:NilClass
hash.reject {|k, v| v.nil? || v.empty?}
#=> {"john"=>["a", "b"], "megh"=>["b", "e"]}

Immutable alternative to `delete` in Ruby

Is there a version of Hash#delete as below:
hash = {a: 1}
hash.delete(:a) # => 1
hash # => {}
that returns a hash without :a, without mutating the original hash so that it would have its original value?
Use Hash#reject.
hash.reject { |k,_| k == :a }
#=> {}
hash
#=> {:a=>1}
This of course does not depend on the hash having a single key-value pair.

correct way of using hash sort in ruby

I'm new to ruby and I'm trying to write a dijkstra function but my hash sort seems doesn't work at all
def distance(start_code, end_code, map)
#initialize hash for distance
#distance are initialized to -1
dist_hash=Hash.new()
start_hash=Hash.new()
parent_hash=Hash.new()
close_list=Array.new()
find=-1
map.citylist.each do |e|
dist_hash[e]=[+1.0/0.0]
end
start_hash[start_code]=0
parent_hash[start_code]=start_code
while (start_hash.empty?)==false
#sort the hash
start_hash.sort_by {|k,v| v}
puts 'value'
puts start_hash.values()
#pop the first item in the hash
h=start_hash.shift()
curr_key=h[0]
curr_val=h[1]
curr_city=map.findcity(curr_key)
close_list<<curr_city.code
#for every one in adjacent list
curr_city.get_adj_city().each do |e|
#if it in the close list then igonore
if close_list.include?(e)==false
#if it is not in the start_hash then add to start hash
if start_hash.has_key?(e)==false
dist=map.adj_dist(curr_city.code, e)
dist=dist+curr_val
start_hash[e]=dist
parent_hash[e]=curr_city.code
#if it is in the start_hash check if we have better distance
else
dist=map.adj_dist(curr_city.code, e)
if (dist+curr_val)<start_hash[e]
parent_hash[e]=curr_city.code
start_hash[e]=dist
end
end
#end pf checking single adj city
end
#end of check if include in close
end
#end of check whole list
if curr_city.code==end_code
find=0
break
end
end
#end of check node
#result
if find==0
ptr=end_code
puts ptr
puts "final list"
while ptr!=start_code
ptr=parent_hash[ptr]
puts ptr
end
return 0
else
return -1
end
end
When I'm trying to call d.distance("BUE", "LOS", map)
the output looks like
value
0
value
1680
4651
value
10053
8047
4651
value
11094
15839
15839
8047
4651
10779
....
the values are printed out right after hash.sort_by but not sorted. Am I using the method correctly?
Ruby 1.9 actually has ordered hashes, so if you do want to continue to work on the sorted result as a Hash, you can simply turn the array into Hash again:
h = {:a=>1, :c=>3, :b=>5, :d=>2} # => {:a=>1, :c=>3, :b=>5, :d=>2}
h_sorted = Hash[h.sort_by{|k,v| v}] # => {:a=>1, :d=>2, :c=>3, :b=>5}
the values are printed out right after hash.sort_by but not sorted. Am I using the method correctly?
No. When I'm not sure how something works, I open up IRB and try a few things with it:
hash = {a:1, b:2, c:4, d: 3}
=> {:a=>1, :b=>2, :c=>4, :d=>3}
hash.sort
=> [[:a, 1], [:b, 2], [:c, 4], [:d, 3]]
hash
=> {:a=>1, :b=>2, :c=>4, :d=>3}
hash.sort_by{|k,v| v }
=> [[:a, 1], [:b, 2], [:d, 3], [:c, 4]]
hash
=> {:a=>1, :b=>2, :c=>4, :d=>3}
sort_by does not alter the hash, it returns a result. Try:
hash = hash.sort_by{|k,v| v } # <- don't use this, it's an array and you'll mislead anyone reading this code.
sorted_tuples = hash.sort_by{|k,v| v }
or something like it.
Try this
hash = {
"fred" => 23,
"joan" => 18,
"pete" => 54
}
hash.values.sort # => [18, 23, 54]
hash.sort_by { |name, age| age } # => [["joan", 18], ["fred", 23], ["pete", 54]]
hash.sort_by { |name, age| name } # => [["fred", 23], ["joan", 18], ["pete", 54]]

How to convert the values of a hash from String to Array in Ruby?

I'm looking to perform a conversion of the values in a Ruby hash from String to Integer.
I thought this would be fairly similar to the way you perform a conversion in a Ruby array (using the map method), but I have not been able to find an elegant solution that doesn't involve converting the hash to an array, flattening it, etc.
Is there a clean solution to do this?
Eg. From
x = { "a" => "1", "b" => "2", "c"=> "3" }
To
x = { "a" => 1, "b" => 2, "c" => 3 }
To avoid modifying the original Hash (unlike the existing answers), I'd use
newhash = x.reduce({}) do |h, (k, v)|
h[k] = v.to_i and h
end
If you're using Ruby 1.9, you can also use Enumerable#each_with_object to achieve the same effect a bit more cleanly.
newhash = x.each_with_object({}) do |(k, v), h|
h[k] = v.to_i
end
If you want to, you can also extract the logic into a module and extend the Hash class with it.
module HMap
def hmap
self.each_with_object({}) do |(k, v), h|
h[k] = yield(k, v)
end
end
end
class Hash
include HMap
end
Now you can use
newhash = x.hmap { |k, v| v.to_i } # => {"a"=>1, "b"=>2, "c"=>3}
My preferred solution:
Hash[x.map { |k, v| [k, v.to_i]}] #=> {"a"=>1, "b"=>2, "c"=>3}
A somewhat wasteful one (has to iterate over the values twice):
Hash[x.keys.zip(x.values.map(&:to_i))] #=> {"a"=>1, "b"=>2, "c"=>3}
Try this:
x.each{|k,v| x[k]=v.to_i}
p.keys.map { |key| p[key] = p[key].to_i }

Deleting multiple key and value pairs from hash in Rails

number = {:a => 1, :b => 2, :c => 3, :d => 4}
upon evaluation of certain condition i want to delete key-value pair of a,b,c
number.delete "A"
number.delete "B"
number.delete "C"
Or, less performant but more terse:
number.reject! {|k, v| %w"A B C".include? k }
or, more performant than second Chris' solution but shorter than first:
%w"A B C".each{|v| number.delete(v)}
ActiveSupport that is a part of Rails comes with several built-in methods can help you to achieve your goal.
If you just want to delete some key-value pairs, you can use Hash#except!
number.except!(:a, :b, :c)
If you want to keep the original hash, then use Hash#except
new_hash = number.except!(:a, :b, :c)
new_hash # => {:d=>4}
number # => {:a=>1, :b=>2, :c=>3, :d=>4}
You also can go with Rails-free way:
new_hash = number.dup.tap do |hash|
%i[a b c].each {|key| hash.delete(key)}
end
new_hash # => {:d=>4}
number # => {:a=>1, :b=>2, :c=>3, :d=>4}
P.S.: the last code example is very slow, I'm just providing it as an alternative.

Resources