Flattening an inner array - arrays

I'm trying to make an area that outputs the following: [#month, #monthly_count] so that the complete output looks like this: [["January", 0], ["February", 0], ["March", 0], ["April", 2], ["May", 3], ["June", 19], ["July", 0], ["August", 0], ["September", 0], ["October", 0], ["November", 0], ["December", 0]]
My code is:
#months = [["January"],["February"],["March"],["April"],["May"],["June"],["July"],["August"],["September"],["October"],["November"],["December"]]
#monthly_count = [[0], [0], [0], [2], [3], [19], [0], [0], [0], [0], [0], [0]]
#monthly_activity_count = Array.new(12){Array.new}
i = 0
12.times do |i|
#monthly_activity_count[i] << #months[i]
#monthly_activity_count[i] << #monthly_count[i]
#monthly_activity_count[i].flatten
i += 1
end
But it outputs:
[[["January"], [0]], [["February"], [0]], [["March"], [0]], [["April"], [2]], [["May"], [3]], [["June"], [19]], [["July"], [0]], [["August"], [0]], [["September"], [0]], [["October"], [0]], [["November"], [0]], [["December"], [0]]]
I tried to use array.flatten within the iterator to flatten each individual array while keeping the array bounds around each month, but this didn't work. How can I make the array correctly?

Try by doing flatten! in your code,
12.times do |i|
#monthly_activity_count[i] << #months[i]
#monthly_activity_count[i] << #monthly_count[i]
#monthly_activity_count[i].flatten!
i += 1
end

flatten(level) works for you.
[[["January"], [0]], [["February"], [0]], [["March"], [0]], [["April"], [2]], [["May"], [3]], [["June"], [19]], [["July"], [0]], [["August"], [0]], [["September"], [0]], [["October"], [0]], [["November"], [0]], [["December"], [0]]].flatten(2)
For more information http://apidock.com/ruby/Array/flatten

#monthly_activity_count = #months.flatten.zip(#monthly_count.flatten)

If I understand what you're attempting to get at, you can probably start with a slightly simpler setup:
months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
From there, you can use either of the following lines to produce the desired array (they're exactly equivalent):
months.map.with_index { |month, index| [month, index] }
months.collect.with_index { |month, index| [month, index] }
Both of those lines will iterate over your months array, passing the month name and its index into the block. The code block (the portion surrounded by { and } returns an array containing just the month name and index — and all of those little arrays are grouped into a containing array because you're using map (or collect).
However, I can't quite imagine why you'd need such a structure. If you know the index of the month you want, you can get its name like so:
months[index]
If you know the month name and want to know its index, you can find out like so:
month_name = "March"
index = months.index { |month| month == month_name }
The index in months.index will iterate over all the months, passing each one into the code block. It expects you to supply a code block that will return true when you've located the object you want the index of. So the code block (month == month_name) is setup to match the name of the month passed in with the name stored in your month_name variable. See this article for more info on Ruby code blocks.

You can do it like so:
# only using some of the array to make the example easier to read
#months = [["January"],["February"],["March"],["April"],["May"]]
#monthly_count = [[0], [0], [0], [2], [3]]
## First Method
zipped = #months.zip(#monthly_count)
=> [[["January"], [0]], [["February"], [0]], [["March"], [0]], [["April"], [2]], [["May"], [3]]]
#monthly_activity_count = zipped.each { |pair| pair.flatten! }
=> [["January", 0], ["February", 0], ["March", 0], ["April", 2], ["May", 3]]
# could also do it as a one liner
#months.zip(#monthly_count).each { |pair| pair.flatten! }
# the flatten! in the line above is not bad, just a warning to help you understand
# the result. The difference between `flatten` and `flatten!` is that `flatten`
# will create a new array to hold the result, whereas `flatten!` will
# modify the array you call it on.
## Second Method
#months.flatten.zip(#monthly_count.flatten)
=> [["January", 0], ["February", 0], ["March", 0], ["April", 2], ["May", 3]]
## Ideal Method
# if you can get your months and monthly_count data as simple arrays, eg ["January",
# "February", ...] then you can remove the flattens in the previous line, giving:
#months.zip(#monthly_count)
See http://ruby-doc.org/core-1.9.3/Array.html#method-i-zip for the docs of the zip method.
See http://dablog.rubypal.com/2007/8/15/bang-methods-or-danger-will-rubyist for an explanation of ! (bang) methods in ruby.

Related

Sort an array of arrays by the number of same occurencies in Ruby

This question is different from this one.
I have an array of arrays of AR items looking something like:
[[1,2,3], [4,5,6], [7,8,9], [7,8,9], [1,2,3], [7,8,9]]
I would like to sort it by number of same occurences of the second array:
[[7,8,9], [1,2,3], [4,5,6]]
My real data are more complexes, looking something like:
raw_data = {}
raw_data[:grapers] = []
suggested_data = {}
suggested_data[:grapers] = []
varietals = []
similar_vintage.varietals.each do |varietal|
# sub_array
varietals << Graper.new(:name => varietal.grape.name, :grape_id => varietal.grape_id, :percent => varietal.percent)
end
raw_data[:grapers] << varietals
So, I want to sort raw_data[:grapers] by the max occurrencies of each varietals array comparing this value: grape_id inside them.
When I need to sort a classical array of data by max occurencies I do that:
grapers_with_frequency = raw_data[:grapers].inject(Hash.new(0)) { |h,v| h[v] += 1; h }
suggested_data[:grapers] << raw_data[:grapers].max_by { |v| grapers_with_frequency[v] }
This code doesn't work cos there are sub arrays there, including AR models that I need to analyze.
Possible solution:
array.group_by(&:itself) # grouping
.sort_by {|k, v| -v.size } # sorting
.map(&:first) # optional step, depends on your real data
#=> [[7, 8, 9], [1, 2, 3], [4, 5, 6]]
I recommend you take a look at the Ruby documentation for the sort_by method. It allows you to sort an array using anything associated with the elements, rather than the values of the elements.
my_array.sort_by { |elem| -my_array.count(elem) }.uniq
=> [[7, 8, 9], [1, 2, 3], [4, 5, 6]]
This example sorts by the count of each element in the original array. This is preceded with a minus so that the elements with the highest count are first. The uniq is to only have one instance of each element in the final result.
You can include anything you like in the sort_by block.
As Ilya has pointed out, having my_array.count(elem) in each iteration will be costlier than using group_by beforehand. This may or may not be an issue for you.
arr = [[1,2,3], [4,5,6], [7,8,9], [7,8,9], [1,2,3], [7,8,9]]
arr.each_with_object(Hash.new(0)) { |a,h| h[a] += 1 }.
sort_by(&:last).
reverse.
map(&:first)
#=> [[7.8.9]. [1,2,3], [4,5,6]]
This uses the form of Hash::new that takes an argument (here 0) that is the hash's default value.

Using self.dup, but failing rspec test to not modify original array

I'm creating a method to transpose square 2-d arrays. My method passes every test, except the "does not modify original array" one. I'm only working on the duped array, so I'm confused on why the test is failing.
Code:
class Array
def my_transpose
orig_arr = self.dup; array = []
orig_arr[0].length.times do
temp_arr = []
orig_arr.each { |arr| temp_arr << arr.shift }
array << temp_arr
end
array
end
end
RSpec:
describe Array do
describe "#my_transpose" do
let(:arr) { [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
] }
let(:small_arr) { [
[1, 2],
[3, 4]
] }
it "transposes a small matrix" do
expect(small_arr.my_transpose).to eq([
[1, 3],
[2, 4]
])
end
it "transposes a larger matrix" do
expect(arr.my_transpose).to eq([
[1, 4, 7],
[2, 5, 8],
[3, 6, 9]
])
end
it "should not modify the original array" do
small_arr.my_transpose
expect(small_arr).to eq([
[1, 2],
[3, 4]
])
end
it "should not call the built-in #transpose method" do
expect(arr).not_to receive(:transpose)
arr.my_transpose
end
end
end
Output:
7) Array#my_transpose should not modify the original array
Failure/Error: expect(small_arr).to eq([
expected: [[1, 2], [3, 4]]
got: [[], []]
(compared using ==)
# ./spec/00_array_extensions_spec.rb:123:in `block (3 levels) in <top (required)>'
When you call dup on an array, it only duplicates the array itself; the array's contents are not also duplicated. So, for example:
a = [[1,2],[3,4]]
b = a.dup
a.object_id == b.object_id # => false
a[0].object_id == b[0].object_id # => true
Thus, modifications to a itself are not reflected in b (and vice versa), but modifications in the elements of a are reflected in b, because those elements are the same objects.
That being the case, the problem crops up here:
orig_arr.each { |arr| temp_arr << arr.shift }
arr is an element of orig_arr, but it is also an element of self. If you did something like remove it from orig_arr, you would not also remove it from self, but if you change it, it's changed, no matter how you are accessing it, and as it turns out, Array#shift is a destructive operation.
Probably the smallest change you could make to your code to make it work as you expect would be to use each_with_index, and then use the index into arr, rather than calling arr.shift, so:
orig_arr.each_with_index { |arr,i| temp_arr << arr[i] }
In fact, though, once you're doing that, you're not doing any destructive operations at all and you don't need orig_arr, you can just use self.
The original array isn’t being modified, but the arrays within it are, as dup is a shallow clone.
xs = [[1,2],[3,4]]
ids = xs.map(&:object_id)
xs.my_transpose
ids == xs.map(&:object_id) #=> true
Since shift is a mutating operation (being performed on the nested array elements), you need to dup the elements within the array as well, e.g.
orig_arr = dup.map(&:dup)
With this modification, your test should pass.

Turn array into array of arrays following structure of another array

I would like to turn an array into an array of arrays following another array of arrays. I'm not sure how to do this, here are the arrays:
orig_array = [[0,1],[4],[3],[],[3,2,6],[]]
my_array = [2,0,1,3,3,4,5]
wanted_array = [[2,0],[1],[3],[],[3,4,5],[]]
I would like to keep the empty arrays.
Thanks
Get the lengths of each element in orig_array, perform cumumlative summations along the length values to give us the indices at which my_array needs to be split and finally use np.split to actually perform the splitting. Thus, the implementation would look something like this -
lens = [len(item) for item in orig_array]
out = np.split(my_array,np.cumsum(lens))[:-1]
Sample run -
In [72]: orig_array = np.array([[0,1],[4],[3],[],[3,2,6],[]])
...: my_array = np.array([2,0,1,3,3,4,5])
...:
In [73]: lens = [len(item) for item in orig_array]
...: out = np.split(my_array,np.cumsum(lens))[:-1]
...:
In [74]: out
Out[74]:
[array([2, 0]),
array([1]),
array([3]),
array([], dtype=int64),
array([3, 4, 5]),
array([], dtype=int64)]
def do(format, values):
if type(format) == list:
return [do(v, values) for v in format]
else:
return values.pop(0)
print do(orig_array, my_array)
Note: this destroys the array where the values come from.
You could do the following:
import copy
def reflect_array(orig_array, order):
wanted_array = copy.deepcopy(orig_array)
for i, part_list in enumerate(orig_array):
for j, _ in enumerate(part_list):
wanted_array[i][j] = order.pop()
return wanted_array
Test run:
orig_array = [[0,1],[4],[3],[],[3,2,6],[]]
my_array = [2,0,1,3,3,4,5]
print reflect_array(orig_array, my_array)
# [[2, 0], [1], [3], [], [3, 4, 5], []]
In [858]: my_array = [2,0,1,3,3,4,5]
In [859]: [[my_array.pop(0) for _ in range(len(x))] for x in orig_array]
Out[859]: [[2, 0], [1], [3], [], [3, 4, 5], []]
Use b=my_array[:] if you don't want to change my_array.
This operates on the same principle as #karoly's answer; just more direct because it assumes only one level of nesting.

Sort a hash of arrays by one of the arrays in ruby

In Ruby, is there a short and sweet way to sort this hash of arrays by score descending:
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
so it looks like this?:
scored = {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
I couldnt find an example where I can sort arrays within a hash like this? I could do this with some nasty code but I feel like there should be a 1 or 2 liner that does it?
You could use sort_by
scored = {:id=>[1, 2, 3], :score=>[8.3, 5, 10]}
scored.tap do |s|
s[:id] = s[:id].sort_by.with_index{ |a, i| -s[:score][i] }
s[:score] = s[:score].sort_by{ |a| -a }
end
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
order = scored[:score].each_with_index.sort_by(&:first).map(&:last).reverse
#=> [2,0,1]
scored.update(scored) { |_,a| a.values_at *order }
#=> {:id=>[3, 1, 2], :score=>[10, 8.3, 5]}
If scored is to not to be mutated, replace update with merge.
Some points:
Computing order makes it easy for the reader to understand what's going on.
The second line uses the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged (which here is all keys). This is a convenient way to modify hash values (generally), in part because the new hash is returned.
I sorted then reversed, rather than sorted by negated values, to make the method more rubust. (That is, the elements of the arrays that are the values can be from any class that implements <=>).
With Ruby 2.2+, another way to sort an array arr in descending order is to use Enumerable#max_by: arr.max_by(arr.size).to_a.
The first line could be replaced with:
arr = scored[:score]
order = arr.each_index.sort_by { |i| arr[i] }.reverse
#=> [2,0,1]
Here is one possible solution. It has an intermediate step, where it utilizes a zipped version of the scores object, but produces the correct output:
s = scored.values.inject(&:zip).sort_by(&:last).reverse
#=> [[3, 10], [1, 8.3], [2, 5]]
result = { id: s.map(&:first), score: s.map(&:last) }
#=> { :id => [3, 1, 2], :score => [10, 8.3, 5] }

Deleting an array from a multidimensional array in ruby

How would this get done? Assume I have the following
arr = [[test, 0, 0, 0], [apples, 0, 9, 8]]
I know I would do something like:
def delete_me(item)
arr.each do |a|
if a[0] == item
#delete the array containing test
end
end
end
delete_me('test')
As far as I can see you can only do: a.remove() but that leaves me with a empty [],m I don't want that, I want it completely gone.
You can use delete_if and match the first term to your argument:
arr = [['test', 0, 0, 0], ['apples', 0, 9, 8]]
def delete_me(array, term)
array.delete_if {|x, *_| x == term }
end
(I've included the array as an argument as well, as the execution context is not clear from your post).
Following up on #iamnotmaynard's suggestion:
arr.delete_if { |a| a[0] == 'test' }
assoc.
arr.delete(arr.assoc("test"))
I had a similar need to remove one or more columns that matched a text pattern.
col_to_delete = 'test'
arr = [['test','apples','pears'],[2,3,5],[3,6,8],[1,3,1]]
arr.transpose.collect{|a| a if (a[0] != col_to_delete)}.reject(&:nil?).transpose
=> [["apples", "pears"], [3, 5], [6, 8], [3, 1]]

Resources