Trouble sorting records on unique attributes - ruby-on-rails

Working with the following code, I need to return only records where the `point' attribute is unique. I can't seem to get there by myself.
uniques = Item.find_all_by_item_id(item_id)
uniques.sort! {|a, b| b.point <=> a.point } # how do I reject the equal points?
In other words.. I guess, how do you make [0, 1, 1, 1, 2, 3, 3, 4, 4, 7] #=> [0, 2, 7] ?

How about this:
# Get the number of items with each point value
counts = Item.count("point", :group => "point")
# Get the IDs of all entries that only show up once
unique_ids = counts.collect { |count| count[0] if count[1] == 1 }.compact
unique_items = Item.find_all_by_id(unique_ids)

I can think of few ways off the top of my head to do this:
uniques.reject!{|u| uniques.select{|x| x == u}.size > 1}
Basically iterate through the uniques array and then see if there is more than one of those items in the array. Obviously there are lots of clever ways to speed this up, but for small arrays this should work.
or
h = Hash.new(0)
uniques.each{|u| h[u] += 1}
h.reject{|k,v| v > 1}.keys
Basically count how many times each item shows up in a hash, if its more than one reject it and then just look at the keys.

Related

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}

Iterating Over 2 Arrays And Building A New Array From Matches

Ok so I have an array of 'winner ids' this array represents users who have won in the previous round of a tournament. I then have an array of objects called 'tournament participations', this represents all the users that have participated in the tournament (many to many relationship join table).
For each 'winner id' I want to iterate through the 'tournament participations' array and find the tournament participation with a user_id that matches the 'winner id', then push it into a new array called 'round participations'...
I have tried the code below but I always get returned the original 'winner_ids' array....
#challenges = Challenge.where(tournament_id: #tournament.id)
#winner_ids = #challenges.pluck(:winner_id)
#tournament_participations = #tournament.tournament_participations
#round_participations = []
#round_participations = #winner_ids.each do |winner_id|
#round_participation = #tournament_participations.where(user_id: winner_id)
#round_participations << #round_participation
end
each returns the enumerable it was called on; in this case, #winner_ids.each is returning #winner.ids. You don't need to assign the result of the iteration to #round_participations.
Also, check out the map method.
Use the map method, instead of .each.
The map method can be used to create a new array based on the original array, but with the values modified by the supplied block. See the below example.
In case of each method
irb(main):001:0> arr = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> arr.each { |a| print a -= 10, " " }
-9 -8 -7 -6 -5
=> [1, 2, 3, 4, 5]
you can see after iterations it returned the original array. But in the case of map
irb(main):005:0> arr = [1, 2, 3, 4, 5]
=> [1, 2, 3, 4, 5]
irb(main):006:0> arr.map { |a| 2*a }
=> [2, 4, 6, 8, 10]
map can transform the contents of an array, meaning that it can perform an operation on each element in the array.

How do I find the maximum value of an object's attribute that occurs exactly twice in an array?

I'm using Ruby 2.4. If I want to find the maximum number of a numeric attribute of my model, I can do
max_num = my_objects_arr.maximum(:numeric_attr)
but how would I find the maximum number of attributes whose values occur exactly twice in my array? That is, let's say my objects array has three objects
obj1 - numeric_attr = 3
obj2 - numeric_attr = 3
obj3 - numeric_attr = 4
The maximum of the attributes above that occur exactly twice would be "3". Although "4" is the maximum of all attributes, it only occurs once in the array.
array = [1, 2, 3, 2, 3, 4]
array.group_by { |e| e } # group_by(&:itself) since 2.3
.select { |_, v| v.count == 2 }
.keys
.max
#⇒ 3
For objects and attributes:
my_objects_arr.group_by { |o| o.numeric_attr }
.select { |_, v| v.count == 2 }
.keys
.max
To get the objects themselves:
my_objects_arr.group_by { |o| o.numeric_attr }
.select { |_, v| v.count == 2 }
.max_by(&:first)
.last
Since you are using rails calculations e.g. #maximum this should work for you
my_objects_arr
.group(:numeric_attr)
.having("count(numeric_attr) = 2")
.maximum(:numeric_attr)
This will find the maximum value of numeric_attr by grouping them by the numeric_attr and selecting the numeric_attr that have exactly 2
SQL estimation
SELECT
MAX(numeric_attr)
FROM
[SOME TABLE]
GROUP BY
numeric_attr
HAVING
COUNT(numeric_attr) = 2
arr = [1, 2, 3, 2, 3, 4]
arr.each_with_object(Hash.new(0)) {|n,h| h[n] += 1}.select {|_,nbr| nbr == 2}.keys.max
#=> 3
This uses the form of Hash::new that creates a hash h with a default value of zero. That means that if h does not have a key k, h[k] returns zero (without altering the hash). This refers to the method Hash#[], not to be confused with Hash#[]=.

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

How would I order a set of objects based on using <=> on an array attribute?

I have a set of 2 or more objects that I'd like to order. I had been doing it like this:
card.max_by{|strength| strength.score
Where score was an integer score I had computed given some arbitrary rules. I knew this would be something I would refactor, so now I am doing so. And the "clean" way to give a score to a hand is to give it an array of values like
foo.score = [9,3,nil,4]
And compare it to another hand which might have an array like
bar.score = [5,10,12,12]
And foo <=> bar would tell me that foo is the greater array and so it should be returned by max_by. The problem is that max_by apparently won't make comparisons on arrays. Is there another way I can do this to sort by the array value?
max_by works with array "attributes" just fine:
# Phrogz's example
Hand = Struct.new(:score)
hands = [
Hand.new([9,3,0,4]),
Hand.new([8,8,8,8]),
Hand.new([5,10,12,12]),
Hand.new([1,99,99,99])
]
#
hands.max_by(&:score) # => #<struct Hand score=[9, 3, 0, 4]>
However if the arrays can contain nils or other values that don't compare with each other, <=> could return nil and max_by could fail.
If it's just array-based ordering that you want (you really do want the spaceship operator) and you want to find the 'biggest' by sorting, then:
Hand = Struct.new(:score)
hands = [
Hand.new([9,3,0,4]),
Hand.new([8,8,8,8]),
Hand.new([5,10,12,12]),
Hand.new([1,99,99,99])
]
biggest = hands.sort_by(&:score).last
p biggest
#=> #<struct Hand score=[9, 3, 0, 4]>
If you really only need to find the largest hand, however, the following will be more efficient than ordering the entire array:
biggest = hands.inject do |max,hand|
if (max.score <=> hand.score) == -1
hand
else
max
end
end
p biggest
#=> #<struct Hand score=[9, 3, 0, 4]>
Edit: Reading your comment, if you really need multiple values that match, I would do this:
Hand = Struct.new(:name,:score) do
MAX_SCORE_PART = 13 # 13 ranks in a suit
def numeric_score
value = 0
score.each_with_index do |part,i|
value += part.to_i * MAX_SCORE_PART**(score.length-i-1)
end
value
end
end
hands = [
Hand.new('Bob', [9,3,nil,4] ),
Hand.new('Jim', [8,8,8,8] ),
Hand.new('Foo', [5,10,12,12]),
Hand.new('Sam', [1,13,13,13]),
Hand.new('Zak', [9,3,0,4] ),
]
require 'pp'
by_score = hands.group_by(&:numeric_score)
pp by_score
#=> {20284=>
#=> [#<struct Hand name="Bob", score=[9, 3, nil, 4]>,
#=> #<struct Hand name="Zak", score=[9, 3, 0, 4]>],
#=> 19040=>[#<struct Hand name="Jim", score=[8, 8, 8, 8]>],
#=> 12843=>[#<struct Hand name="Foo", score=[5, 10, 12, 12]>],
#=> 4576=>[#<struct Hand name="Sam", score=[1, 13, 13, 13]>]}
pp by_score[by_score.keys.max]
#=> [#<struct Hand name="Bob", score=[9, 3, nil, 4]>,
#=> #<struct Hand name="Zak", score=[9, 3, 0, 4]>]
For an inject-based implementation:
def numeric_score
score.enum_for(:inject,0).with_index do |(val,part),i|
val += part.to_i * MAX_SCORE_PART**(score.length-i-1)
end
end

Resources