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

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]]

Related

Mass method invocation

So in Ruby one can mass assign variables like this:
a, b, c = [1, 2, 3]
But what if I wanted to do the same for object methods, but without having to write out the whole thing like so:
foo.a, foo.b, foo.c = [1, 2, 3]
Is there a DRY way to accomplish this?
I am not sure you’ll like it, but the DRYest way I can think of is:
[:a, :b, :c].zip([1, 2, 3]).each { |k, v| foo.public_send "#{k}=", v }
# or vice versa
[1, 2, 3].zip([:a, :b, :c]).each { |v, k| foo.public_send "#{k}=", v }
Or, in more OO way:
class Foo
attr_accessor :a, :b, :c
def massive_assign attrs, values
attrs.zip(values).each { |k, v| public_send "#{k}=", v }
end
end
foo = Foo.new
foo.massive_assign([:a, :b, :c], [1, 2, 3])
Mass assignment to instance variables can be done like below as well:
foo.instance_eval { #a, #b, #c = [1, 2, 3] }
There's no specific syntax, but you could implement a setter for multiple attributes that are passed as a hash. Rails uses a similar approach:
class Foo
attr_accessor :a, :b, :c
def attributes=(attrs)
attrs.each do |name, value|
public_send("#{name}=", value)
end
end
end
foo = Foo.new
#=> #<Foo:0x007fc6d8a1e950>
foo.attributes = { a: 1, b: 2, c: 3 }
#=> #<Foo:0x007fc6d8a1e950 #a=1, #b=2, #c=3>
From the comments up there I assume that the poster is meaning a case where it's not actually foo but a more complex expression; and not only 3 assignments (method calls) but many.
There are 2 techniques that come to mind: tap and send:
some.complex[expression].which.evaluates.to.the.receiving.object.tap do |obj|
value_hash.each_pair do |key,value|
obj.send("#{key}=", value)
end
end
Hope that helps.
Of course, the usual security caveats apply (better make sure value_hash only contains valid/sane/allowed names).

Path to an embedded object

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

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]]

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

Ruby: provide an argument while turning proc to a block

We can easily define a method and turn it into block with unary ampersand.
def my_method(arg)
puts arg*2
end
['foo', 'bar'].each(&method(:my_method))
# foofoo
# barbar
# or
my_method = ->(arg) { puts arg*2 }
['foo', 'bar'].each(&my_method)
# same output
As we see the first argument is passed automatically when we work with aggregates. But what if we need to pass 2 or even more arguments?
my_method = ->(arg,num) { puts arg*num }
['foo', 'bar'].each(&my_method)
# ArgumentError: wrong number of arguments (1 for 2)
['foo', 'bar'].each(&my_method(3))
# NoMethodError: undefined method `foo' for main:Object
['foo','bar'].each do |i, &my_method|
yield i, 3
end
# LocalJumpError: no block given (yield)
Is that possible to pass additional arguments while turning proc to a block?
#sawa is right. You can do that with curry.
Proc version:
mult = proc {|a, b| a * b} # => #<Proc:0x00000002af1098#(irb):32>
[1, 2].map(&mult.curry[2]) # => [2, 4]
Method version:
def mult(a, b)
a*b
end
[1, 2].map(&method(:mult).to_proc.curry[2]) # => [2, 4]
Regarding your comment:
Strange, but it swaps arguments during the performance
Actually, the argument order is preserved.
curry returns a new proc that effectively collects arguments until there are enough arguments to invoke the original method / proc (based on its arity). This is achieved by returning intermediate procs:
def foo(a, b, c)
{ a: a, b: b, c: c }
end
curried_proc = foo.curry #=> #<Proc:0x007fd09b84e018 (lambda)>
curried_proc[1] #=> #<Proc:0x007fd09b83e320 (lambda)>
curried_proc[1][2] #=> #<Proc:0x007fd09b82cfd0 (lambda)>
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}
You can pass any number of arguments at once to a curried proc:
curried_proc[1][2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2][3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1][2, 3] #=> {:a=>1, :b=>2, :c=>3}
curried_proc[1, 2, 3] #=> {:a=>1, :b=>2, :c=>3}
Empty arguments are ignored:
curried_proc[1][][2][][3] #=> {:a=>1, :b=>2, :c=>3}
However, you obviously can't alter the argument order.
An alternative to currying is partial application which returns a new proc with lower arity by fixing one or more arguments. Unlike curry, there's no built-in method for partial application, but you can easily write your own:
my_proc = -> (arg, num) { arg * num }
def fix_first(proc, arg)
-> (*args) { proc[arg, *args] }
end
fixed_proc = fix_first(my_proc, 'foo') #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc[2] #=> "foofoo"
fixed_proc[3] #=> "foofoofoo"
[2, 3].map(&fixed_proc) #=> ["foofoo", "foofoofoo"]
Or fixing the last argument:
def fix_last(proc, arg)
-> (*args) { proc[*args, arg] }
end
fixed_proc = fix_last(my_proc, 2) #=> #<Proc:0x007fa31c2070d0 (lambda)>
fixed_proc['foo'] #=> "foofoo"
fixed_proc['bar'] #=> "barbar"
['foo', 'bar'].map(&fixed_proc) #=> ["foofoo", "barbar"]
Of course, you are not limited to fixing single arguments. You could for example return a proc that takes an array and converts it to an argument list:
def splat_args(proc)
-> (array) { proc[*array] }
end
splatting_proc = splat_args(my_proc)
[['foo', 1], ['bar', 2], ['baz', 3]].map(&splatting_proc)
#=> ["foo", "barbar", "bazbazbaz"]

Resources