I have hundreds of arrays that am normalizing for a CSV.
[
["foo", "tom", nil, 1, 4, "cheese"],
["foo", "tom", "fluffy",nil, 4],
["foo", "tom", "fluffy",1, nil],
...
]
Currently to make them all equal length i am finding the max length and setting to a value.
rows.each { |row| row[max_index] ||= nil }
this is cool because it makes the array length equal to the new length.
Instead of appending a bunch of nils at the end I needed to append COLUMN_N where N is the index (1-based).
table_rows.each do |row|
last_index = row.length - 1
(last_index..max_index).to_a.each { |index| row[index] ||= "COLUMN_#{index+1}" }
end
Which seemed like an awkward way to have a default value that is a function of the index.
You can't choose a default value for filling elements with []= method. But you can easily do something like this if there aren't other nils that you don't want to replace.
row.each_with_index.map { |item, index| item.nil? ? "column_#{index}": item }
To get a default value instead of nil you can use fetch:
row = ["foo", "tom", "fluffy", 1, 4]
row.fetch(7) { |i| "COLUMN_#{i + 1}" }
=> "COLUMN_8"
But it won't fill the array for you.
Also see: Can I create an array in Ruby with default values?
This seems like it could work for you.
class Array
def push_with_default(item, index, &block)
new_arr = Array.new([self.size + 1, index].max, &block)
self[index] = item
self.map!.with_index { |n, i| n.nil? ? new_arr[i] : n }
end
end
>> array = [1,2,5,9]
[
[0] 1,
[1] 2,
[2] 5,
[3] 9
]
>> array.push_with_default(2, 10) { |i| "column_#{i}" }
[
[ 0] 1,
[ 1] 2,
[ 2] 5,
[ 3] 9,
[ 4] "column_4",
[ 5] "column_5",
[ 6] "column_6",
[ 7] "column_7",
[ 8] "column_8",
[ 9] "column_9",
[10] 2
]
I don't believe a method like this exists on Array already though.
Related
I am having a hard time figuring out how to make the next rows a hash with the key from the first row.
I have an array structured like this:
[["id", "name", "address"], [1, "James", "...."], [2, "John", "...."] ]
To be:
[{ id : 1, name: "James", address: "..."}, ...]
I used a gem "simple_xlsx_reader", I am extracting out only the first sheet.
wb.sheets.first.row
and got a similar array output from above.
thanks!
arr = [["id", "name"], [1, "Jack"], [2, "Jill"]]
[arr.first].product(arr.drop 1).map { |a| a.transpose.to_h }
#=> [{"id"=>1, "name"=>"Jack"}, {"id"=>2, "name"=>"Jill"}]
The steps:
b = [arr.first]
#=> [["id", "name"]]
c = arr.drop 1
#=> [[1, "Jack"], [2, "Jill"]]
d = b.product(c)
#=> [[["id", "name"], [1, "Jack"]], [["id", "name"], [2, "Jill"]]]
d.map { |a| a.transpose.to_h }
#=> [{"id"=>1, "name"=>"Jack"}, {"id"=>2, "name"=>"Jill"}]
The first element of d passed to map's block is:
a = d.first
[["id", "name"], [1, "Jack"]]
The block calculation is therefore:
e = a.transpose
#=> [["id", 1], ["name", "Jack"]]
e.to_h
#=> {"id"=>1, "name"=>"Jack"}
This is what you're looking for:
arr = [["id", "name", "address"], [1, "James", "...."], [2, "John", "...."] ]
keys, *values = arr
values.map {|vals| keys.zip(vals).to_h }
Enumerable#zip takes two arrays (the receiver and the argument) and "zips" them together, producing an array of tuples (two-element arrays) e.g.:
keys = [ "foo", "bar", "baz" ]
values = [ 1, 2, 3 ]
p keys.zip(values)
# => [ [ "foo", 1 ], [ "bar", 2 ], [ "baz", 3 ] ]
Array#to_h takes an array of tuples and turns it into a hash.
If you're using a version of Ruby earlier than 2.1 you'll need to use Hash[ *keys.zip(vals) ] instead.
P.S. If you want symbol keys instead of string keys you'll want to perform that conversion before the map, e.g.:
keys = keys.map(&:to_sym)
Or, if you don't mind modifying the original array:
keys.map!(&:to_sym)
You can try this very simple one line that make your work
arr =[["id", "name", "address"], [1, "James", "add 1"], [2, "John", "add2"] ]
arr.map {|a| arr.first.zip(a).to_h unless a == arr.first }.compact
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A']
indval = ahash.shift
ahash become as follows
[1, 2, 3, 4]
and mainhash become as follows
{"A"=>[1, 2, 3, 4], "B"=>[0, 1, 2, 3]}
I am manipulating ahash variable by shifting some values from ahash, When I do this operation it affects the mainhash value. Why it is happening?
Am I missing any conceptual understanding?
It's because ahash and mainhash both have references to the same Array instance. If you modify this through ahash, referenced object is being modified, so no wonder it changes also in mainhash.
To operate on copy (shallow copy, to be precise) of the object instead of the same object, you should use dup method:
ahash = mainhash['A'].dup
Look Array#shift
Removes the first element of self and returns it (shifting all other elements down by one). Returns nil if the array is empty.
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A']
p ahash.object_id # => 8577888
p mainhash['A'].object_id # => 8577888
p indval = ahash.shift # => 0
As above seen, ahash and mainhash['A'] refer to the same Array object [ 0,1,2,3,4], thus changing ahash#shift causes 0 to be removed from ahash which also causes 0 to be removed from mainhash['A'].
Said that your Hash becomes as below :
mainhash
# => {"A"=>[1, 2, 3, 4], "B"=>[0, 1, 2, 3]}
All operations are legitimate and happened as documented to the link,I have given above.
How can I avoid affecting the mainhash
As #Marek Lipka said :
you should use dup method: ahash = mainhash['A'].dup.
mainhash = { 'A' => [ 0,1,2,3,4 ] , 'B' => [ 0 ,1,2 ,3 ] }
ahash = mainhash['A'].dup
ahash.object_id # => 8577516
mainhash['A'].object_id # => 8577600
indval = ahash.shift # => 0
ahash # => [1, 2, 3, 4]
mainhash['A'] # => [0, 1, 2, 3, 4]
Why it is happening ?.
arr = [1, 2, 3]
x = arr
arr.shift
p arr
p x
--output:--
[2, 3]
[2, 3]
arr and x both refer to the same array. Assignment ('=') does not create a copy.
Now look at this code:
arr = [1, 2, 3]
x = arr.dup
arr.shift
p arr
p x
--output:--
[2, 3]
[1, 2, 3]
And by the way, the name 'ahash' is a terrible name for an array.
I have a hash, which contains a hash, which contains a number of arrays, like this:
{ "bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 9],
"bar" => [8, 10, 12]
}
}
I would like to compare the arrays against the other arrays, and then alert me if they are duplicates. It is possible for hash["bob"]["foo"] and hash["fred"]["foo"] to have duplicates, but not for hash["bob"]["foo"] and hash["bob"]["bar"]. Same with hash["fred"].
I can't even figure out where to begin with this one. I suspect inject will be involved somewhere, but I could be wrong.
This snippet will return an array of duplicates for each key. Duplicates can only be generated for equal keys.
duplicates = (keys = h.values.map(&:keys).flatten.uniq).map do |key|
{key => h.values.map { |h| h[key] }.inject(&:&)}
end
This will return [{"foo"=>[1]}, {"bar"=>[]}] which indicates that the key foo was the only one containing a duplicate of 1.
The snippet above assume h is the variable name of your hash.
h = {
"bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 9],
"bar" => [1, 10, 12]
}
}
h.each do |k, v|
numbers = v.values.flatten
puts k if numbers.length > numbers.uniq.length
end
There are many ways to do it.
Here's one that should be easy to read.
It works in Ruby 1.9. It uses + to combine two arrays and then uses the uniq! operator to figure out whether there is a duplicate number.
h = { "bob" =>
{
"foo" => [1, 3, 5],
"bar" => [2, 4, 6]
},
"fred" =>
{
"foo" => [1, 7, 12],
"bar" => [8, 10, 12]
}
}
h.each do |person|
if (person[1]["foo"] + person[1]["bar"]).uniq! != nil
puts "Duplicate in #{person[1]}"
end
end
I'm not sure what exactly you are looking for. But at look at a possible solution, perhaps you can reuse something.
outer_hash.each do |person, inner_hash|
seen_arrays = Hash.new
inner_hash.each do |inner_key, array|
other = seen_arrays[array]
if other
raise "array #{person}/#{inner_key} is a duplicate of #{other}"
end
seen_arrays[array] = "#{person}/#{inner_key}"
end
end
Is there an elegant way to find and replace any integers superior to 3 (as example) in a multidimensional array? The array may have the dimension 1, 2, 3, or more. Just an example of a such array:
[ [ [ 3, 3, 5 ],
[ 4, 3, 3 ] ],
[ [ 3, 2, 3 ],
[ 0, 3, 8 ] ] ]
I would like to do so without flatten the array.
Following sepp2k idea, here is a possible implementation:
class Object
def deep_map(&block)
if self.respond_to? :each
result = []
self.each do |e|
result << e.deep_map(&block)
end
return result
else
return block.call(self)
end
end
end
Then apply deep_map as you wish on the array:
> [[[3, 3, 5], [4, 3, 3]], [[3, 2, 3], [0, 3, 8]]].deep_map { |e| e > 3 ? 0 : e }
=> [[[3, 3, 0], [0, 3, 3]], [[3, 2, 3], [0, 3, 0]]]
Or, more briefly:
class Object
def deep_map(&block)
respond_to?(:map) ? map{|e| e.deep_map(&block)} : block.call(self)
end
end
Or, polymorphically:
class Object
def deep_map(&block); block.call(self) end
end
class Array
def deep_map(&block); map{|e| e.deep_map(&block)} end
end
You can write a deep_map method, which calls map on the array and then for each element test whether it's a sub-array. If it is, call deep_map recursively with the sub-array, otherwise yield the element.
You can then use that deep_map method to transform the inner elements of your multi-dimensional array without affecting its structure.
So, if I've done this right, f(x) will traverse a multidimensional Array until it finds one containing something that isn't an Array or subclass of Array, at which point it will yield the innermost Array to the block and replace it with the block's return value.
def f x, &block
x.map do |a|
if a.first.class.ancestors.include? Array
f a, &block
else
yield a
end
end
end
p(f [ [ [ 3, 3, 5 ],
[ 4, 3, 3 ] ],
[ [ 3, 2, 3 ],
[ 0, 3, 8 ] ] ] do |e|
e.reject { |x| x > 3 }
end
)
With a list in Python I can return a part of it using the following code:
foo = [1,2,3,4,5,6]
bar = [10,20,30,40,50,60]
half = len(foo) / 2
foobar = foo[:half] + bar[half:]
Since Ruby does everything in arrays I wonder if there is something similar to that.
Yes, Ruby has very similar array-slicing syntax to Python. Here is the ri documentation for the array index method:
--------------------------------------------------------------- Array#[]
array[index] -> obj or nil
array[start, length] -> an_array or nil
array[range] -> an_array or nil
array.slice(index) -> obj or nil
array.slice(start, length) -> an_array or nil
array.slice(range) -> an_array or nil
------------------------------------------------------------------------
Element Reference---Returns the element at index, or returns a
subarray starting at start and continuing for length elements, or
returns a subarray specified by range. Negative indices count
backward from the end of the array (-1 is the last element).
Returns nil if the index (or starting index) are out of range.
a = [ "a", "b", "c", "d", "e" ]
a[2] + a[0] + a[1] #=> "cab"
a[6] #=> nil
a[1, 2] #=> [ "b", "c" ]
a[1..3] #=> [ "b", "c", "d" ]
a[4..7] #=> [ "e" ]
a[6..10] #=> nil
a[-3, 3] #=> [ "c", "d", "e" ]
# special cases
a[5] #=> nil
a[6, 1] #=> nil
a[5, 1] #=> []
a[5..10] #=> []
If you want to split/cut the array on an index i,
arr = arr.drop(i)
> arr = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
> arr.drop(2)
=> [3, 4, 5]
You can use slice() for this:
>> foo = [1,2,3,4,5,6]
=> [1, 2, 3, 4, 5, 6]
>> bar = [10,20,30,40,50,60]
=> [10, 20, 30, 40, 50, 60]
>> half = foo.length / 2
=> 3
>> foobar = foo.slice(0, half) + bar.slice(half, foo.length)
=> [1, 2, 3, 40, 50, 60]
By the way, to the best of my knowledge, Python "lists" are just efficiently implemented dynamically growing arrays. Insertion at the beginning is in O(n), insertion at the end is amortized O(1), random access is O(1).
Ruby 2.6 Beginless/Endless Ranges
(..1)
# or
(...1)
(1..)
# or
(1...)
[1,2,3,4,5,6][..3]
=> [1, 2, 3, 4]
[1,2,3,4,5,6][...3]
=> [1, 2, 3]
ROLES = %w[superadmin manager admin contact user]
ROLES[ROLES.index('admin')..]
=> ["admin", "contact", "user"]
another way is to use the range method
foo = [1,2,3,4,5,6]
bar = [10,20,30,40,50,60]
a = foo[0...3]
b = bar[3...6]
print a + b
=> [1, 2, 3, 40, 50 , 60]
I like ranges for this:
def first_half(list)
list[0...(list.length / 2)]
end
def last_half(list)
list[(list.length / 2)..list.length]
end
However, be very careful about whether the endpoint is included in your range. This becomes critical on an odd-length list where you need to choose where you're going to break the middle. Otherwise you'll end up double-counting the middle element.
The above example will consistently put the middle element in the last half.