Getting a "NoSuchMethodError" when trying to create an array of arrays - arrays

I'm using Ruby 2.4. I'm having trouble creating an array of arrays. I have this
data_cols = [[], []]
lines.each do |line|
parts = [0, *shared_space_indexes, line.size].each_cons(2).map { |a, b| line[a...b].strip }
parts.each_with_index do |part, index|
data_cols[index][data_cols[index] ? data_cols[index].size : 0] = part
end
end
But I get a "NoMethodError: undefined method `[]=' for nil:NilClass" error on the "data_cols[index][data_cols[index] ? data_cols[index].size : 0] = part" line. What I'm trying to do is for each parts array, push each item in "parts" onto its own array correspoding to the index of that element in parts. So, for instance, if the first iteration of the loop has parts equal to
[1, 5, 8, 12]
I would have a data_cols array that looks like
[[1], [5], [8], [12]]
and if the next iteration of the loop had a parts array that looked like
[19, 20, 21, 22]
the data_cols array would then be changed to
[[1, 19], [5, 20], [8, 21], [12, 22]]

It's hard to test without knowing what lines and shared_space_indexes look like.
2 columns → a matrix
zip
For the second part of your question you can use :
[1, 5, 8, 12].zip([19, 20, 21, 22])
#=> [[1, 19], [5, 20], [8, 21], [12, 22]]
transpose
[[1, 5, 8, 12],[19, 20, 21, 22]].transpose
#=> [[1, 19], [5, 20], [8, 21], [12, 22]]
With transpose, both arrays need to have the same size.
3 columns → matrix
array1 = [1, 5, 8, 12]
array2 = [19, 20, 21, 22]
array3 = [99, 88, 77, 66]
p [array1, array2, array3].transpose
#=> [[1, 19, 99], [5, 20, 88], [8, 21, 77], [12, 22, 66]]
columns → rows
If you have an array of arrays :
arrays = [
[1, 5, 8, 12],
[19, 20, 21, 22],
[99, 88, 77, 66]
]
p arrays.transpose
#=> [[1, 19, 99], [5, 20, 88], [8, 21, 77], [12, 22, 66]]
So in your case, I'd just concentrate on creating an array of arrays with the same length, and use transpose at the end.
matrix + column → matrix
As you noticed, if you have
data_cols = [[1, 19], [5, 20], [8, 21], [12, 22]]
arr = [99, 88, 77, 66]
data_cols.zip(arr) would produce a weird half-nested array :
[[[1, 19], 99], [[5, 20], 88], [[8, 21], 77], [[12, 22], 66]]
You can use :
p data_cols.zip(arr).map(&:flatten)
#=> [[1, 19, 99], [5, 20, 88], [8, 21, 77], [12, 22, 66]]

Related

How to create a 3D list of 2D lists that vary in size(The 2D lists vary in size) in Python?

I have my dataframe like this. You can see that the column called filename indicates that this row is in a file and another row is in a different file or not. I have also created another column to count the total number of rows are in a file.
I have extracted the ymin and ymax by concatenating all of them to a list and the result is a 2D list:
y = [[4, 43], [9, 47], [76, 122], [30, 74], [10, 47], [81, 125], [84, 124], [47, 90], [1, 38],[2, 40], [2, 44], [4, 48], [5, 48], [6, 44], [8, 45], [75, 116], [73, 123], [28, 73], [39, 84], [84, 121], [2, 39],...]
Thus, this only puts all the coordinates into a list without knowing which belongs to the first file and which belongs to the second
My approach is making a 3D list like this:
y = [[[4, 43], [9, 47], [76, 122], [30, 74], [10, 47], [81, 125], [84, 124], [47, 90], [1, 38]],[[2, 40], [2, 44], [4, 48], [5, 48], [6, 44], [8, 45], [75, 116], [73, 123], [28, 73], [39, 84], [84, 121], [2, 39]],...]
You can see from [4,43] to [1,38] that they are in the same file.
You can also see from [2,40] to [2,39] that they are also in the same file.
Here is my current attempt
def get_y_coordinate(count):
"""
Create a 3D list of y_coordinates that can distinguish which list is in a file which list belongs to another file
:param: count - the list taken from the column "count" from the dataframe
"""
c = 2 # Number of chunks to make
fi_y= lambda y, c: [y[i:i+c] for i in range(0, len(y), c)] # Making y into chunks of 2 ex: from [4,43,9,47] to [4,43],[9,74]
y = fi_y(y,c) # Now y is [[4, 43], [9, 47],...]]
# This is my current approach, I create a new list called bigy.
bigy = []
current = 0
for i in count:
if current != i:
bigy.append([y[j] for j in range(current, i+current)])
current = i
return bigy
>> bigy = [[[4, 43], [9, 47], [76, 122], [30, 74], [10, 47], [81, 125], [84, 124], [47, 90], [1, 38]],[[2, 40], [2, 44], [4, 48], [5, 48], [6, 44], [8, 45], [75, 116], [73, 123], [28, 73], [39, 84], [84, 121], [2, 39]],...]
I achieved the result for a first few hundreds of files. However, up until around file 700, it doesn't work anymore. I need another approach for this problem if anyone can be patient enough to read to here and help me out. Thank you very much!
I think my first inclination would be to just iterate over the dataframe and collect results in a defaultdict. Maybe something like:
import collections
import pandas
mock_data = pandas.DataFrame([
{"Name": "product_name", "ymin": 4, "ymax": 43},
{"Name": "product_name", "ymin": 9, "ymax": 47},
{"Name": "product_total_money", "ymin": 76, "ymax": 122},
{"Name": "vat", "ymin": 30, "ymax": 74},
{"Name": "product_name", "ymin": 10, "ymax": 47}
])
y_results = collections.defaultdict(list)
for _, row in mock_data.iterrows():
y_results[row["Name"]].append((row["ymin"], row["ymax"]))
print(y_results)
Alternately, you might also try:
mock_data.groupby('Name').agg(lambda x: list(x))

Sort odd numbers in array while keeping even numbers in place

I'm working on a coding challenge to sort the odd elements of an array while keeping all even elements in their initial position.
I can sort the odd elements in groups between even elements while leaving the evens alone, but I want them to be ordered throughout the array. Here is my code:
def sort_array source_array
return [] if source_array.empty?
a = source_array
n = a.length
loop do
swapped = false
(n - 1).times do |i|
if (a[i] > a[i + 1]) && a[i].odd? && a[i + 1].odd?
a[i], a[i + 1] = a[i + 1], a[i]
swapped = true
end
end
break if not swapped
end
a
end
source_array = [7, 5, 35, 8, 12, 17, 47, 47, 37, 64, 22, 55, 13]
p sort_array(source_array)
# => [5, 7, 35, 8, 12, 17, 37, 47, 47, 64, 22, 13, 55]
You could try this approach:
Create an array with all odds (leaving source_array intact). Sort
this array.
Consider source_array element by element. Is the
element even then leave it . If it is odd, then replace it with the
first element on the sorted odds list. Remove the first element of the
sorted list.
Here are a couple of ways to do that.
arr = [7, 5, 35, 8, 12, 17, 47, 47, 37, 64, 22, 55, 13]
Both begin with the following calculation
odd, even = arr.each_with_index.partition { |n,i| n.odd? }
#=> [[[7, 0], [5, 1], [35, 2], [17, 5], [47, 6], [47, 7], [37, 8], [55, 11], [13, 12]],
# [[8, 3], [12, 4], [64, 9], [22, 10]]]
odd
#=> [[7, 0], [5, 1], [35, 2], [17, 5], [47, 6], [47, 7], [37, 8], [55, 11], [13, 12]]
even
#=> [[8, 3], [12, 4], [64, 9], [22, 10]]
See Enumerable#each_with_index, Enumerable#partition and Integer#odd?.
#1
odd_val, odd_idx = odd.transpose
#=> [[7, 5, 35, 17, 47, 47, 37, 55, 13],
# [0, 1, 2, 5, 6, 7, 8, 11, 12]]
a = even.concat(odd_val.sort.zip(odd_idx))
#=> [[8, 3], [12, 4], [64, 9], [22, 10], [5, 0], [7, 1], [13, 2],
# [17, 5], [35, 6], [37, 7], [47, 8], [47, 11], [55, 12]]
h = a.map(&:reverse).to_h
#=> {3=>8, 4=>12, 9=>64, 10=>22, 0=>5, 1=>7, 2=>13, 5=>17, 6=>35, 7=>37,
# 8=>47, 11=>47, 12=>55}
h.values_at(*0..arr.size-1)
#=> [5, 7, 13, 8, 12, 17, 35, 37, 47, 64, 22, 47, 55]
Note:
odd_val.sort.zip(odd_idx)
#=> [5, 7, 13, 17, 35, 37, 47, 47, 55].zip([0, 1, 2, 5, 6, 7, 8, 11, 12])
#=> [[5, 0], [7, 1], [13, 2], [17, 5], [35, 6], [37, 7], [47, 8], [47, 11], [55, 12]]
See Array#sort, Array#zip and Hash#values_at.
#2
a = odd.map(&:first).sort
#=> [5, 7, 13, 17, 35, 37, 47, 47, 55]
even.each { |n,i| a.insert(i,n) }
a #=> [5, 7, 13, 8, 12, 17, 35, 37, 47, 64, 22, 47, 55]
See Array#insert.
source_array = [7, 5, 35, 8, 12, 17, 47, 47, 37, 64, 22, 55, 13]
odd_sorted = source_array.select(&:odd?).sort
source_array.map {|input| input.even? ? input : odd_sorted.shift }
# [5, 7, 13, 8, 12, 17, 35, 37, 47, 64, 22, 47, 55]

ruby flatten and compact array full of empty arrays

In my ruby project, I have an array of arrays of arrays which is the result of doing some heavy graph traversal
example:
[[], [1, 2, 3, 4, 5], [[], [], [[], [], [34, 54, 23, 45]]]]
I want the final product to be
[[1, 2, 3, 4, 5], [34, 54, 23, 45]]
flatten will leave me with
[1, 2, 3, 4, 5, 34, 54, 23, 45]
so I don't want that
I'm stuck and could use some help on how to do this
so far, I have
class Array
def flatten_blanks
each{ |elem| elem.flatten_blanks if elem.is_a?(Array) }
reject!{ |elem| elem.is_a?(Array) && elem.length < 1 }
self
end
end
which is close but leaves me with something like
[[[[[[[1, 2, 3, 4, 5]]]]]]], [[[[[[[34, 54, 23, 45]]]]]]]
squeeze = -> (arr, acc = []) do
case arr.compact.first
when NilClass then acc
when Array
arr.reject(&:empty?).map { |e| squeeze.(e, acc) }.first
else acc << arr
end
end
squeeze.(arr)
#⇒ [[1, 2, 3, 4, 5], [34, 54, 23, 45]]
For you example it does the trick:
array = [[], [1, 2, 3, 4, 5], [[], [], [[], [], [34, 54, 23, 45]]]]
array.map(&:flatten).delete_if(&:empty?)
It will produce:
[[1, 2, 3, 4, 5], [34, 54, 23, 45]]
require 'json'
arr = [[], [1, 2, 3, 4, 5], [[], [], [[], [], [34, 54, 23, 45]]]]
JSON.parse("[#{arr.to_json.scan(/\[[^\[\]]+\]/).join(',')}]")
#=> [[1, 2, 3, 4, 5], [34, 54, 23, 45]]
A recursive method (similar to mudsie's recursive proc) could also be used.
def recurse(arr)
arr.each_with_object([]) { |e,a|
e.first.is_a?(Array) ? a.concat(recurse e) : a << e unless e.empty? }
end
recurse(arr)
#=> [[1, 2, 3, 4, 5], [34, 54, 23, 45]]
Let's call your array arr
arr.map(&:flatten).reject(&:empty?)
To explain a little, you run flatten on the array's elements instead of the array itself, and then delete the empty subarrays.

How to change value of an element in dictionary of arrays?

I have created a dictionary like this
var MyArray: [String:[String:[Int]]] = [
"xx": ["x1": [1, 2, 3], "x2": [4, 5, 6], "x3": [7, 8, 9]],
"yy": ["y1": [10, 11, 12], "y2": [13, 14, 15], "y3": [16, 17, 18]]]
How can I change the value 3 in "x1" in "xx" to an other number?
I don't know that this is the number 3 but i know that it is in MyArray["xx"]!["x1"]![2]
// example setup
var myArray: [String:[String:[Int]]] = [
"xx": ["x1": [1, 2, 3], "x2": [4, 5, 6], "x3": [7, 8, 9]],
"yy": ["y1": [10, 11, 12], "y2": [13, 14, 15], "y3": [16, 17, 18]]]
// value to be replaced
let oldNum = 3
// value to replace old value by
let newNum = 4
// extract the current value (array) for inner key 'x1' (if it exists),
// and proceed if 'oldNum' is an element of this array
if var innerArr = myArray["xx"]?["x1"], let idx = innerArr.index(of: oldNum) {
// replace the 'oldNum' element with your new value in the copy of
// the inner array
innerArr[idx] = newNum
// replace the inner array with the new mutated array
myArray["xx"]?["x1"] = innerArr
}
print(myArray)
/* ["yy": ["y3": [16, 17, 18], "y2": [13, 14, 15], "y1": [10, 11, 12]],
"xx": ["x1": [1, 2, 4], "x3": [7, 8, 9], "x2": [4, 5, 6]]]
^ ok! */
Based on the following Q&A:
Dictionary in Swift with Mutable Array as value is performing very slow? How to optimize or construct properly?
A more performant approach would be actually removing the inner array (for key "x1"); mutating it; and re-adding it to the dictionary
// check if 'oldNum' is a member of the inner array, and if it is: remove
// the array and mutate it's 'oldNum' member to a new value, prior to
// adding the array again to the dictionary
if let idx = myArray["xx"]?["x1"]?.index(of: oldNum),
var innerArr = myArray["xx"]?.removeValue(forKey: "x1") {
innerArr[idx] = newNum
myArray["xx"]?["x1"] = innerArr
}
print(myArray)
// ["yy": ["y3": [16, 17, 18], "y2": [13, 14, 15], "y1": [10, 11, 12]], "xx": ["x1": [1, 2, 4], "x3": [7, 8, 9], "x2": [4, 5, 6]]]
If you know the index of the number you would like to change out of the three, you can change the number 3 directly using the subscripts ["xx"]?["x1"]?[2].
var myArray = [
"xx": [
"x1": [1, 2, 3],
"x2": [4, 5, 6],
"x3": [7, 8, 9]
],
"yy": [
"y1": [10, 11, 12],
"y2": [13, 14, 15],
"y3": [16, 17, 18]
]
]
array["xx"]?["x1"]?[2] = 4

How to sum the integer inside array of arrays?

a = [[5, 6], [6, 5], [7, 4], [1, 0, 0], [9, 9], [6, 8], [8, 6], [1, 8, 0], [9, 0]]
How can I sum the integers inside the inner array and return an array of sum of the inner array?
I need it to return [(5+6), (6+5), (7+4), (1+0+0), ...], thus [11, 11, 11, 1, 18, 14, 14, 9, 9]
a.map{|a| a.inject(:+)}
# => [11, 11, 11, 1, 18, 14, 14, 9, 9]
Try
sum_arry = a.map { |sub_arry| sub_arry.inject(&:+) }
Then sum_arry should be the array you want.
Use map + reduce
a.map { |a| a.reduce(:+) }

Resources