Ruby Iterating Through Array for Method Names - arrays

I have an object that I want to output a bunch of methods' results from. I have an array of method names and want to iterate through them:
img = Magick::Image::read('/Users/rich/Projects/imagemagick/orig/IMG_4677.jpg')[0]
atts = "background_color base_columns base_filename base_rows bias black_point_compensation".split(' ')
atts.each do |i|
puts img.i # problem evaluating i
end
I have tried string interpolation and eval but I can't get it to realize it's a method call.
Is there a way I can apply an array as method names to an object?

Try using public_send:
atts = "upcase downcase".split
atts.each do |i|
puts 'hEllO'.public_send(i)
end
#HELLO
#hello

Related

How to find a specific value in a nested array?

I'm trying to figure out how to place a value into one of three arrays and then shuffle those arrays and have the program output the index location of the value.
Here is what I have so far:
# The purpose of this program is to randomly place the name Zac
# in one of three arrays and return the array number and position of
# Zac
A1 = ["John","Steve","Frank","Charles"]
A2 = ["Sam","Clint","Stuart","James"]
A3 = ["Vic","Jim","Bill","David"]
n = [A1,A2,A3]
name = "Zac"
def placename(title, namelist)
mix = rand(2)
namelist[mix] << title
namelist.shuffle
return namelist
end
allnames = [] << placename(name, n)
def findname(allnames, key)
allnames.each do |i|
until allnames[i].include?(key) == true
i+=1
end
location = allnames[i].find_index(key)
puts "The location and value of #{key} is #{location}"
end
end
findname(allnames, name)
At the moment I'm getting a "undefined method for Nil Class" error (no method error)
Can someone please clarify what I'm doing wrong with this or if there is a more effective way of going about this? Thanks in advance!!
Your approach assumes that in the block starting...
allnames.each do |i|
... that i will contain the index of the allnames element. This isn't true. i will contain the VALUE (contents) of the element.
What you could try as an alternative is...
allnames.each_with_index do |_value, i|
or, you can do...
allnames.each do |value|
and then replace all references to allnames[i] with value
another problem is that...
allnames = [] << placename(name, n)
puts the returned array of arrays inside ANOTHER array. I think what you want to do is..
allnames = placename(name, n)
I modified the last fewlines. I hope this is what you wanted
allnames = placename(name, n)
def findname allnames, key
r = allnames.map.with_index{|x,i|x.include?(key) ? i : p}-[p]
puts "The location of value #{key} is array number #{r[0]} and item number #{allnames[r[0]].index(key)}"
end
findname(allnames, name)
Edit: Randomization
To get randomized array number and item number you have to do the following
def placename(title, namelist)
mix = rand(3) # Since the number of arrays (nested within) is 3 we can use 3 instead of 2
namelist[mix] << title
namelist.map!{|x|x.shuffle}.shuffle! # Shuffling each item and the whole array in place.
return namelist
end
Assuming you want to modify the array in place, I'd do it like this:
# insert name into random subarray
def insert_name name
subarray_idx = rand #name_arrays.size
subarray = #name_arrays[subarray_idx]
insertion_idx = rand subarray.size
#name_arrays[subarray_idx].insert insertion_idx, name
sprintf '"%s" inserted at #name_arrays[%d][%d]',
name, subarray_idx, insertion_idx
end
# define starting array, then print & return the
# message for further parsing if needed
#name_arrays = [
%w[John Steve Frank Charles],
%w[Sam Clint Stuart James],
%w[Vic Jim Bill David],
]
p(insert_name 'Zac')
This has a few benefits:
You can inspect #name_arrays to validate that things look the way you expect.
The message can be parsed with String#scan if desired.
You can modify #insert_name to return your indexes, rather than having to search for the name directly.
If you don't capture the insertion index as a return value, or don't want to parse it from your message String, you can search for it by leveraging Enumerable#each_with_index and Array#index. For example:
# for demonstration only, set this so you can get the same
# results since the insertion index was randomized
#name_arrays =
[["John", "Steve", "Frank", "Charles"],
["Sam", "Clint", "Stuart", "James"],
["Vic", "Jim", "Zac", "Bill", "David"]]
# return indices of nested match
def find_name_idx name
#name_arrays.each_with_index
.map { [_2, _1.index(name)] }
.reject { _1.any? nil }
.pop
end
# use Array#dig to retrieve item at nested index
#name_arrays.dig *find_name_idx('Zac')

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

How can I enumerate a property in an array of objects, modify it, and join them in a string in ruby?

How can I enumerate a property in an array of objects, modify it, and join them in a string? I know there must be Ruby features that make this easier, but I have been having trouble understanding them.
How can I accomplish this more efficiently (less code/easier to read)?
arr = Array.new
someArrayOfObjects.map{
|obj| arr.push(obj.someProp + '-addThisStr')
}
puts arr.sort.uniq.join(', ')
#end result:
somepropA-addThisStr,somepropB-addThisStr, etc
You basically have it but you can skip the Array.new and arr.push:
result = someArrayOfObjects.map do |obj|
obj.someProp + '-addThisStr'
end.sort.uniq.join(", ")
puts result

Why does Ruby's each_with_object drop data that was appended with the += operator to the array memo?

I'm stumped by the following Ruby behaviour:
result = [1,2,3].each_with_object([]) do |elem, memo|
memo << [elem]
end
puts result.to_s
# => [[1], [2], [3]]
The above code works how I would expect it to work. However, the code below just seems to drop the numbers I'm trying to append to the array.
result = [1,2,3].each_with_object([]) do |elem, memo|
memo += [elem]
end
puts result.to_s
# => []
Could anyone explain to me what's going on here and how I am supposed to use the += operator with each_with_object in the above context?
memo is local variable, that points to an array object. these loop variables are set at each iteration.
In the first example, you change this array object.
In the second example, you override the local variable with a new array. Because memo += [elem] is just a shorthand for memo = momo + [elem]
The old array stays empty.
Very often you better use inject instead of each_with_object. With inject, the new memo variable is set with the result of the block, so you can use non-destructive functions.
[1,2,3].inject([]) do |memo, elem|
memo + [elem]
end
According to docs, += – concatenation – returns a new array built by concatenating the two arrays together to produce a third array.
Thus, on every iteration the block result is set to a new array, that is reset back to the empty array that you've passed to each_with_object method in the next iteration. Consider the following demonstration.

How can I remove a numerical extension from an array of filenames?

I want to remove the last 11 characters of strings inside of an array. The array is:
["cool.mp3?3829483927", "wow.mp3?3872947629", "woa.mp3?8392748308"]
I want to convert the strings to this:
["cool.mp3", "wow.mp3", "woa.mp3"]
Is there a method specifically for this in Ruby? I know of chop and chomp, but nothing that can access each string in an array.
TL;DR
There are lots of ways to transform your string, including #slice, #split, #sub, and #partition to name a few. What you're really missing is the Array#map method, which applies a method or block to each element of an array.
Partition Your Filenames
One way to modify your array elements is to map the Enumerable#partition method onto each element, which splits your filenames into an array of components. Ordinarily, this would return an array of arrays where each partitioned string is a sub-array, but you can have the #map block return just the components you want. In this case, what you want is the first element of each partitioned array.
This may sound complicated, but it's actually very simple. For example:
files = ['cool.mp3?3829483927', 'wow.mp3?3872947629', 'woa.mp3?8392748308']
files.map { |filename| filename.partition('?').first }
#=> ["cool.mp3", "wow.mp3", "woa.mp3"]
A Minified Version
If you value compactness over readability, you can get the same result as the solution above with:
files = %w(cool.mp3?3829483927 wow.mp3?3872947629 woa.mp3?8392748308)
files.map { |f| f.partition(??)[0] }
#=> ["cool.mp3", "wow.mp3", "woa.mp3"]
If you want what is before the first '?', do this :
arr.map{|thing| thing.split('?')[0]}
If it's always 11 chars you can do:
arr=["cool.mp3?3829483927","wow.mp3?3872947629", "woa.mp3?8392748308"]
new=arr.map{|thing| thing[0...-11]}
String::slice can take a regex as an argument, so you could extract the names that you want (rather than dropping the last 11 characters):
arr = ["cool.mp3?3829483927","wow.mp3?3872947629", "woa.mp3?8392748308"]
arr.map! {|x| x.slice(/\w+\.mp3/) }
#=> ["cool.mp3", "wow.mp3", "woa.mp3"]
If arr is your array of strings:
arr.map { |s| s[/[^?]+/] }
# => ["cool.mp3", "wow.mp3", "woa.mp3"]
The regular expression /[^?]+/ matches one or more characters that are not (^ at the beginning of the character class) question marks. This uses the methods Array#map and String#[].

Resources