How to convert a ruby lambda to a reusable library method - ruby

I have a method in a chef recipe like so
overwrite_properties = lambda do |tmpl_path, params|
attrs = {}
File.read(tmpl_path).split("\n").map do |line|
line = line.sub(/#.*$/, '').strip
j, v = line.split(/\s*=\s*/, 2)
attrs[j] = v if j
end
params.each {|j,v| v.nil? ? attrs.delete(j) : attrs[j] = v }
attrs.map {|j,v| "#{j}=#{v}\n" }.sort.join
end
which is called like so
overwrite_properties.call("#{server_home}/config.orig/#{fname}", params)
My question is how can I convert this to a reusable function to be called from another module?
eg.
module HelperMod
def self.overwrite_properties(&block)
//etc
end
end
Thanks

This is simple - a lambda is an anonymous function (function with no name). You can convert this to a function within a module like so:
module HelperMod
def self.overwrite_properties(tmpl_path, params)
attrs = {}
File.read(tmpl_path).split("\n").map do |line|
line = line.sub(/#.*$/, '').strip
j, v = line.split(/\s*=\s*/, 2)
attrs[j] = v if j
end
params.each {|j,v| v.nil? ? attrs.delete(j) : attrs[j] = v }
attrs.map {|j,v| "#{j}=#{v}\n" }.sort.join
end
end
HelperMod.overwrite_properties("#{server_home}/config.orig/#{fname}", params)

Related

how to apply a function to each values of `params`

I am trying to pre-process the params with to_ar2en_i function in ApplicationController before any action processes the params, I have the following in my application_controller.rb:
# translates every params' entity from arabic to english
before_action :param_convert_ar2en_i
private
def param_convert_ar2en_i h = nil, path = []
h ||= params
h.each_pair do |k, v|
if v.respond_to?(:key?)
param_convert_ar2en_i v, [path, k].flatten
else
# something like:
params[[path, k].flatten].to_ar2en_i
end
end
end
The problem is I don't know how to apply to_ar2en_i to a nested params with the path of [[path, k].flatten].
Can anybody kindly help me on this?
Silly me!! Instead of trying to access params in params[[path, k].flatten].to_ar2en_i all I needed to do is just to access h[k].to_ar2en_i and since the h is passed by reference in Ruby, it will do the job.
def param_convert_ar2en_i h = nil, l = [], depth: 0
h ||= params
h.each_pair do |k, v|
if v.respond_to? :key?
param_convert_ar2en_i v, [l, k].flatten, depth: depth + 1
else
if h[k].respond_to? :each
h[k].each { |i| i.to_ar2en_i if i.respond_to? :to_ar2en_i }
else
h[k].to_ar2en_i if h[k].respond_to? :to_ar2en_i
end
end
end
end

Ruby: Modify a Range

I'm trying to add a method to the Range class. The goal is to check if an integer is is included in the range and to include it if it isn't. It would be useful to store in a Range some minimum and maximum values.
So I was thinking of the following:
class Range
def include!(n)
if n < self.begin
self = n..self.end
elsif n > self.end
self = self.begin..n
end
end
end
r = Range.new(500, 500)
100.times do
r.include!(rand(1000))
end
But I get a Can't change the value of self error.
Would this be the only solution:
class Range
def include(n)
if n < self.begin
n..self.end
elsif n > self.end
self.begin..n
else
self
end
end
end
r = Range.new(500, 500)
100.times do
r = r.include(rand(1000))
end
Ranges themselves are immutable, you can't change their properties after they are created. However, it's still possible to achieve what you want by creating new ranges instead of modifying existing ones, as you discovered. I'd probably do it like this:
module RangeAdder
def +(value)
return self if value.nil?
return self + value.min + value.max if value.is_a? Range
return value.reduce(self, :+) if value.is_a? Enumerable
new_begin = [self.begin, value].min
new_end = [self.end, value].max
new_exclude_end = value > self.end ? false : self.exclude_end?
Range.new(new_begin, new_end, new_exclude_end)
end
end
class Range
include RangeAdder
end
Then:
r = 500..500
r += Array.new(100) { rand(1000) }
#=> 15..982
Or if all you really want to do is get the minimum and maximum values from an array, you can do that with:
a = Array.new(100) { rand(1000) }
r = Range.new(*a.minmax)
#=> 11..996

Check if two linked lists are equal in Ruby?

I have the following implementation of a linked list in Ruby:
class Node
attr_accessor :data, :next
def initialize(data = nil)
#data = data
#next = nil
end
end
class LinkedList
def initialize(items)
#head = Node.new(items.shift)
items.inject(#head) { |last, data| #tail = last.next = Node.new(data) }
end
def iterate
return nil if #head.nil?
entry = #head
until entry.nil?
yield entry
entry = entry.next
end
end
def equal?(other_list)
#How do I check if all the data for all the elements in one list are the same in the other one?
end
end
I have tried using the .iterate like this:
def equals?(other_list)
other_list.iterate do |ol|
self.iterate do |sl|
if ol.data != sl.data
return false
end
end
end
return true
end
But this is doing a nested approach. I fail to see how to do it.
You can't do it easily with the methods you have defined currently, as there is no way to access a single next element. Also, it would be extremely useful if you implemented each instead of iterate, which then gives you the whole power of the Enumerable mixin.
class LinkedList
include Enumerable # THIS allows you to use `zip` :)
class Node # THIS because you didn't give us your Node
attr_accessor :next, :value
def initialize(value)
#value = value
#next = nil
end
end
def initialize(items)
#head = Node.new(items.shift)
items.inject(#head) { |last, data| #tail = last.next = Node.new(data) }
end
def each
return enum_for(__method__) unless block_given? # THIS allows block or blockless calls
return if #head.nil?
entry = #head
until entry.nil?
yield entry.value # THIS yields node values instead of nodes
entry = entry.next
end
end
def ==(other_list)
# and finally THIS - get pairs from self and other, and make sure all are equal
zip(other_list).all? { |a, b| a == b }
end
end
a = LinkedList.new([1, 2, 3])
b = LinkedList.new([1, 2, 3])
c = LinkedList.new([1, 2])
puts a == b # => true
puts a == c # => false
EDIT: I missed this on the first run through: equal? is supposed to be referential identity, i.e. two variables are equal? if they contain the reference to the same object. You should not redefine that method, even though it is possible. Rather, == is the general common-language meaning of "equal" as in "having the same value", so I changed it to that.
I think there is something wrong with your initialize method in LinkedList, regardless could this be what you need
...
def equal?(other_list)
other_index = 0
cur_index = 0
hash = Hash.new
other_list.iterate do |ol|
hash[ol.data.data] = other_index
other_index += 1
end
self.iterate do |node|
return false if hash[node.data.data] != cur_index
return false if !hash.has_key?(node.data.data)
cur_index += 1
end
return true
end
...
Assuming this is how you use your code
a = Node.new(1)
b = Node.new(2)
c = Node.new(3)
listA = [a,b,c]
aa = Node.new(1)
bb = Node.new(2)
cc = Node.new(3)
listB = [aa,bb,cc]
linkA = LinkedList.new(listA)
linkB = LinkedList.new(listB)
puts linkA.equal?(linkB)

Is there an abstraction for the declare-update-return pattern?

When writing iterative code with mutation in ruby, I often find myself following this pattern:
def build_x some_data
x = [] # or x = {}
some_data.each do |data|
x.some_in_place_update! (... data ...)
end
x
end
(x often does not have the same shape as some_data, so a simple map will not do.)
Is there a more idiomatic or better way to write code that follows this pattern?
[edit] A real example:
def to_hierarchy stuff
h = {}
stuff.each do |thing|
path = thing.uri.split("/").drop(4)
sub_h = h
path.each do |segment|
sub_h[segment] ||= {}
sub_h = sub_h[segment]
end
sub_h.merge!(
data: thing.data,
)
end
h
end
This begins with a flat list of things, which have related but distinct uris. It transforms this flat list into a hierarchy, grouping related things that share the same segments of a uri. This follows the pattern I described: initialize h, loop over some data and mutate h along the way, and then spit out h at the end.
[edit2] Another related example
def count_data obj
i = if obj[:data] then 1 else 0
obj.each do |k, v|
i += count_statements v unless :data == k
end
i
end
Your to_hierarchy example could be done with each_with_object:
def to_hierarchy stuff
stuff.each_with_object({}) do |thing, h|
#...
end
end
each_with_object passes the extra object to the block and returns that object when the iteration is done.
If you're more of a traditionalist, you could use inject:
def to_hierarchy stuff
stuff.inject({}) do |h, thing|
#...
h
end
end
Note the block argument order change and that the block has to return h so that inject can feed it back into the next block invocation.
Your general example could be written as:
def build_x some_data
some_data.each_with_object([]) do |data, x|
x.some_in_place_update! (... data ...)
end
end
or:
def build_x some_data
some_data.inject({}) do |x, data|
x.some_in_place_update! (... data ...)
x
end
end
Ah! You want each_with_object. Like this
def to_hierarchy stuff
stuff.each_with_object({}) do |thing, h|
path = thing.uri.split("/").drop(4)
sub_h = h
path.each do |segment|
sub_h[segment] ||= {}
sub_h = sub_h[segment]
end
sub_h.merge!(
data: thing.data,
)
end
end

How to handle combination []+= for auto-vivifying hash in Ruby?

In order to implement auto-vivification of Ruby hash, one can employ the following class
class AutoHash < Hash
def initialize(*args)
super()
#update, #update_index = args[0][:update], args[0][:update_key] unless
args.empty?
end
def [](k)
if self.has_key?k
super(k)
else
AutoHash.new(:update => self, :update_key => k)
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def few(n=0)
Array.new(n) { AutoHash.new }
end
end
This class allows to do the following things
a = AutoHash.new
a[:a][:b] = 1
p a[:c] # => {} # key :c has not been created
p a # => {:a=>{:b=>1}} # note, that it does not have key :c
a,b,c = AutoHash.new.few 3
b[:d] = 1
p [a,b,c] # => [{}, {:d=>1}, {}] # hashes are independent
There is a bit more advanced definition of this class proposed by Joshua, which is a bit hard for me to understand.
Problem
There is one situation, where I think the new class can be improved. The following code fails with the error message NoMethodError: undefined method '+' for {}:AutoHash
a = AutoHash.new
5.times { a[:sum] += 10 }
What would you do to handle it? Can one define []+= operator?
Related questions
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
Multiple initialization of auto-vivifying hashes using a new operator in Ruby
ruby hash initialization r
still open: How to create an operator for deep copy/cloning of objects in Ruby?
There is no way to define a []+= method in ruby. What happens when you type
x[y] += z
is
x[y] = x[y] + z
so both the [] and []= methods are called on x (and + is called on x[y], which in this case is an AutoHash). I think that the best way to handle this problem would be to define a + method on AutoHash, which will just return it's argument. This will make AutoHash.new[:x] += y work for just about any type of y, because the "empty" version of y.class ('' for strings, 0 for numbers, ...) plus y will almost always equal y.
class AutoHash
def +(x); x; end
end
Adding that method will make both of these work:
# Numbers:
a = AutoHash.new
5.times { a[:sum] += 10 }
a[:sum] #=> 50
# Strings:
a = AutoHash.new
5.times { a[:sum] += 'a string ' }
a[:sum] #=> "a string a string a string a string a string "
And by the way, here is a cleaner version of your code:
class AutoHash < Hash
def initialize(args={})
super
#update, #update_index = args[:update], args[:update_key]
end
def [](k)
if has_key? k
super(k)
else
AutoHash.new :update => self, :update_key => k
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def +(x); x; end
def self.few(n)
Array.new(n) { AutoHash.new }
end
end
:)
What I think you want is this:
hash = Hash.new { |h, k| h[k] = 0 }
hash['foo'] += 3
# => 3
That will return 3, then 6, etc. without an error, because the the new value is default assigned 0.
require 'xkeys' # on rubygems.org
a = {}.extend XKeys::Hash
a[:a, :b] = 1
p a[:c] # => nil (key :c has not been created)
p a # => { :a => { :b => 1 } }
a.clear
5.times { a[:sum, :else => 0] += 10 }
p a # => { :sum => 50 }

Resources