Ruby Iterating through multidimensional arrays - 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.

Related

looping through nested arrays and accessing elements RUBY

I want to iterate through a nested array and operate on each element, so square each number in this case. I know how to do it for a normal array but for some reason i am getting undefined map method when include deeper iteration to access the inner arrays. Am a newbie too so if you could explain how it works that would help my learning. Thanks
def SquareArrayElements(n)
n.each do |number|
number.map do |num|
squared_nums = num * num
end
end
squared_nums
end
SquareArrayElements([10,[[10],10],[10]])
Since you've said in the comments that:
i want to square the 10s and return them to one array =>[100,100,100,100].
I assume that you don't need to preserve the original arrays and just want one array with the squared results. If that's the case you can #flatten the array and then square the numbers:
def square_array(arr)
arr.flatten.map { |e| e ** 2 }
end
square_array([10,[[10],10],[10]])
#=> [100, 100, 100, 100]
You only need to call map on the arrays, not on the integers. That's why you're getting an undefined map method, integers don't support that method.
Try
def square_array(element)
if element.responds_to?(:map)
element.map{|m| square_array(m)}
else
element * element
end
end

How to collapse a multi-dimensional array of hashes in Ruby?

Background:
Hey all, I am experimenting with external APIs and am trying to pull in all of the followers of a User from a site and apply some sorting.
I have refactored a lot of the code, HOWEVER, there is one part that is giving me a really tough time. I am convinced there is an easier way to implement this than what I have included and would be really grateful on any tips to do this in a much more eloquent way.
My goal is simple. I want to collapse an array of arrays of hashes (I hope that is the correct way to explain it) into one array of hashes.
Problem Description:
I have an array named f_collectionswhich has 5 elements. Each element is an array of size 200. Each sub-element of these arrays is a hash of about 10 key-value pairs. My best representation of this is as follows:
f_collections = [ collection1, collection2, ..., collection5 ]
collection1 = [ hash1, hash2, ..., hash200]
hash1 = { user_id: 1, user_name: "bob", ...}
I am trying to collapse this multi-dimensional array into one array of hashes. Since there are five collection arrays, this means the results array would have 1000 elements - all of which would be hashes.
followers = [hash1, hash2, ..., hash1000]
Code (i.e. my attempt which I do not want to keep):
I have gotten this to work with a very ugly piece of code (see below), with nested if statements, blocks, for loops, etc... This thing is a nightmare to read and I have tried my hardest to research ways to do this in a simpler way, I just cannot figure out how. I have tried flatten but it doesn't seem to work.
I am mostly just including this code to show I have tried very hard to solve this problem, and while yes I solved it, there must be a better way!
Note: I have simplified some variables to integers in the code below to make it more readable.
for n in 1..5 do
if n < 5
(0..199).each do |j|
if n == 1
nj = j
else
nj = (n - 1) * 200 + j
end
#followers[nj] = #f_collections[n-1].collection[j]
end
else
(0..199).each do |jj|
njj = (4) * 200 + jj
#followers[njj] = #f_collections[n-1].collection[jj]
end
end
end
Oh... so It is not an array objects that hold collections of hashes. Kind of. Lets give it another try:
flat = f_collection.map do |col|
col.collection
end.flatten
which can be shortened (and is more performant) to:
flat = f_collection.flat_map do |col|
col.collection
end
This works because the items in the f_collection array are objects that have a collection attribute, which in turn is an array.
So it is "array of things that have an array that contains hashes"
Old Answer follows below. I leave it here for documentation purpose. It was based on the assumption that the data structure is an array of array of hashes.
Just use #flatten (or #flatten! if you want this to be "inline")
flat = f_collections.flatten
Example
sub1 = [{a: 1}, {a: 2}]
sub2 = [{a: 3}, {a: 4}]
collection = [sub1, sub2]
flat = collection.flatten # returns a new collection
puts flat #> [{:a=>1}, {:a=>2}, {:a=>3}, {:a=>4}]
# or use the "inplace"/"destructive" version
collection.flatten! # modifies existing collection
puts collection #> [{:a=>1}, {:a=>2}, {:a=>3}, {:a=>4}]
Some recommendations for your existing code:
Do not use for n in 1..5, use Ruby-Style enumeration:
["some", "values"].each do |value|
puts value
end
Like this you do not need to hardcode the length (5) of the array (did not realize you removed the variables that specify these magic numbers). If you you want to detect the last iteration you can use each_with_index:
a = ["some", "home", "rome"]
a.each_with_index do |value, index|
if index == a.length - 1
puts "Last value is #{value}"
else
puts "Values before last: #{value}"
end
end
While #flatten will solve your problem you might want to see how DIY-solution could look like:
def flatten_recursive(collection, target = [])
collection.each do |item|
if item.is_a?(Array)
flatten_recursive(item, target)
else
target << item
end
end
target
end
Or an iterative solution (that is limited to two levels):
def flatten_iterative(collection)
target = []
collection.each do |sub|
sub.each do |item|
target << item
end
end
target
end

Return unique values of an array without using `uniq`

For a challenge, I'm trying to return the unique values of an array without using uniq. This is what I have so far, which doesn't work:
def unique
unique_arr = []
input_arr.each do |word|
if word != unique_arr.last
unique_arr.push word
end
end
puts unique_arr
end
input = gets.chomp
input_arr = input.split.sort
input_arr.unique
My reasoning here was that if I sorted the array first before I iterated through it with each, I could push it to unique_arr without repetition being a possibility considering if it's a duplicate, the last value pushed would match it.
Am I tackling this the wrong way?
Yes, you are making at least two mistakes.
If you want to call it as input_arr.unique with input_arr being an array, then you have to define the method on Array. You have input_arr within your method body, which comes from nowhere.
puts in the last line of your code outputs to the terminal, but makes the method return nil, which makes it behave differently from uniq.
It can be fixed as:
class Array
def unique
unique_arr = []
each do |word|
unique_arr.push(word) unless unique_arr.last == word
end
unique_arr
end
end
A unique array? That sounds like a Set to me:
require 'set'
Set.new([1,2,3,2,3,4]).to_a
#=> [1,2,3,4]
Here's a concise way to do it that doesn't explicitly use functionality from another class but probably otherwise misses the point of the challenge:
class Array
def unique
group_by(&:itself).keys
end
end
I try this three options. Just for challenge
class Array
def unique
self.each_with_object({}) { |k, h| h[k] = k }.keys
end
def unique2
self.each_with_object([]) { |k, a| a << k unless a.include?(k) }
end
def unique3
arr = []
self.map { |k| arr << k unless arr.include?(k) }
arr
end
end
Here is one more way to do this:
uniques = a.each.with_object([]) {|el, arr| arr << el if not arr.include?(el)}
That's so easy if you see it this way:
a = [1,1,2,3,4]
h = Hash.new
a.each{|q| h[q] = q}
h.values
and this will return:
[1, 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]

Modify hashes in an array based on another array

I have two arrays like this:
a = [{'one'=>1, 'two'=>2},{'uno'=>1, 'dos'=>2}]
b = ['english', 'spanish']
I need to add a key-value pair to each hash in a to get this:
a = [{'one'=>1, 'two'=>2, 'language'=>'english'},{'uno'=>1, 'dos'=>2, 'language'=>'spanish'}]
I attempted this:
(0..a.length).each {|c| a[c]['language']=b[c]}
and it does not work. With this:
a[1]['language']=b[1]
(0..a.length).each {|c| puts c}
an error is shown:
NoMethodError (undefined method '[]=' for nil:NilClass)
How can I fix this?
a.zip(b){|h, v| h["language"] = v}
a # => [
# {"one"=>1, "two"=>2, "language"=>"english"},
# {"uno"=>1, "dos"=>2, "language"=>"spanish"}
# ]
When the each iterator over your Range reaches the last element (i.e. a.length), you will attempt to access a nonexisting element of a.
In your example, a.length is 2, so on the last iteration of your each, you will attempt to access a[2], which doesn't exist. (a only contains 2 elements wich indices 0 and 1.) a[2] evaluates to nil, so you will now attempt to call nil['language']=b[2], which is syntactic sugar for nil.[]=('language', b[2]), and since nil doesn't have a []= method, you get a NoMethodError.
The immediate fix is to not iterate off the end of a, by using an exclusive Range:
(0...a.length).each {|c| a[c]['language'] = b[c] }
By the way, the code you posted:
(0..a.length).each {|c| puts c }
should clearly have shown you that you iterate till 2 instead of 1.
That's only the immediate fix, however. The real fix is to simply never iterate over a datastructure manually. That's what iterators are for.
Something like this, where Ruby will keep track of the index for you:
a.each_with_index do |hsh, i| hsh['language'] = b[i] end
Or, without fiddling with indices at all:
a.zip(b.zip(['language'].cycle).map(&:reverse).map(&Array.method(:[])).map(&:to_h)).map {|x, y| x.merge!(y) }
[Note: this last one doesn't mutate the original Arrays and Hashes unlike the other ones.]
The problem you're having is that your (0..a.length) is inclusive. a.length = 2 so you want to modify it to be 0...a.length which is exclusive.
On a side note, you could use Array#each_with_index like this so you don't have to worry about the length and so on.
a.each_with_index do |hash, index|
hash['language'] = b[index]
end
Here is another method you could use
b.each_with_index.with_object(a) do |(lang,i),obj|
obj[i]["language"] = lang
obj
end
#=>[
{"one"=>1, "two"=>2, "language"=>"english"},
{"uno"=>1, "dos"=>2, "language"=>"spanish"}
]
What this does is creates an Enumerator for b with [element,index] then it calls with_object using a as the object. It then iterates over the Enumerator passing in each language and its index along with the a object. It then uses the index from b to find the proper index in a and adds a language key to the hash that is equal to the language.
Please know this is a destructive method where the objects in a will mutate during the process. You could make it non destructive using with_object(a.map(&:dup)) this will dup the hashes in a and the originals will remain untouched.
All that being said I think YAML would be better suited for a task like this but I am not sure what your constraints are. As an example:
yml = <<YML
-
one: 1
two: 2
language: "english"
-
uno: 1
dos: 2
language: "spanish"
YML
require 'yaml'
YAML.load(yml)
#=>[
{"one"=>1, "two"=>2, "language"=>"english"},
{"uno"=>1, "dos"=>2, "language"=>"spanish"}
]
Although using YAML I would change the structure for numbers to be more like language => Array of numbers by index e.g. {"english" => ["zero","one","two"]}. That way you can can access them like ["english"][0] #=> "zero"

Resources