Pass method to reduce/inject instead of block - ruby

What is the best way to pass a method to reduce or inject instead of block like this:
def super_process(list, item)
list ||= []
list << another_method(item) + just_another_method
end
arr = ['1', '2', '3']
arr.reduce(&method(:super_process))
I have a problem with handling of list (it's default value). It's assigned to the first element of arr on first iteration but on next iteration it's assigned to the result of the first one.
I know I can write:
arr.reduce {|list, item| list << another_method(list, item) }
But that seems quite long and inexpressive to me.

The problem in your example is due to not passing the initial value to reduce. From ruby-doc.org:
reduce { |memo, obj| block } → obj
...
If you do not explicitly specify an initial value for memo, then the
first element of collection is used as the initial value of memo.
Therefore you probably want to pass an array as the first argument. I've changed the definition of super_process to something simpler:
def super_process list, item
list.push item + 1
end
arr = [1, 2, 3]
res = arr.reduce [], &method(:super_process)
puts res
This will output
2
3
4

Related

Creating a Ruby method that pads an Array

I'm working on creating a method that pads an array, and accepts 1. a desired value and 2. an optional string/integer value. Desired_size reflects the desired number of elements in the array. If a string/integer is passed in as the second value, this value is used to pad the array with extra elements. I understand there is a 'fill' method that can shortcut this - but that would be cheating for the homework I'm doing.
The issue: no matter what I do, only the original array is returned. I started here:
class Array
def pad(desired_size, value = nil)
desired_size >= self.length ? return self : (desired_size - self.length).times.do { |x| self << value }
end
end
test_array = [1, 2, 3]
test_array.pad(5)
From what I researched the issue seemed to be around trying to alter self's array, so I learned about .inject and gave that a whirl:
class Array
def pad(desired_size, value = nil)
if desired_size >= self.length
return self
else
(desired_size - self.length).times.inject { |array, x| array << value }
return array
end
end
end
test_array = [1, 2, 3]
test_array.pad(5)
The interwebs tell me the problem might be with any reference to self so I wiped that out altogether:
class Array
def pad(desired_size, value = nil)
array = []
self.each { |x| array << x }
if desired_size >= array.length
return array
else
(desired_size - array.length).times.inject { |array, x| array << value }
return array
end
end
end
test_array = [1, 2, 3]
test_array.pad(5)
I'm very new to classes and still trying to learn about them. Maybe I'm not even testing them the right way with my test_array? Otherwise, I think the issue is I get the method to recognize the desired_size value that's being passed in. I don't know where to go next. Any advice would be appreciated.
Thanks in advance for your time.
In all 3 of your tries, you are returning the original array if desired_size is greater than the original array size. You have that backwards. In other words, you just return instead of padding.
Your first attempt was close. You need to:
1) Fix your conditional check.
2) It's OK to modify the self array, so the more complicated tries are not necessary.
3) Make sure you return self no matter what you do.
By modifying self, not only do you return the modified array, but you also change the array held by the variable test_array. So if you were to do:
test_array = [1, 2, 3]
puts test_array.pad(5, 4).inspect // prints [1, 2, 3, 4, 4]
puts test_array // prints [1, 2, 3, 4, 4]
In Ruby, when a function modifies self, the function name ends with a !, so if you were to write it modifying self, it would be better to name it pad!.
If you want to write it so that it doesn't modify self, you could start with:
array = self.dup
and then do all of your operations on array.

Complicated ruby inject method

Can't seem to figure this out. Please help me understand what this code is requesting for regarding a variable and what the intended output is supposed to be. Thanks in advance!
def function_name(a)
a.inject({}){ |a,b| a[b] = a[b].to_i + 1; a}.\
reject{ |a,b| b == 1 }.keys
end
Assuming a is an array,
The function first count the occurrences of the keys.
a = ['a', 'b', 'c', 'b']
a.inject({}) { |a,b|
# a: a result hash, this is initially an empty hash (`{}` passed to inject)
# b: each element of the array.
a[b] = a[b].to_i + 1 # Increase count of the item
a # The return value of this block is used as `a` argument of the block
# in the next iteration.
}
# => {"a"=>1, "b"=>2, "c"=>1}
Then, it filter items that occur multiple times:
...reject{ |a,b|
# a: key of the hash entry, b: value of the hash entry (count)
b == 1 # entry that match this condition (occurred only once) is filtered out.
}.keys
# => ["b"]
So, function names like get_duplicated_items should be used instead of function_name to better describe the purpose.
It wants a to be an array, but it doesn't seem to matter what the array is made up of so you'll need some other clue to know what should be in the array.
What the code does is fairly straight foreword. For each item in the array it uses it as a key in a hash. It then basically counts how many times it sees that key. Finally it removes all of the items that only showed up once.
It returns the unique items in the array a that show up 2 or more times.

Adding array together with nil as a possibility

I am creating a method that will take an array of numbers and add them together. I don't want to use inject because I haven't learned it yet. I prefer to start with the basics. I want to use either each or while.
I've been re-writing this code and testing it against rspec, and I keep running into a problem because the first test consists of the array being empty with nil. I tried doing an if else statement to set nil to 0 if the array is empty?, but that didn't seem to work. Here is what I've got right now.
def sum(x)
total = 0
sum.each { |x| total += x}
total
end
The rspec is testing an empty array [] as well as others that have multiple integers. Thoughts?
You're not enumerating the array passed in to the method, you're enumerating the variable sum. You want x.each { |x| total += x}, although using x within the {} is a little odd in this case because you've used the name for your method parameter.
You can use compact! to remove the nils from your array.
def sum(x)
total = 0
x.compact! #lose the nils
x.each { |i| total += i}
total
end
Edit:
If the x being passed to your sum() method is nil, you can check for that with nil?.
The do something like
if x.nil?
0 #assuming you want to return 0
else
#rest of your function
You want to return nil if the array passed in is empty?
You are getting confused with your identifiers. You are trying to iterate over sum, which is the name of the method, and you are using x as both the method parameter and the iteration block parameter.
I suggest you use something more descriptive, like arr for the method parameter and v for the block parameter (holding the value of each value from the array).
Finally, you need to initialise the total to nil so that the correct value is returned if the array is empty. Unfortunately you can't do arithmetic on nil, so in the code below I have added a line to set total to zero if it isn't already set.
This will do what you ask.
def sum(arr)
total = nil
arr.each do |v|
total = 0 unless total
total += v
end
total
end
p sum [1,2,3]
p sum []
output
6
nil
You could create a new instance method for the Array class:
class Array
def sum
total = 0.0
self.each {|x| total += x if ['Fixnum', 'Float'].include?(x.class.name)}
total%1==0 ? total.to_i : total
end
end
Then you would use it like so:
puts [].sum # => 0
puts [1, 2, 3].sum # => 6
puts [2, nil, "text", 4.5].sum # => 6.5

Return array of hashes based on hash element match

I am wondering how one would search through an array of hashes and return a value based on a search string. For example, #contacts contains the hash elements: :full_name, :city, and :email. The variable #contacts (I guess it would be an array) contains three entries (perhaps rows). Below is the code I have so far to conduct a search based on :city value. However it's not working. Can anyone give me an idea what's going on?
def search string
#contacts.map {|hash| hash[:city] == string}
end
You should use select instead of map:
def search string
#contacts.select { |hash| hash[:city] == string }
end
In your code you tried to map (or transform) your array using a block, which yields boolean values. map takes a block and invokes the block for each element of self, constructing a new array containing elements returned by the block. As the result, you got an array of booleans.
select works similar. It takes a block and iterates over the array as well, but instead of transforming the source array it returns an array containing elements for which the block returns true. So it's a selection (or filtering) method.
In order to understand the difference between these two methods it's useful to see their example definitions:
class Array
def my_map
[].tap do |result|
self.each do |item|
result << (yield item)
end
end
end
def my_select
[].tap do |result|
self.each do |item|
result << item if yield item
end
end
end
end
Example usage:
irb(main):007:0> [1,2,3].my_map { |x| x + 1 }
[2, 3, 4]
irb(main):008:0> [1,2,3].my_select { |x| x % 2 == 1 }
[1, 3]
irb(main):009:0>
You can try this:
def search string
#contacts.select{|hash| h[:city].eql?(string) }
end
This will return an array of hashes which matches string.

Ruby 'tap' method - inside assignment

Recently I discovered that tap can be used in order to "drily" assign values to new variables; for example, for creating and filling an array, like this:
array = [].tap { |ary| ary << 5 if something }
This code will push 5 into array if something is truthy; otherwise, array will remain empty.
But I don't understand why after executing this code:
array = [].tap { |ary| ary += [5] if something }
array remains empty. Can anyone help me?
In the first case array and ary point to the same object. You then mutate that object using the << method. The object that both array and ary point to is now changed.
In the second case array and ary again both point to the same array. You now reassign the ary variable, so that ary now points to a new array. Reassigning ary however has no effect on array. In ruby reassigning a variable never effects other variables, even if they pointed to the same object before the reassignment.
In other words array is still empty for the same reason that x won't be 42 in the following example:
x = 23
y = x
y = 42 # Changes y, but not x
Edit: To append one array to another in-place you can use the concat method, which should also be faster than using +=.
I want to expand on this a bit:
array = [].tap { |ary| ary << 5 if something }
What this does (assuming something is true-ish):
assigns array to [], an empty array.
array.object_id = 2152428060
passes [] to the block as ary. ary and array are pointing to the same array object.
array.object_id = 2152428060
ary.object_id = 2152428060
ary << 5 << is a mutative method, meaning it will modify the receiving object. It is similar to the idiom of appending ! to a method call, meaning "modify this in place!", like in .map vs .map! (though the bang does not hold any intrinsic meaning on its own in a method name). ary has 5 inserted, so ary = array = [5]
array.object_id = 2152428060
ary.object_id = 2152428060
We end with array being equal to [5]
In the second example:
array = [].tap{ |ary| ary += [5] if something }
same
same
ary += 5 += is short for ary = ary + 5, so it is first modification (+) and then assignment (=), in that order. It gives the appearance of modifying an object in place, but it actually does not. It creates an entirely new object.
array.object_id = 2152428060
ary.object_id = 2152322420
So we end with array as the original object, an empty array with object_id=2152428060 , and ary, an array with one item containing 5 with object_id = 2152322420. Nothing happens to ary after this. It is uninvolved with the original assignment of array, that has already happened. Tap executes the block after array has been assigned.

Resources