How Ruby Method Modify in Place - ruby

How does one write Ruby methods for modification in place?
I want to accomplish the following:
def fulljoin(ruby_array)
r = ''
ruby_array.each {|item| r += "'#{ item }', "}
r.chop!.chop!
end
a = ['Alex', 'Bert', 'Charlie']
a = fulljoin(a) # => 'Alex', 'Bert', 'Charlie'
But I want to modify the array a in place:
a.fulljoin!
What is the syntax to accomplish this?

Initially a is an Array. If you could write method a.fulljoin! with desirable result, a would become a String, but it's not possible in Ruby.
But a.fulljoin! can convert a to Array with single member a[0] - a
String you need. And it will be as close to your goal as possible:
class Array
def fulljoin!
r = "'#{self.join("', '")}'"
self.clear
self[0] = r
end
end
a = ["Alex", "Bert", "Charlie"]
a.fulljoin!
p a
=> ["'Alex', 'Bert', 'Charlie'"]
P.S.: As suggested by #engineersmnky, method fulljoin! can be simplified to:
class Array
def fulljoin!
self.replace(["'#{self.join("', '")}'"])
end
end

Related

Looping an array and storing the values to a hash in ruby

I am trying to loop an array which might look like following:
names = ['sid','john'] #this array will be dynamic, The values keep changing
I am trying to write a method where I will define an empty hash and loop the array using .each
and then store the values to hash.But not working.
def add_address
names = ['sid','john']
addr_arr = {}
names.each do |n|
addr_arr['name'] = n
end
addr_arr
end
this returns only {"name"=>"john"}.
What am I doing wrong?
The problem with your implementation is that there's only one hash and each time you set a value for the "name" key, the previous value for that key will be deleted and replaced by the new value.
I see addr_arr has arr in the name, so I assume you wanted something like this:
def add_address
names = ['sid','john']
addr_arr = []
names.each do |n|
addr_arr << { "name" => n}
end
addr_arr
end
add_address
#=> [{"name"=>"sid"}, {"name"=>"john"}]
or shorter:
['sid','john'].map{ |name| {"name" => name} }
#=> [{"name"=>"sid"}, {"name"=>"john"}]
If you always use the key 'name', you're overwriting its values every time, I don't think that's what you want. I don't know if this is what you want anyway, but this should be enough to understand the problem
names.each do |n|
addr_arr[n] = n
end

How does array#shift work? (or any destructive method)

I'd like to emulate how array#shift works in ruby, but on a string. Basically, I want to reopen String and add a method that will destructively change the string and return the character that it deletes:
class String
def shift
char = self[0]
# I thought this would work... not so fast!
self = self[1..-1]
char
end
end
It makes total sense that you can't change the value of self, but then how does array#shift do it? It doesn't even look like the array you're referencing as self points to a different object in memory
array = [1,2,3]
array.object_id #=> 70215380977180
array.shift
array = [2,3]
array.object_id #=> 70215380977180.. da fuq
How does this work?
Array#shift! is implemented in C, and can operate on the underlying array storage directly.
You can emulate this with String#slice!:
> str = "foobar"
=> "foobar"
> str.slice!(0, 1)
=> "f"
> str
=> "oobar"
If you want to implement this on String:
class String
def shift(n = 1)
slice!(0, n)
end
end

Make an array in Ruby

I am very beginner in Ruby and probably the question is too easy but well, I've already spent some time on it and couldn't find a solution.
My Ruby script takes a number (ex 10) and a name (ex Vincent). What I want is to make an array looking like
Vincent0
Vincent1..
Vincent9
I can't figure a way to make it..
def arrayfy(string, number)
arr = []
0.upto(number-1) do |i|
arr << "#{string}#{i}"
end
return arr
end
Update: To add these as variables to the class
class Foo
def arrayfy(string, number)
0.upto(number-1) do |i|
var_string = "##{string}#{i}"
var_symbol = var_string.to_sym
self.instance_variable_set(var_symbol, "")
end
end
end
Array.new(10) {|i| "Vincent#{i}"}
gives you
["Vincent0", "Vincent1", "Vincent2", "Vincent3", "Vincent4", "Vincent5",
"Vincent6", "Vincent7", "Vincent8", "Vincent9"]
The documentation for Array is available at http://www.ruby-doc.org/core/classes/Array.html (googling for Array RDoc will give you the URL).
The bit in the braces ({|i| "Vincent#{i}"}) is called a block. You'll definitely want to learn about them.
Using Array.new with a block (docs):
def create_array(count, name)
Array.new(10) { |i| "#{name}#{i} }
end
Using Enumerable#reduce (docs):
def create_array(count, name)
(0...count).reduce([]) { |m,i| m << "#{name}#{i}" }
end
Or using Enumerable#each_with_object (docs):
def create_array(count, name)
(0...count).each_with_object([]) { |i,a| a << "#{name}#{i}" }
end
Using it:
# Using the array (assigning to variables)
array = create_array(10, 'Vincent') # => ['Vincent0', 'Vincent1', 'Vincent2' ...]
name = array[1] # => 'Vincent1'
Just for the record, a solution in a more functional style:
>> def arrayify(str, n)
.. ([str] * n).zip(0...n).map(&:join)
.. end
#=> nil
>> arrayify('Vincent', 10)
#=> ["Vincent0", "Vincent1", "Vincent2", "Vincent3", "Vincent4", "Vincent5", "Vincent6", "Vincent7", "Vincent8", "Vincent9"]
def array_maker(number, string)
result = []
for i in 0..number do
result << "#{string}#{i}"
end
result
end

problem with variable in ruby

Excuse I am newbie in ruby.
My problem is about argument passes by value and reference.
I am coding this method
def show_as_tree(parents)
array = []
iterate_categories(parents, array)
end
def iterate_categories(parents, array)
parents.each do |p|
#return p.description or "-#{p.description} if the node is root or not
p.description = category_name(p)
#add to array
array << p
#call iterate categories with children of parent node and same array
iterate_categories(p.children, array)
end
end
however the array content is only the parent nodes.
I need understand the ruby mechanism for references and how could fix my problem?
I'm pretty sure Ruby just creates a copy of your array. Therefore what you should be doing is having
array << iterate_categories(p.children, new_array)
And return your array at the end of the function.
Just did a quick example: (Updating code based on Wayne Conrad's answer… it is correct)
class Person
attr_accessor :name, :children
end
class Test
def iterate_categories(parents,array)
parents.each do |p|
array << p.name
if !p.children.nil?
iterate_categories(p.children,array)
end
end
end
def iterate_categories_test
p1 = Person.new
p1.name = "Bob"
p2 = Person.new
p2.name = "Joe"
p3 = Person.new
p3.name = "Ann"
p4 = Person.new
p4.name = "John"
p1.children = [p2,p3]
p3.children = [p4]
array = []
iterate_categories([p1],array)
puts array
end
end
Then:
>> a = Test.new
>> a.iterate_categories_test
Bob
Joe
Ann
John
=> nil
Hopefully that helps.
Your show_as_tree method should return the array.
def show_as_tree(parents)
array = []
iterate_categories(parents, array)
array
end
Without array as the last line, the return value of show_as_tree is the return value of iterate_categories, which happens to be parents. That's why it looks like only parents is getting added to array. That's an illusion: It was parents being returned, not array.
Ruby does not make copies of its arguments. It passes references by value. That means it is the same array being acted upon throughout your functions.
I don't quite get the code, the formatting seems all wrong but I'll try to help.
When you pass an argument to ruby, it seems to give the method a copy, not a reference. Fortunately, you don't need to worry about that, as ruby lets you return multiple things in one call.
For example, in irb I ran something like this:
ruby-1.9.2-p180 :005 > def stuff(a, b)
ruby-1.9.2-p180 :006?> c = a + b
ruby-1.9.2-p180 :007?> [c, a]
ruby-1.9.2-p180 :008?> end
=> nil
ruby-1.9.2-p180 :009 > a, b = stuff(1, 2)
=> [3, 1]
ruby-1.9.2-p180 :010 > a
=> 3
ruby-1.9.2-p180 :011 > b
=> 1
That way you can easily return multiple values without problem
I hope I answered your question.

Merge Ruby arrays

I have a few arrays of Ruby objects of class UserInfo:
class UserInfo
attr_accessor :name, :title, :age
end
How can I merge these arrays into one array? A user is identified by its name, so I want no duplicate names. If name, title, age, etc. are equal I'd like to have 1 entry in the new array. If names are the same, but any of the other details differ I probably want those 2 users in a different array to manually fix the errors.
Thanks in advance
Redefine equality comparison on your object, and you can get rid of actual duplicates quickly with Array#uniq
class UserInfo
attr_accessor :name, :title, :age
def == other
name==other.name and title==other.title and age==other.age
end
end
# assuming a and b are arrays of UserInfo objects
c = a | b
# c will only contain one of each UserInfo
Then you can sort by name and look for name-only duplicates
d = c.sort{ |p,q| p.name <=> q.name } #sort by name
name = ""
e = []
d.each do |item|
if item.name == name
e[-1] = [e[-1],item].flatten
else
e << item
end
end
A year ago I monkey patched a kind of cryptic instance_variables_compare on Object. I guess you could use that.
class Object
def instance_variables_compare(o)
Hash[*self.instance_variables.map {|v|
self.instance_variable_get(v)!=o.instance_variable_get(v) ?
[v,o.instance_variable_get(v)] : []}.flatten]
end
end
A cheesy example
require 'Date'
class Cheese
attr_accessor :name, :weight, :expire_date
def initialize(name, weight, expire_date)
#name, #weight, #expire_date = name, weight, expire_date
end
end
stilton=Cheese.new('Stilton', 250, Date.parse("2010-12-02"))
gorgonzola=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
irb is my weapon of choice
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola", "#expire_date"=>#<Date: 4910305/2,0,2299161>}
>> gorgonzola.instance_variables_compare(stilton)
=> {"#name"=>"Stilton", "#expire_date"=>#<Date: 4910275/2,0,2299161>}
>> stilton.expire_date=gorgonzola.expire_date
=> #<Date: 4910305/2,0,2299161>
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola"}
>> stilton.instance_variables_compare(stilton)
=> {}
As you can see the instance_variables_compare returns an empty Hash if the two objects has the same content.
An array of cheese
stilton2=Cheese.new('Stilton', 210, Date.parse("2010-12-02"))
gorgonzola2=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
arr=[]<<stilton<<stilton2<<gorgonzola<<gorgonzola2
One hash without problems and one with
h={}
problems=Hash.new([])
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name].instance_variables_compare(c) != {}
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Now the Hash h contains the objects without merging problems and the problems hash contains those that has instance variables that differs.
>> h
=> {"Gorgonzola"=>#<Cheese:0xb375e8 #name="Gorgonzola", #weight=250, #expire_date=#<Date: 2010-12-17 (4911095/2,0,2299161)>>}
>> problems
=> {"Stilton"=>[#<Cheese:0xf54c30 #name="Stilton", #weight=210, #expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>, #<Cheese:0xfdeca8 #name="Stilton", #weight=250,#expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>]}
As far as I can see you will not have to modify this code at all to support an array of UserInfo objects.
It would most probably be much faster to compare the properties directly or with a override of ==. This is how you override ==
def ==(other)
return self.weight == other.weight && self.expire_date == other.expire_date
end
and the loop changes into this
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name] != c
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Finally you might want to convert the Hash back to an Array
result = h.values
Here's another potential way. If you have a way of identifying each UserInfo, say a to_str method that prints out the values:
def to_str()
return "#{#name}:#{#title}:#{#age}"
end
You can use inject and a hash
all_users = a + b # collection of users to "merge"
res = all_users.inject({})do |h,v|
h[v.to_str] = v #save the value indexed on the string output
h # return h for the next iteration
end
merged = res.values #the unique users

Resources