fixing horrible formatting when adding to ruby array from params - arrays

I have some code that adds to a session array like so:
if policy_session[:modalities] #array exists just add new value to it
policy_session[:modalities] << [params[:modality], policy_session[:mode_list]]
else #the array does't exist yet, so create and add first one.
policy_session[:modalities] = [params[:modality], policy_session[:mode_list]]
but this produces horrible formatting on my :modalities array. It looks like this:
>> policy_session[:modalities]
>># [["var_1"], "1",[["var_2"], ["2"]], [["var_3"], ["1"]]]
Which is a total pain to try and iterate over later in my program.
I have tried a bunch of different things, but haven't come up with anything that really looks better then this.
How do I create and then add to the array such that my output will be readable? And all formatted the same!
I would like something like this:
>>policy_session[:modalities]
>># [["var_1", "1"], ["var_2", "2"], ["var_3", "1"]]

Something like this...
policy_session[:modalities] ||= [] # set it to an empty array if nil
policy_session[:modalities] << [params[:modality], policy_session[:mode_list]]
Edit: To get rid of the extra []'s...
policy_session[:modalities] ||= [] # set it to an empty array if nil
policy_session[:modalities] << [params[:modality], policy_session[:mode_list]].flatten

Related

Using flatten! on an array of arrays not working

I am building a script that takes in a column from a CSV that can contain 0 or more ID numbers. I have created an array of the column, however, since some cells have no ID number and some have multiple,I have an array of arrays.
I want to create an array where each element is a single ID (i.e split the IDs from each element in the array to a single element).
Here is my code so far:
require 'csv'
class MetadataTherapyParser
def initialize (csv)
#csv = csv
end
def parse_csv
therapy_array = []
CSV.foreach(#csv) do |csv_row|
therapy_array << csv_row[0]
end
therapy_array
end
def parse_therapies(therapy_array)
parsed_therapy_array = therapy_array.flatten!
end
end
metadata_parse = MetadataTherapyParser.new ("my_path.csv")
therapy_array = metadata_parse.parse_csv
metadata_parse.parse_therapies(therapy_array)
p therapy_array
However, the output is still an array of arrays. I am thinking it may have something to do with nil values? I have tried looking for answers online to no avail.
If you could give me some advice as how to fix this problem, it would be greatly appreciated!
Thank you in advance.
EDIT
I have posted a snippet of my output below. It still appears to be a nested array.
[nil, nil, "57e923a0f5c3c85c9200052b, 58b828f4f5c3c806490046a6", "57e923a0f5c3c85c9200052b, 4ffaf15af758862fb10155e3, 58b828f4f5c3c806490046a6", "57e923a0f5c3c85c9200052b, 4ffaf15af758862fb10155e3, 58b828f4f5c3c806490046a6", nil, nil, nil, nil, nil, "5f9176e50cf19216d6da9289", "6082f6bd0cf19225863fc985", "6082f6fd0cf192258d3fce0e", "6082f69e0cf19225ac3fc551", "6082f6a60cf19225a23fd3e4, 6082f6d30cf192258d3fce0a, 6082f7fa0cf19225953fc77c"]
You say you have "an array of arrays" but your example array is "57e923a0f5c3c85c9200052b, 4ffaf15af758862fb10155e3, 58b828f4f5c3c806490046a6" ... that's not an array. That's a string. You probably want to split strings that have commas into separate array elements.
So instead of
therapy_array << csv_row[0]
try instead
therapy_array << csv_row[0].to_s.split(',').map(&:strip)
the flatten is working perfectly. the issue you are having is that you have lots of strings in your output, with commas in them.
having not got a copy of your CSV, I'm going to assume that it has been parsed correctly, and that you do want to keep the contents of the first cell as it is:
def parse_therapies(therapy_array)
parsed_therapy_array = therapy_array.map { |x| x && x.split(/,/) }.flatten.compact
therapy_array.replace(parsed_therapy_array)
end
this will also remove all the nil elements, assuming you don't want them, using the compact procedure.

Ruby, print all the hashes "subfield" in one row

I have a JSON array structured like this:
{"elements":[{"ECL001":{"description":"First Element", "max_level":3, "size":10}},{"ECL002":{"description":"Second Element", "max_level":4, "size":1}}]}
I'm parsing my structure and then I print data if condition are satisfied.
require 'json'
x = JSON.parse(File.open('data_elements.dat').read)
elements = x["elements"]
elements.each do |elem_specific|
elem_specific.each do |id, data|
if data['max_level'] > 3
puts "#{data['description']}, #{data['max_level']}, #{data[size]}"
end
end
end
It's work correctly, but is there a faster solution to prints data?
I mean ... Is possible replace this
puts "#{data['description']}, #{data['max_level']}, #{data[size]}"
with something like
puts "#{data[*ALL]}"
I solved it!
I found this:
puts "#{data.values}" # Print all Values
puts "#{data.keys}" # Print all Keys

Create a new array with common string in all arrays in ruby

Lets say we have below array
arrays=[["a","b", "c"],["b","g","c"],["b","c","g"]]
To find common array fields we can do arrays[0] & arrays[1] & arrays[2] which will return ["b","c"] in this case. This works fine. But how can we do the same when the array count is not predictable?
My initial thought is doing something like a loop like this.
array_count.times do |index|
#but this way how can I achieve same above or any better approach???
end
Thank you.
Use Reduce method
result=arrays.reduce do |x,y|
x & y
end
p result
output
["b", "c"]
Update
Another short way would be
arrays.reduce(:&)

Ruby array intersection returning a blank array

I'm very new to Ruby so please go easy on me. I have this small function that doesn't want to perform an intersection command. If I go into irb and enter the arrays then set the intersection command like: third_array = array1 & array2, third_array returns the common element. But when I run this snippet through irb, it just returns [ ]. Any suggestions?
class String
define_method(:antigrams) do |word2|
array1 = []
array2 = []
array1.push(self.split(""))
array2.push(word2.split(""))
third_array = array1 & array2
third_array
end
end
After looking at what you have, I think your code boils down to this:
class String
def antigrams(word)
self.chars & word.chars
end
end
"flurry".antigrams("flagrant")
# => ["f", "l", "r"]
If you're calling split('') on a word that's effectively the same as chars, though a lot less efficient. Another mistake was pushing a whole array into an array, which creates a nested array of the form [ [ 'f', 'l', ... ] ]. Since the two resulting array-of-arrays have nothing in common, their inner arrays are different, the & operation returns an empty array.
What you meant was to concatenate the one array to the other, something that can be done with += for example.
Whenever you're curious what's happening, either use irb to try out chunks of code, or p to debug at different points in your method.

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