Path to an embedded object - ruby

Given a nested array or hash as the receiver and some object as the argument, what is the best way to return the path to an occurrence of the object if the receiver includes the object, or nil otherwise? I define path as an array of array indices or hash keys that leads to the object. The argument object will never be any of the hash keys, and will never appear more than once. For example, I expect:
[
:a,
[:b, :c, {:d => :foo}],
:e,
]
.path_to(:foo) # => [1, 2, :d]
{
:a => [3, "foo"],
:b => 5,
:c => 2,
}
.path_to(3) # => [:a, 0]
When there is no occurrence, return nil:
[:foo, "hello", 3]
.path_to(:bar) => nil
If no one comes up with a reasonable answer, then I will post my own answer shortly.

Here you are my own recursive solution. I am sure that it could be improved but it is a good start and works exactly as requested.
# path.rb
module Patheable
def path_to item_to_find
path = []
find_path(self, item_to_find, path)
result = path.empty? ? nil : path
result.tap { |r| puts r.inspect } # just for testing
end
private
def find_path(current_item, item_to_find, result)
if current_item.is_a?(Array)
current_item.each_with_index do |value, index|
find_path(value, item_to_find, result.push(index))
end
elsif current_item.is_a?(Hash)
current_item.each do |key, value|
find_path(value, item_to_find, result.push(key))
end
else
result.pop unless current_item == item_to_find
end
end
end
class Array
include Patheable
end
class Hash
include Patheable
end
[
:a,
[:b, :c, {:d => :foo}],
:e,
].path_to(:foo) # => [1, 2, :d]
{
:a => [3, "foo"],
:b => 5,
:c => 2,
}.path_to(3) # => [:a, 0]
[:foo, "hello", 3].path_to(:bar) # => nil
#end path.rb
# example of use
$ ruby path.rb
[1, 2, :d]
[:a, 0]
nil

Nothing like a bit of recursion.
require 'minitest/autorun'
class Array
def path_to(obj)
# optimize this
Hash[self.each.with_index.to_a.map {|k,v| [v,k]}].path_to(obj)
end
end
class Hash
def path_to(obj)
inverted = self.invert
if inverted[obj]
[inverted[obj]]
else
self.map {|k, v|
if v.respond_to?(:path_to)
if res = v.path_to(obj)
[k] + res
end
end
}.find {|path|
path and path[-1] != nil
}
end
end
end
describe "path_to" do
it "should work with really simple arrays" do
[:a, :e,].path_to(:a).must_equal [0]
end
it "should work with simple arrays" do
[:a, [:b, :c], :e,].path_to(:c).must_equal [1, 1]
end
it "should work with arrays" do
[:a, [:b, :c, {:d => :foo}], :e,].path_to(:foo).must_equal [1, 2, :d]
end
it "should work with simple hashes" do
{:d => :foo}.path_to(:foo).must_equal [:d]
end
it "should work with hashes" do
({:a => [3, "foo"], :b => 5, :c => 2,}.path_to(3).must_equal [:a, 0])
end
end

This is the answer that I came up with.
class Object
def path_to obj; end
end
class Array
def path_to obj
if i = index(obj) then return [i] end
a = nil
_, i = to_enum.with_index.find{|e, _| a = e.path_to(obj)}
a.unshift(i) if i
end
end
class Hash
def path_to obj
if value?(obj) then return [key(obj)] end
a = nil
kv = find{|_, e| a = e.path_to(obj)}
a.unshift(kv.first) if kv
end
end

Related

How do I generate a proc to return successive entries in a hash, transformed?

I would like to generate a proc that, when called, returns successive values for any Enumerable. In other words:
a_proc = generate_proc([:a, :b, :c])
a_proc.call # => :a
a_proc.call # => :b
etc.
Ideally, I'd want to translate it before returning, too, so it might get used like:
a_proc = generate_proc([:a, :b, :c ]) { |e| "Element: #{e.inspect}" }
a_proc.call # => "Element: :a"
a_proc.call # => "Element: :b"
a_proc.call # => "Element: :c"
This isn't exactly what you are asking for, but I think it may meet your needs:
an_enum = [:a, :b, :c].each
an_enum.next # => :a
an_enum.next # => :b
And:
an_enum = [:a, :b, :c].map { |e| "Element: #{e.inspect}" }.each
# or, if you want to defer calling the block...
an_enum = [:a, :b, :c].lazy.map { |e| "Element: #{e.inspect}" }
an_enum.next # => "Element: :a"
an_enum.next # => "Element: :b"
an_enum.next # => "Element: :c"
For more information, check out Enumerator and Enumerator::Lazy.
UPDATE FROM FUTURE, SMARTER, HANDSOMER ME:
To answer your original question, you can turn any object's method into a bound, callable object with Object#method. We can even do it to Enumerator#next!
an_enum = [:a, :b, :c].each
a_proc = an_enum.method(:next)
a_proc.call # => :a
a_proc.call # => :b
a_proc.call # => :c
def generate_proc x, &postproc
myobj = Fiber.new { x.each { |v| Fiber.yield v }}
postproc ? -> () { postproc.call myobj.resume } :
-> () { myobj.resume }
end
And this is exactly what you're asking for:
def generate_proc(list, &transform)
enum = list.to_enum
if transform
proc { transform[enum.next] }
else
proc { enum.next }
end
end

Distinguish {k: :v} vs. [:k, :v] when iterating through Hash/Array

I need to implement the callback on #each. The receiver of each might be both Array and Hash. So, I have a code like:
receiver.each do |k, v|
case receiver
when Hash then puts "#{k} = #{v}"
when Array then puts "[#{k}, #{v}]"
end
end
The check for receiver is lame, though. Is there a way to receive/interprete a codeblock argument[s] to clearly distinguish the following cases:
{ a: 1, b: 2 }
versus
[[:a, 1], [:b, 2]]
I tried parenthesis, single argument, splatted argument. Everything just gets an Array of size 2. Am I doomed to stick with explicit type check?
The best you can do is to get the type check out of the loop:
def foo(receiver)
format = receiver.is_a?(Array) ? "[%s, %s]" : "%s = %s"
receiver.each do |k_v|
puts format % k_v
end
end
See String#%
If you want to tell, within the each block, whether the key/value pair came from an array or a hash, you have to resort to monkey patching. It can be done, but it's pretty awful:
class Hash
orig_each = instance_method(:each)
define_method(:each) do |&block|
orig_each.bind(self).call do |kv|
def kv.from_hash?
true
end
def kv.from_array?
false
end
block.call(kv)
end
end
end
class Array
orig_each = instance_method(:each)
define_method(:each) do |&block|
orig_each.bind(self).call do |kv|
def kv.from_hash?
false
end
def kv.from_array?
true
end
block.call(kv)
end
end
end
in use:
e = {a: 1, b: 2}
e.each do |kv|
p [kv.from_hash?, kv.from_array?, kv]
end
# => [true, false, [:a, 1]]
# => [true, false, [:b, 2]]
e = [[:a, 1], [:b, 2]]
e.each do |kv|
p [kv.from_hash?, kv.from_array?, kv]
end
# => [false, true, [:a, 1]]
# => [false, true, [:b, 2]]

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 do I extract a sub-hash from a hash?

I have a hash:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
What is the best way to extract a sub-hash like this?
h1.extract_subhash(:b, :d, :e, :f) # => {:b => :B, :d => :D}
h1 #=> {:a => :A, :c => :C}
ActiveSupport, at least since 2.3.8, provides four convenient methods: #slice, #except and their destructive counterparts: #slice! and #except!. They were mentioned in other answers, but to sum them in one place:
x = {a: 1, b: 2, c: 3, d: 4}
# => {:a=>1, :b=>2, :c=>3, :d=>4}
x.slice(:a, :b)
# => {:a=>1, :b=>2}
x
# => {:a=>1, :b=>2, :c=>3, :d=>4}
x.except(:a, :b)
# => {:c=>3, :d=>4}
x
# => {:a=>1, :b=>2, :c=>3, :d=>4}
Note the return values of the bang methods. They will not only tailor existing hash but also return removed (not kept) entries. The Hash#except! suits best the example given in the question:
x = {a: 1, b: 2, c: 3, d: 4}
# => {:a=>1, :b=>2, :c=>3, :d=>4}
x.except!(:c, :d)
# => {:a=>1, :b=>2}
x
# => {:a=>1, :b=>2}
ActiveSupport does not require whole Rails, is pretty lightweight. In fact, a lot of non-rails gems depend on it, so most probably you already have it in Gemfile.lock. No need to extend Hash class on your own.
If you specifically want the method to return the extracted elements but h1 to remain the same:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2 = h1.select {|key, value| [:b, :d, :e, :f].include?(key) } # => {:b=>:B, :d=>:D}
h1 = Hash[h1.to_a - h2.to_a] # => {:a=>:A, :c=>:C}
And if you want to patch that into the Hash class:
class Hash
def extract_subhash(*extract)
h2 = self.select{|key, value| extract.include?(key) }
self.delete_if {|key, value| extract.include?(key) }
h2
end
end
If you just want to remove the specified elements from the hash, that is much easier using delete_if.
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h1.delete_if {|key, value| [:b, :d, :e, :f].include?(key) } # => {:a=>:A, :c=>:C}
h1 # => {:a=>:A, :c=>:C}
Ruby 2.5 added Hash#slice:
h = { a: 100, b: 200, c: 300 }
h.slice(:a) #=> {:a=>100}
h.slice(:b, :c, :d) #=> {:b=>200, :c=>300}
If you use rails, Hash#slice is the way to go.
{:a => :A, :b => :B, :c => :C, :d => :D}.slice(:a, :c)
# => {:a => :A, :c => :C}
If you don't use rails, Hash#values_at will return the values in the same order as you asked them so you can do this:
def slice(hash, *keys)
Hash[ [keys, hash.values_at(*keys)].transpose]
end
def except(hash, *keys)
desired_keys = hash.keys - keys
Hash[ [desired_keys, hash.values_at(*desired_keys)].transpose]
end
ex:
slice({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2)
# => {'bar' => 'foo', 2 => 'two'}
except({foo: 'bar', 'bar' => 'foo', 2 => 'two'}, 'bar', 2)
# => {:foo => 'bar'}
Explanation:
Out of {:a => 1, :b => 2, :c => 3} we want {:a => 1, :b => 2}
hash = {:a => 1, :b => 2, :c => 3}
keys = [:a, :b]
values = hash.values_at(*keys) #=> [1, 2]
transposed_matrix =[keys, values].transpose #=> [[:a, 1], [:b, 2]]
Hash[transposed_matrix] #=> {:a => 1, :b => 2}
If you feels like monkey patching is the way to go, following is what you want:
module MyExtension
module Hash
def slice(*keys)
::Hash[[keys, self.values_at(*keys)].transpose]
end
def except(*keys)
desired_keys = self.keys - keys
::Hash[[desired_keys, self.values_at(*desired_keys)].transpose]
end
end
end
Hash.include MyExtension::Hash
You can use slice!(*keys) which is available in the core extensions of ActiveSupport
initial_hash = {:a => 1, :b => 2, :c => 3, :d => 4}
extracted_slice = initial_hash.slice!(:a, :c)
initial_hash would now be
{:b => 2, :d =>4}
extracted_slide would now be
{:a => 1, :c =>3}
You can look at slice.rb in ActiveSupport 3.1.3
module HashExtensions
def subhash(*keys)
keys = keys.select { |k| key?(k) }
Hash[keys.zip(values_at(*keys))]
end
end
Hash.send(:include, HashExtensions)
{:a => :A, :b => :B, :c => :C, :d => :D}.subhash(:a) # => {:a => :A}
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
keys = [:b, :d, :e, :f]
h2 = (h1.keys & keys).each_with_object({}) { |k,h| h.update(k=>h1.delete(k)) }
#=> {:b => :B, :d => :D}
h1
#=> {:a => :A, :c => :C}
if you use rails, it may be convenient to use Hash.except
h = {a:1, b:2}
h1 = h.except(:a) # {b:2}
Both delete_if and keep_if are part of Ruby core. Here you can achieve what you would like to without patching the Hash type.
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2 = h1.clone
p h1.keep_if { |key| [:b, :d, :e, :f].include?(key) } # => {:b => :B, :d => :D}
p h2.delete_if { |key, value| [:b, :d, :e, :f].include?(key) } #=> {:a => :A, :c => :C}
For futher info, check the links below from the documentation:
delete_if
keep_if
As others have mentioned, Ruby 2.5 added the Hash#slice method.
Rails 5.2.0beta1 also added it's own version of Hash#slice to shim the functionality for users of the framework that are using an earlier version of Ruby.
https://github.com/rails/rails/commit/01ae39660243bc5f0a986e20f9c9bff312b1b5f8
If looking to implement your own for whatever reason, it's a nice one liner as well:
def slice(*keys)
keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) }
end unless method_defined?(:slice)
if you want to extract from data base record also it is better to use slice
hash = { a: 1, b: 2, c: 3, d: 4 }
hash.slice!(:a, :b) # => {:c=>3, :d=>4}
hash # => {:a=>1, :b=>2}
https://api.rubyonrails.org/classes/Hash.html#method-i-slice-21
class Hash
def extract(*keys)
key_index = Hash[keys.map{ |k| [k, true] }] # depends on the size of keys
partition{ |k, v| key_index.has_key?(k) }.map{ |group| Hash[group] }
end
end
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2, h1 = h1.extract(:b, :d, :e, :f)
Here is a quick performance comparison of the suggested methods, #select seems to be the fastest
k = 1_000_000
Benchmark.bmbm do |x|
x.report('select') { k.times { {a: 1, b: 2, c: 3}.select { |k, _v| [:a, :b].include?(k) } } }
x.report('hash transpose') { k.times { Hash[ [[:a, :b], {a: 1, b: 2, c: 3}.fetch_values(:a, :b)].transpose ] } }
x.report('slice') { k.times { {a: 1, b: 2, c: 3}.slice(:a, :b) } }
end
Rehearsal --------------------------------------------------
select 1.640000 0.010000 1.650000 ( 1.651426)
hash transpose 1.720000 0.010000 1.730000 ( 1.729950)
slice 1.740000 0.010000 1.750000 ( 1.748204)
----------------------------------------- total: 5.130000sec
user system total real
select 1.670000 0.010000 1.680000 ( 1.683415)
hash transpose 1.680000 0.010000 1.690000 ( 1.688110)
slice 1.800000 0.010000 1.810000 ( 1.816215)
The refinement will look like this:
module CoreExtensions
module Extractable
refine Hash do
def extract(*keys)
select { |k, _v| keys.include?(k) }
end
end
end
end
And to use it:
using ::CoreExtensions::Extractable
{ a: 1, b: 2, c: 3 }.extract(:a, :b)
This code injects the functionality you're asking for into the Hash class:
class Hash
def extract_subhash! *keys
to_keep = self.keys.to_a - keys
to_delete = Hash[self.select{|k,v| !to_keep.include? k}]
self.delete_if {|k,v| !to_keep.include? k}
to_delete
end
end
and produces the results you provided:
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
p h1.extract_subhash!(:b, :d, :e, :f) # => {b => :B, :d => :D}
p h1 #=> {:a => :A, :c => :C}
Note: this method actually returns the extracted keys/values.
Here's a functional solution that can be useful if you're not running on Ruby 2.5 and in the case that you don't wan't to pollute your Hash class by adding a new method:
slice_hash = -> keys, hash { hash.select { |k, _v| keys.include?(k) } }.curry
Then you can apply it even on nested hashes:
my_hash = [{name: "Joe", age: 34}, {name: "Amy", age: 55}]
my_hash.map(&slice_hash.([:name]))
# => [{:name=>"Joe"}, {:name=>"Amy"}]
Just an addition to slice method, if the subhash keys which you want to separate from original hash is going to be dynamic you can do like,
slice(*dynamic_keys) # dynamic_keys should be an array type
We can do it by looping on keys only we want to extract and just checking the key is exist and then extract it.
class Hash
def extract(*keys)
extracted_hash = {}
keys.each{|key| extracted_hash[key] = self.delete(key) if self.has_key?(key)}
extracted_hash
end
end
h1 = {:a => :A, :b => :B, :c => :C, :d => :D}
h2 = h1.extract(:b, :d, :e, :f)

Searching for range overlaps in Ruby hashes

Say you have the following Ruby hash,
hash = {:a => [[1, 100..300],
[2, 200..300]],
:b => [[1, 100..300],
[2, 301..400]]
}
and the following functions,
def overlaps?(range, range2)
range.include?(range2.begin) || range2.include?(range.begin)
end
def any_overlaps?(ranges)
# This calls to_proc on the symbol object; it's syntactically equivalent to
# ranges.sort_by {|r| r.begin}
ranges.sort_by(&:begin).each_cons(2).any? do |r1, r2|
overlaps?(r1, r2)
end
end
and it's your desire to, for each key in hash, test whether any range overlaps with any other. In hash above, I would expect hash[:a] to make me mad and hash[:b] to not.
How is this best implemented syntactically?
hash.each{|k, v| puts "#{k} #{any_overlaps?( v.map( &:last )) ? 'overlaps' : 'is ok'}."}
output:
a overlaps.
b is ok.
Here's another way to write any_overlaps:
def any_overlaps?(ranges)
(a = ranges.map { |r| [r.first, r.last] }.sort_by(&:first).flatten) != a.sort
end
any_overlaps? [(51..60),(11..20),(18..30),(0..10),(31..40)] # => true
any_overlaps? [(51..60),(11..20),(21..30),(0..10),(31..40)] # => false

Resources