Rails remove subarray from array - ruby-on-rails

In my Rails 5 app I have something like this:
a = [1,2,3]
a.map do |entry|
entry.delete if condition == true
end
This removes the entry if the condition is true.
Now I have this:
a = [[1,2],[2,3],[3,4]]
a.map do |entry|
entry.delete if condition == true
end
This loops through a but now entry is an array and delete should remove the entire entry but instead I get:
wrong number of arguments (given 0, expected 1)
Dos anybody know how I can loop through an array of arrays and remove an entire subarray?

Try this:
a.delete_if {condition}
For example:
a = [[1,2],[2,3],[3,4]]
a.delete_if {|entry| entry[0] == 1 }
# returns [[2, 3], [3, 4]]

instead of using map you should use flat_map see: https://apidock.com/ruby/Enumerable/flat_map

Related

Remove array elements at indices non destructively

Let's say you have something like this:
my_array = ['some_rather', 'long_named', 'array_element', 'entry']
I want to remove arbitrary entries by index from my_array without changing it and I want the filtered (i.e. array with indices removed) to be returned from my call. Furthermore, I want to avoid chaining 4 separate calls and write a block doing so.
Example:
filtered_array = my_array.drop_indices(1,3)
You could chain Enumerable's with_index onto Array's reject method to do what you want, though this might violate your desire to not chain separate method calls or write a block to do this:
my_array = ['some_rather', 'long_named', 'array_element', 'entry', 'long_named']
indices_to_remove = [1, 3]
filtered = my_array.reject.with_index { |_, index| indices_to_remove.include?(index) }
p filtered # => ["some_rather", "array_element", "long_named"]
p my_array # => ["some_rather", "long_named", "array_element", "entry", "long_named"]
If this isn't acceptable, the only other thing I can think of right now, to keep duplicate items (as noted in my comment to your solution), is to change from indices_to_remove to indices_to_keep:
my_array = ['some_rather', 'long_named', 'array_element', 'entry', 'long_named']
indices_to_remove = [1, 3]
indices_to_keep = [*(0...my_array.length)] - indices_to_remove
filtered = my_array.values_at(*indices_to_keep)
p filtered # => ["some_rather", "array_element", "long_named"]
p my_array # => ["some_rather", "long_named", "array_element", "entry", "long_named"]
For Arrays with duplicate Elements the best I know is:
array = [0,15,8,15,8]
indices_to_remove = [1,4]
res = array.reject.with_index{ |_,i| indices_to_remove.include?(i) }
returns
[0,8,15]
Additionally for arrays with unique entries such as a set of users
(using variable definitions from the question)
filtered_array = my_array - my_array.values_at(1,3)
Bonus, if your indices are inside an array themselves:
indices_to_remove = [1,3]
filtered_array = my_array - my_array.values_at(*indices_to_remove)
I think this is rather descriptive, not awkward and not shorter than needed.
One more possible solution with some addition, now it's also will work with negative indexes:
array = %w( a b c d e f )
indexes = [1, 2, -1, -9, -6, 6]
def array_except(array, *indexes)
indexes = indexes.map { |e| e.negative? ? e + array.length : e }
array.values_at(*((0...array.length).to_a - indexes))
end
array_except(array, *indexes)
=> ["d", "e"]
array_except(array, 0, -1)
=> ["b", "c", "d", "e"]

Using .map function to create hashes

I have an array [5,2,6,4] and I would like to create a structure such as the first minus the second etc until the last row.
I have tried using map, but not sure how to proceed since i might need indxes.
I would like to store the result in something that looks like:
{1 => (5, 2, 3), 2 =>(2,6,-4), 3 => (6,4,2)}
So an array of x should return x-1 hashes.
Anybody knows how to do? should be a simple one.
Thank you.
First, you want to work with the array elements in pairs: 5,2, 2,6, ... That means you want to use each_cons:
a.each_cons(2) { |(e1, e2)| ... }
Then you'll want the index to get the 1, 2, ... hash keys; that suggests throwing a Enumerator#with_index into the mix:
a.each_cons(2).with_index { |(e1, e2), i| ... }
Then you can use with_object to get the final piece (the hash) into play:
a.each_cons(2).with_index.with_object({}) { |((e1, e2), i), h| h[i + 1] = [e1, e2, e1 - e2] }
If you think all the parentheses in the block's arguments are too noisy then you can do it in steps rather than a single one-liner.
You can use each_index:
a = [5, 2, 6, 4]
h = {}
a[0..-2].each_index { |i| h[i+1] = [a[i], a[i+1], a[i] - a[i+1]] }
h
=> {1=>[5, 2, 3], 2=>[2, 6, -4], 3=>[6, 4, 2]}
Try to use
each_with_index
Suppose you have an array:
arr = [3,[2,3],4,5]
And you want to covert with hash(key-value pair). 'Key' denotes an index of an array and 'value' denotes value of an array. Take a blank hash and iterate with each_with_index and pushed into the hash and finally print the hash.
Try this:
hash={}
arr.each_with_index do |val, index|
hash[index]=val
end
p hash
Its output will be:
{0=>3, 1=>[2, 3], 2=>4, 3=>5}
If you want that index always starts with 1 or 2 etc then use
arr.each.with_index(1) do |val, index|
hash[index] = val
end
Output will be:
{1=>3, 2=>[2, 3], 3=>4, 4=>5}

Can someone please explain use of each_with_index, reduce, select chained in the code along with OR (||)?

Can someone explain this code to me? What does the || syntax mean and how does it work?
array = [1, 2, 3, 4, 5, 3, 6, 7, 2, 8, 1, 9]
array.each_with_index.reduce({}) { |hash, (item, index)|
hash[item] = (hash[item] || []) << index
hash
}.select{ |key, value| value.size > 1 }
I'd rewrite it this way
array.each_with_index.reduce(Hash.new { Array.new }) do |hash, (item, index)|
hash.merge(item => hash[item] << index)
end.select { |_, indexes| indexes.size > 1 }
We use each_with_index because we want to access the index while looping the array. You can see it later next to item as a parameter in the reduce's block.
reduce permits us to "transform" a collection in something else. In our case we want to construct an hash out of an array.
In the reduce's block we add the current index to the key value pair for the current item. I used merge to do it in just one expression (update the hash and using it as an expression to return).
In the end, we keep just the key value pairs whose values (and those are arrays) have more than one elements. Note that we don't care about keys here so I called the key parameter _.
array = [1, 2, 3, 4, 5, 3, 6, 7, 2, 8, 1, 9]
array.each_with_index.reduce({}) do |hash, (item, index)|
hash[item] = (hash[item] || []) << index
hash
end.select do |key, value|
value.size > 1
end
First of all cleaner way of writing Enumerators single line are enclosed with {} and multiline with do; end.
array.each_with_index.reduce({}) do |hash, (item, index)|
# hash[item] gets either itself and if itself is nil it gets an empty
# array assigned and additionally the index gets added to this array
hash[item] = (hash[item] || []) << index
# return the hash for the reduce enumerator
hash
end
In this part you iterate over the array and pass in the initial empty hash with .reduce({}). The hash then gets transformed and returned in L3 in the "loop" and passed into the next iteration of the reduce. Your result then is the build up hash which then immediately get enumerated with select where only key value pairs get returned that have a value size larger than 1.
Best would be to readup on Enumerators#reduce and how the objects are passed into the "loop".
hash[item] is nil at first. nil || [] => [], so hash[item] become array. << push an item into array.
hope this can help you understand how hash store the value
require "pp"
hash = {}
pp hash[1] # => nil
pp hash[1] || [] # => []
pp (hash[1] || []) << 1 # => [1]
pp hash[1] # => nil
hash[1] = (hash[1] || []) << 1
pp hash[1] # => [1]

remove array from multidimensional array if first 3 values aren't unique in ruby on rails

OK, so I have this array of arrays. Each array within the larger array is very much the same, ten specific values. If my value at location 3 is a specific value, then I want to iterate through the rest of the remaining arrays within the larger array and see if the first 3 values at locations 0, 1, and 2 match. if they then match, I'd like to delete the original array. I'm having a hard time with it, maybe there is an easy way? I'm sure there is, I'm fairly new to this whole coding stuff =) So much appreciation in advance for your help....
here's where I'm at:
#projectsandtrials.each do |removed|
if removed[3] == ["Not Harvested"]
#arraysforloop = #projectsandtrials.clone
#arraysforloop1 = #arraysforloop.clone.delete(removed)
#arraysforloop1.each do |m|
if (m & [removed[0], removed[1], removed[2]]).any?
#projectsandtrials.delete(removed)
end
end
end
end
Lets look at your situation:
#projectsandtrials.each do |removed|
// some logic, yada yada
#projectsandtrials.delete(removed)
end
You can't just delete stuff out of an array you're iterating through. At least not until you finish iterating through it. What you should be using instead is a filtering method like reject instead of just an each.
So instead of deleting right there, you should just return true when using reject.
I think about it like this when iterating through arrays.
Do I want the array to stay the same size and have the same content?
Use each.
Do I want the array to be the same size, but have different content?
Use map.
Do I want the array to be less than or equal to the current size?
Use select or reject.
Do I want it to end up being a single value?
Use reduce.
Code
def prune(arr, val)
arr.values_at(*(0..arr.size-4).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }.
concat((arr.size-3..arr.size-1).to_a))
end
Example
arr = [ [1,2,3,4,0],
[3,4,5,6,1],
[3,4,5,4,2],
[3,4,5,6,3],
[3,4,5,6,4],
[3,4,0,6,5],
[2,3,5,4,6],
[2,3,5,5,7],
[2,3,5,7,8],
[2,3,5,8,9],
[2,3,5,7,0]
]
Notice that the last values of the elements (arrays) of arr are consecutive. This is to help you identify the elements of prune(arr, 4) (below) that have been dropped.
prune(arr, 4)
# => [[3, 4, 5, 6, 1],
# [3, 4, 5, 4, 2],
# [3, 4, 5, 6, 3],
# [3, 4, 5, 6, 4],
# [3, 4, 0, 6, 5],
# [2, 3, 5, 5, 7],
# [2, 3, 5, 7, 8],
# [2, 3, 5, 8, 9],
# [2, 3, 5, 7, 0]]
Explanation
The arrays at indices 0 and 6 have not been included in array returned.
arr[0] ([1,2,3,4,0]) has not been included because arr[0][3] = val = 4 and arr[1], arr[2] and arr[3] all begin [3,4,5].
arr[6] ([2,3,5,4,6]) has not been included because arr[6][3] = 4 and arr[7], arr[8] and arr[9] all begin [2,3,5].
arr[2] ([3,4,5,5,2]) has been included because, while arr[2][3] = 4, arr[3][0,3], arr[4][0,3] and arr[5][0,3] all not all equal (i.e., arr[5][2] = 0).
Notice that the last three elements of arr will always be included in the array returned.
Now let's examine the calculations. First consider the following.
arr.size
#=> 11
a = (0..arr.size-4).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }
#=> (0..7).reject { |i| arr[i][3] == val &&
arr[i+1..i+3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 } }
#=> [1, 2, 3, 4, 5, 7]
Consider reject's block calculation for i=0 (recall val=4).
arr[i][3] == val && arr[i+1..i+3].transpose[0,3].map(&:uniq).all? {|a| a.size==1 }}
#=> 4 == 4 && arr[1..3].transpose[0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3,4,5,6,1],
# [3,4,5,4,2],
# [3,4,5,6,3]].transpose[0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3, 3, 3],
# [4, 4, 4],
# [5, 5, 5],
# [6, 4, 6],
# [1, 2, 3]][0,3].map(&:uniq).all? { |a| a.size==1 }
#=> [[3, 3, 3],
# [4, 4, 4],
# [5, 5, 5]].map(&:uniq).all? { |a| a.size==1 }
#=> [[3], [4], [5]].all? { |a| a.size==1 }
#=> true
meaning arr[0] is to be rejected; i.e., not included in the returned array.
The remaining block calculations (for i=1,...,10) are similar.
We have computed
a #=> [1, 2, 3, 4, 5, 7]
which are the indices of all elements of arr except the last 3 that are to be retained. To a we add the indices of the last three elements of arr.
b = a.concat((arr.size-3..arr.size-1).to_a)
#=> a.concat((8..10).to_a)
#=> a.concat([8,9,10])
#=> [1, 2, 3, 4, 5, 7, 8, 9, 10]
Lastly,
arr.values_at(*b)
returns the array given in the example.
Your code snippet seems fine, although there are couple of things to note:
#arraysforloop.clone.delete(removed) removes all the occurences of removed array (not only the first one). E.g. [1,2,3,1].delete(1) would leave you with [2,3]. You could fix it with using an iterator for #projectsandtrials and delete_at method.
delete method returns the same argument you pass to it (or nil if no matches found). So #arraysforloop1 = #arraysforloop.clone.delete(removed) makes your #arraysforloop1 to contain the removed array's elements only! Removing an assignment could save you.
I see no reason to have two cloned arrays, #arraysforloop and #arraysforloop1, as the former one is not used anyhow later. May be we could omit one of them?
#projectsandtrials.delete(removed) leaves you in a strange state, as long as you're iterating the same array right now. This could end up with you missing the right next element after the removed one. Here is a simple snippet to illustrate the behaviour:
> a = [1,2,3]
> a.each{|e, index| puts("element is: #{e}"); a.delete(1);}
element is: 1
element is: 3
As you see, after deleting element 1 the loop moved to element 3 directly, omitting the 2 (as it became the first element in array and algorithm thinks it's been handled already).
One of the possibilities to make it less messy is to split it to a bundle of methods. Here is an option:
def has_searched_element? row
# I leave this method implementation to you
end
def next_rows_contain_three_duplicates?(elements, index)
# I leave this method implementation to you
end
def find_row_ids_to_remove elements
[].tap do |result|
elements.each_with_index do |row, index|
condition = has_searched_element?(row) && next_rows_contain_three_duplicates?(elements, index)
result << index if condition
end
end
end
row_ids_to_remove = find_row_ids_to_remove(#projectsandtrials)
# now remove all the elements at those ids out of #projectsandtrials

Convert array to single object if array is a single object

Really simple question.
Is there a method to do the following?
["a"] => "a"
[1] => 1
[1,"a"] => [1, "a"]
i.e. if an array is a single object, return the object, otherwise return the array.
Without doing something ugly like
array.length == 1 ? array[0] : array
basically you should stick to what you wrote - it's simple and does what it should.
of course you may always monkey patch the Array definition... (unrecommended, but it does what you expect)
class Array
def first_or_array
length > 1 ? self : self[0]
end
end
[1].first_or_array # 1
[1, 2].first_or_array # [1, 2]

Resources