How can I implement a chain method for my class? - arrays

I'm working with a string that has to be converted to a 2 dimensional array:
rows = [['1', '2'], ['10', '20']]
I need those values as integers instead of strings. I can iterate through them and then do map, like:
rows.each {|row| row.map!(&:to_i)}
I was trying to create a to_i method that could be chainable, so I can run rows.to_i.
def to_i
each do |nested|
nested.map!(&:to_i)
end
end
This unsurprisingly fails:
NoMethodError: undefined method `to_i' for [["1", "2"], ["10", "20"]]:Array
since Array doesn't implement the method. Besides monkey patching the Array class, is there a proper or more rubier way to do it?
The other alternative is doing:
def ary_to_i array
array.each do |nested|
nested.map!(&:to_i)
end
end
but I found the method call ary_to_i(rows) confusing.

You could define a to_i method just for rows:
rows = [['1','2'], ['10','20']]
def rows.to_i
each do |nested|
nested.map!(&:to_i)
end
end
p rows.to_i # => [[1, 2], [10, 20]]

Although it’s an anti-pattern, you might monkey-patch the Array class:
class Array
def to_i
map do |nested|
nested.map(&:to_i)
end
end
def to_i!
each do |nested|
nested.map!(&:to_i)
end
end
end
But I’d better go with the latter alternative in OP.
Sidenote: mutating arrays is not a best idea ever, but if you do, choose the name ending with a bang as in my example.

Related

Delete item from array without returning it

I'm trying to write a method that will cause a rspec test like this to pass:
it "starts the thing and move on" do
class.method_1("Name One")
class.method_1("Name Two")
expect(class.method_2).to eq "Some string Name One"
expect(class.method_3).to eq ["Name Two"]
end
method_1 just adds a name to an array, and method_3 returns the array (defined in initialize method):
def method_1(name)
#array << name
end
def method_3
#array
end
I figured it would be pretty simple to interpolate #array[0] into the string and use #array.delete_at(0) to modify the array. Like so:
def method_2
p "Some string #{#array[0]}"
#array.delete_at(0)
end
But that method returns "Name One" instead of the string. If I comment out the delete code, the string returns properly but my array hasn't been modified. I've been in Ruby docs for a long time but #shift has the same issue about returning the removed item.
I'm almost certain I've over complicated this -- what am I missing?
You can collapse all this down to more conventional Ruby like this:
class MyTestClass
attr_reader :array
def initialize
#array = [ ]
end
def push(s)
#array << s
end
def special_shift
"Some string #{#array.shift}"
end
end
Then in terms of usage:
it "starts the thing and move on" do
my_thing.push("Name One")
my_thing.push("Name Two")
expect(my_thing.special_shift).to eq "Some string Name One"
expect(my_thing.array).to eq ["Name Two"]
end
Using names like push and shift which are consistent with Ruby conventions make the purpose and action of a method a lot easier to understand.
When it comes to your implementation of method_3 you forget that you can inline whatever you want inside a #{...} block, even methods that modify things. The p method is used for display, it won't return anything. To return something you need to have it either as the last thing evaluated (implicit) or by using return (explicit).
Change method_2 to the following to get the array back
def method_2
p "Some string #{#array[0]}"
#array.delete_at(0)
#array
end
From array#delete_if on ruby-doc.org
Deletes the element at the specified index, returning that element, or nil if the index is out of range.
Alternatively use object#tapwhich returns self
#array = [1,2,3,4]
#=> [1, 2, 3, 4]
#array.tap {|arr| arr.delete_at(0)}
#=> [2, 3, 4]

Intersection of an arbitrary number of arrays

I need a function which takes an array of arrays as its argument, then returns the intersection of all the subarrays. How could I improve the following code, if at all?
class Array
def grand_intersection
if self.length > 1
filters = self[1..-1]
filters.reduce(self[0]) {|start, filter| start & filter}
else
self
end
end
end
P.S. I'm not too concerned about handling arrays whose content won't respond to #& -- the method won't be exposed to the user.
class Array
def grand_intersection
self.reduce :&
end
end
[[1,2,3,4,5], [2,3,4], [1,2,4,5]].grand_intersection
#=> [2, 4]

Define a trim method on Array that removes the first and last element

class Array
define_method(:trim) do
new_array = self.pop()
new_array = self.shift()
end
end
EDIT: What I tried (among other things)
["hi", "ho", "he"].trim()
This returns "hi".
Remove the last element. Remove the first element. But how do I get the method to return the remaining array instead of what's returned by .shift (or whatever happens to be the last instruction of the method)? Do I need another variable?
Thank you.
pop() and shift() will modify the array directly. You just need to tell the method to return self
class Array
define_method(:trim) do
self.pop()
self.shift()
self
end
end
EDIT : as this method can be dangerous, I suggest you define both trim! and trim. Where trim! will modify the array directly and trim return a copy of the array, trimmed
class Array
def trim!
self.pop()
self.shift()
self
end
def trim
self.dup.trim!
end
end
You can use range when accessing array elements, like that
ary = [1, 2, 3, 4]; ary[1..-2] #=> [2, 3]
So going back to the method, it can be:
class Array
def trim
self[1..-2]
end
end
[EDIT]: to avoid returning nil for empty arrays:
class Array
def trim
self[1..-2] || []
end
end

Define a next_in_line method on Array that takes the element at the beginning of the array and puts it at the end

Here's the instructions on what I'm supposed to do.
Define a next_in_line method on Array that takes the element at the beginning of the array and puts it at the end. Hint: remember, Array#shift removes the first element, and Array#push adds an element to the end.
I've tried a dozen variations but nothing seems to work. Here's what I thought would work:
class Array
define_method(:next_in_line) do
new_array = self.shift()
new_array = new_array.push()
end
end
Pardon my non-programmer-speak syntax, but here's what I thought I was doing:
Define the class of the method (array).
Define the method (next in line)
The third line removes the first element of the array
the fourth line pushes the removed element to the end.
Then I type: ["hi", "hello", "goodbye"].next_in_line()
Here's the error message I get when I try it:
NoMethodError: undefined method 'push' for "hi":String
Why doesn't my code work?
The error is because: when called without argument, self.shift returns the element, not an array.
To fix the error, use this:
class Array
def next_in_line
return self if empty?
push shift
end
end
["hi", "hello", "goodbye"].next_in_line
# => ["hello", "goodbye", "hi"]
Note that there's a built-in Array#rotate.
As alternative solution I will do something like:
class Array
def next_in_line
self.rotate(1)
end
# If you want to reverse(make Last element as First)
def prev_in_line
self.rotate(-1)
end
end
array = ["hi", "hello", "goodbye"]
> array.next_in_line
#=> ["hello", "goodbye", "hi"]
> array.prev_in_line
#=> ["goodbye", "hi", "hello"]
This works:
class Array
def next_in_line
push(self.shift())
end
end
You don't need to use define_method to define this instance method. define_method is great for metaprogramming, but you don't need it here.
Here's how to make the code work with define_method for your education purposes:
class Array
define_method(:next_in_line) do
push shift
end
end

Ruby Iterating through multidimensional arrays

I just wanna know if there are other good ways to solve problems like the following:
p [1,[2,3,"hi",[[[[2,"ex","bye"]]]]]].count_type(String)
# => 3
So our goal is to count the types in a multidimensional Array but, as i said, problems like this, not just this problem. The general problem is that we get multidimensional Arrays and then we need to search for types or Arrays which have a minimum index of 2 or some other conditions. I am sorry for my bad language usage and hope you get the point.
I know that recursive methods work. But is there any other way with recursive or non recursive implementation?
I use the following:
def count_type(type)
counter = 0
self.each { |elem|
if elem.is_a?(type)
counter +=1
end
if elem.is_a?(Array)
counter += elem.method(type)
end
}
end
return counter
I know that the part with elem.is_a?(type) differs and depends on what you are asking for.
I forgot to tell you that it is forbidden to use flatten and my goal is not adding a new method to class Array but to learn new ways to solve the above explained problems.
I'd try and make this more in line stylistically with the Ruby core functions especially if you're intending to add it to Array:
class Array
def deep_count(*args, &block)
count = self.count(*args, &block)
each do |e|
count += e.deep_count(*args, &block) if e.is_a?(Array)
end
count
end
end
[1,[2,3,"hi",[[[[2,"ex","bye"]]]]]].deep_count {|v| v.is_a?(String)}
# => 3
[1,[2,3,"hi",[[[[2,"ex","bye"]]]]]].deep_count(2)
# => 2
Update: Version without patching core Array:
def array_deep_count(array, *args, &block)
count = array.count(*args, &block)
array.each do |e|
count += e.deep_count(*args, &block) if e.is_a?(Array)
end
count
end
This largely involves swapping self with an argument.

Resources