In Rails, how do I figure out if an array of objects contains specific attributes matching given values? - ruby-on-rails

I'm using Ruby on Rails 5.0.1 with Ruby 2.4. I have an array of objects, stored in the array, "results." Each object has a numeric attribute
numeric_attr
I would like to know, given my array, how I can tell if I have exactly one object with a numeric attribute value of "1" and incrementing by one. Order is not important. So, for instance, if I have an array of three objects,
[MyObject(numeric_attr = 2), MyObject(numeric_attr = 1), MyObject(numeric_attr = 3)]
I want to know if I have exactly one object with numeric_attr = 1, another object with numeric_attr = 2, and another with numeric_attr = 3. So the above satisfies the condition. The below example does not
[MyObject(numeric_attr = 4), MyObject(numeric_attr = 1), MyObject(numeric_attr = 3)]
because although there is an object with numeric_attr = 1, there is no object with numeric_attr = 2. It is possible thet the numeric_attr field is nil. How can I figure this out?

This one-liner should work:
results.map(&:numeric_attr).sort == (1..results.count).to_a
Explanation:
results
#=> [#<MyObject:... #attr=2>, #<MyObject:... #attr=3>, #<MyObject:... #attr=1>]
results.map(&:attr)
#=> [2, 3, 1]
results.map(&:attr).sort
#=> [1, 2, 3]
(1..results.length).to_a
#=> [1, 2, 3]
# therefore:
results.map(&:attr).sort == (1..results.count).to_a
#=> true
If there is a chance that numeric_attr is nil:
results.map(&:attr).compact.sort == (1..results.count).to_a
Of course, if there is even a single nil value, the result is guaranteed to be false.
If the sequence could start at any number, not just 1:
results.map(&:attr).sort == results.count.times.to_a.
map { |i| i + results.map(&:attr).sort.first }
This is not very efficient though, as it sorts the numbers twice.

If they always start at 1 #Máté's solution works, if they can start at any arbitrary number then you could:
count = 0
array_objects.sort_by(&:numeric_attr).each_cons(2) {|a,b| count+=1 if a.numeric_attr==b.numeric_attr-1 }
count+1==array_objects.count
Not as elegant but handles a lot more situations

Related

How cascading modifies original object?

var intList = [3, 2, 1];
var sorted = intList..toList()..sort(); // [1, 2, 3]
var sorted2 = intList..toList().sort(); // [3, 2, 1]
Why my original list is also being modified in first sort and which list is being sorted in second sort?
NOTE: I'm not looking for the correct way to do it which is this:
var sorted = intList.toList()..sort(); // [1, 2, 3]
x..y evalutes to x. Cascade chains are evaluated left-to-right, so x..y..z is the same as (x..y)..z. Your first example therefore makes calls to toList() and to sort() on the original object.
Member access (.) has higher precedence than the cascade operator (..). Your second example calls sort() on the copy returned by toList(), not on the original object.

Is there a way to check if a hash value in Ruby is the same throughout or compare only the values for equality?

I have the below code that returns the number of instances of an item in an array as a hash. I now need to check if the value is the same. For example if the hash is like this = {1=>3, 2=>3} i need to check if the value is the same, in this case it is but dont know how to check this.
arr.inject(Hash.new(0)) {|number,index| number[index] += 1 ;number}
Thanks
So, given h = { 1 => 3, 2 => 3 }, if I got you, you want to know if the values are ALL the same. If you knew the keys you could do
all_the_same = h[1] == h[2]
If there are more keys you want to check
all_the_same = h.values_at(1, 2, 3, 4).uniq.length == 1
If you don't know how many keys you have or which are these keys you could do
all_the_same = h.values.uniq.length == 1

remove item from a list by specific index

I have this table:
local ls = {
["foo"] = {1, 2, 3, 4, 5},
["bar"] = {5, 4, 3, 2, 1}
}
I want to remove "foo" from list.
I tried this:
table.remove(ls, "foo")
but returns a error: "Only numbers"
Okay, but I can't input a number. This list isn't static, in my code a lot of indexes will be inserted in this list.
The question is, is there other way to do this or other function that fit my problem?
table.remove only works for a sequence. In your code, the table ls isn't one.
To remove an entry from a table, just assign the value of specific key to nil:
ls.foo = nil

How to count the number of arrays in a nested array

I want to count the number of arrays in the nested arrays of an array
array = [[["-", 0, "I"], ["+", 0, "you"]], [["+", 3, "i"]], [["-", 4, "loved"], ["-", 5, "that"], ["+", 5, "it"], ["+", 6, "tasted"], ["+", 7, "like"]]]
This example would have 8 nested arrays inside the arrays in the array array. (not sure if I worded that right)
The easiest/cleanest way is to partially flatten an array by one nesting level:
array.flatten(1).count
Other option is to sum sub-arrays:
array.inject([], :+).count
However the real question you need to ask to yourself is - how did I end up with such a weird construct?
The key here is to properly state the question - I assume it to be count the number of arrays inside the nested array, which themselves do not contain another arrays.
def count_inner_arrays(arr)
sub_arrays = arr.select { |el| el.is_a? Array }
sub_arrays.empty? ? 1 : sub_arrays.map(&method(:count_inner_arrays)).inject(0, :+)
end

Is there a way to apply multiple method by one liner in ruby?

There is an array like this:
a = [1,2,3,4]
I want to get the return values of size and sum like this.
size = a.size
sum = a.sum
Is there a way to get both values by a one-liner like this?
size, sum = a.some_method(&:size, &:sum)
In Ruby, you can do multiple assignments in one line:
size, sum = a.size, a.sum
It doesn't make it more readable, though.
You could do this:
a = [1,2,3,4]
methods = [:size, :max, :min, :first, :last]
methods.map { |m| a.send m }
#=> [4, 4, 1, 1, 4]
Another possible solution:
size, sum = a.size, a.reduce { |a,b| a = a + b }
Previous answers are correct, but if OP was actually concerned about walking the array multiple times, then array.size does not walk the array, it merely returns the length, thus there is no saving from a oneliner in that regard.
On the other hand, if size was just an example and the question is more about making multiple operations on an array in one go, then try something like this:
arr = [1,2,3,4,5,6]
product,sum = arr.inject([1,0]){|sums,el| [sums[0]*el, sums[1]+el]}
# => [720, 21]
That is, inject the array with multiple initial values for the results and then calculate new value for every element.

Resources