Related
Here's my stat model.
Stat(id: integer, points: float, user_id: integer, match_id: integer, team_id: integer)
For team model
Team(id: integer, name: string)
I'm getting error on teams.name part here's the error.
#<ActiveRecord::StatementInvalid: Mysql2::Error: Expression #1 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'db-name.teams.name' which is not functionally dependent on columns in GROUP BY clause;
Sample stat:
{id: 1, points: 2, user_id: 1, match_id: 1, team_id: 1}
{id: 2, points: 3, user_id: 3, match_id: 1, team_id: 2}
{id: 3, points: 4, user_id: 1, match_id: 2, team_id: 1}
My current code:
sample = Stat
.joins(:user)
.joins(:team)
.select('teams.name as team_name, users.id as user_id, match_id, SUM(points) as points')
.where(user_id: params[:user_id])
.group(:match_id)
.where.not(match_id: nil)
.order("match_id DESC")
.limit(10)
I have a list of 10 items -- it is an array of hashes.
[{ id: 1, name: 'one'}, { id: 2, name: 'two' } .. { id: 10, name: 'ten' }]
I also have a random number of containers -- let's say 3, in this case. These containers are hashes with array values.
{ one: [], two: [], three: [] }
What I want to do, is iterate over the containers and drop 2 items at a time resulting in:
{
one: [{id:1}, {id:2}, {id:7}, {id:8}],
two: [{id:3}, {id:4}, {id:9}, {id:10}],
three: [{id:5}, {id:6}]
}
Also, if the item list is an odd number (11), the last item is still dropped into the next container.
{
one: [{id:1}, {id:2}, {id:7}, {id:8}],
two: [{id:3}, {id:4}, {id:9}, {id:10}],
three: [{id:5}, {id:6}, {id:11}]
}
note: the hashes are snipped here so it's easier to read.
My solution is something like this: (simplified)
x = 10
containers = { one: [], two: [], three: [] }
until x < 1 do
containers.each do |c|
c << 'x'
c << 'x'
end
x -= 2
end
puts containers
I'm trying to wrap my head around how I can achieve this but I can't seem to get it to work.
Round-robin pair distribution into three bins:
bins = 3
array = 10.times.map { |i| i + 1 }
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array.
each_slice(2). # divide into pairs
group_by. # group into bins
with_index { |p, i| i % bins }. # round-robin style
values. # get rid of bin indices
each(&:flatten!) # join pairs in each bin
Completely different approach, stuffing bins in order:
base_size, bins_with_extra = (array.size / 2).divmod(bins)
pos = 0
bins.times.map { |i|
length = 2 * (base_size + (i < bins_with_extra ? 1 : 0)) # how much in this bin?
array[pos, length].tap { pos += length } # extract and advance
}
# => [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
If you absolutely need to have this in a hash,
Hash[%i(one two three).zip(binned_array)]
# => {:one=>[1, 2, 7, 8], :two=>[3, 4, 9, 10], :three=>[5, 6]}
The lovely (but likely not as performant) solution hinted at by Stefan Pochmann:
bins.times.with_object(array.to_enum).map { |i, e|
Array.new(2 * (base_size + (i < bins_with_extra ? 1 : 0))) { e.next }
}
This is just to show a different approach (and I would probably not use this one myself).
Given an array of items and the containers hash:
items = (1..10).to_a
containers = { one: [], two: [], three: [] }
You could dup the array (in order not to modify the original one) and build an enumerator that cycles each_value in the hash:
array = items.dup
enum = containers.each_value.cycle
Using the above, you can shift 2 items off the array and push them to the next container until the array is emtpy?:
enum.next.push(*array.shift(2)) until array.empty?
Result:
containers
#=> {:one=>[1, 2, 7, 8], :two=>[3, 4, 9, 10], :three=>[5, 6]}
You can use Enumerable#each_slice to iterate over a range from 0 to 10 in 3s and then append to an array of arrays:
containers = [
[],
[],
[]
]
(1...10).each_slice(3) do |slice|
containers[0] << slice[0]
containers[1] << slice[1]
containers[2] << slice[2]
end
p containers
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
I need to sort a mixed array in Ruby. It has Fixnums and Strings
ar = [1, "cool", 3, "story", 4, "bro"]
I want the Fixnums to take precedence over the strings and don't care what order the strings are in.
ar = [1,3,4,"cool","story","bro"]
I've tried writing a method for class Array
Class Array
def mixed_sort
self.sort do |a,b|
if a.class == Fixnum and b.class != a.class
-1
else
a <=> b
end
end
end
end
I thought I might just pass a block to the Array#sort method. However this method still throws an error before hitting the block
[1] pry(main)> [1, "11", '12', 3, "cool"].mixed_sort
ArgumentError: comparison of String with 3 failed
from /config/initializers/extensions/array.rb:3:in `sort'
I would do as below using Enumerable#grep:
ar = [1, "cool", 3, "story", 4, "bro"]
ar.grep(Fixnum).sort + ar.grep(String)
# => [1, 3, 4, "cool", "story", "bro"]
If you want also to sort the strings, do as below :
ar = [1, "cool", 3, "story", 4, "bro"]
ar.grep(Fixnum).sort + ar.grep(String).sort
# => [1, 3, 4, "bro", "cool", "story"]
a, b = [1, "cool", 3, "story", 4, "bro"].partition(&Fixnum.method(:===))
a.sort + b #=> [1, 3, 4, "cool", "story", "bro"]
Practically, I have always needed to sort by Fixnum first and followed by String elements:
ar.sort_by { |n| n.to_s } # => [1, 3, 4, "bro", "cool", "story"]
This only converts an element into string within the block for comparison but returns the Fixnum in it's original state
I have a notifications table where I email out reports to people based on the frequency they've selected.
If an email address exists across multiple questions and has the same frequency...then I need to group them together so I can send one report for both questions.
[
#<Notification id: 1, question_id: 58, email: "john#example.com", frequency: "daily">,
#<Notification id: 2, question_id: 25, email: "john#example.com", frequency: "daily">,
#<Notification id: 3, question_id: 47, email: "john#example.com", frequency: "monthly">,
#<Notification id: 3, question_id: 19, email: "frank#example.org", frequency: "monthly">
]
So in my example data, 3 reports would be sent:
1 to john#example.com for question's 58 and 25 on a daily basis
1 to john#example.com for question 47 on a monthly basis
1 to frank#example.org for question 19 on a monthly basis
I may not be explaining this very well, so let me know if something needs clarification.
You can achieve this with a regular group_by:
#notifications = your_method_to_retrieve_notifications
#notifications = #noticications.group_by do |notif|
notif.ferquency + ':' + notif.email
end
This will group your notifications like this:
#notifications = {
'daily:john#example.com' => [<Notification id: 1>, #etc.],
'monthly:john#example.com' => [# list of notifications],
'monthly:frank#example.org' => [# list of notif]
}
If you want only an array of list of notifications grouped by frequency & email, use the above method and add this:
#notifications = your_method_to_retrieve_notifications
#notifications = #noticications.group_by do |notif|
notif.ferquency + ':' + notif.email
end.values
# returns Array of arrays like:
[
[<Notification id: 1 frequency: "daily", email "a#b.com">,<Notification id: 2 frequency: "daily", email "a#b.com">],
[<Notification id: 3 frequency: "daily", email "xx#yy.com">],
[<Notification id: 4 frequency: "monthly", email "a#b.com">,<Notification id: 5 frequency: "monthly", email "a#b.com">],
]
Let’s say I have this array with shipments ids.
s = Shipment.find(:all, :select => "id")
[#<Shipment id: 1>, #<Shipment id: 2>, #<Shipment id: 3>, #<Shipment id: 4>, #<Shipment id: 5>]
Array of invoices with shipment id's
i = Invoice.find(:all, :select => "id, shipment_id")
[#<Invoice id: 98, shipment_id: 2>, #<Invoice id: 99, shipment_id: 3>]
Invoices belongs to Shipment.
Shipment has one Invoice.
So the invoices table has a column of shipment_id.
To create an invoice, I click on New Invoice, then there is a select menu with Shipments, so I can choose "which shipment am i creating the invoice for". So I only want to display a list of shipments that an invoice hasn't been created for.
So I need an array of Shipments that don't have an Invoice yet. In the example above, the answer would be 1, 4, 5.
a = [2, 4, 6, 8]
b = [1, 2, 3, 4]
a - b | b - a # => [6, 8, 1, 3]
First you would get a list of shipping_id's that appear in invoices:
ids = i.map{|x| x.shipment_id}
Then 'reject' them from your original array:
s.reject{|x| ids.include? x.id}
Note: remember that reject returns a new array, use reject! if you want to change the original array
Use substitute sign
irb(main):001:0> [1, 2, 3, 2, 6, 7] - [2, 1]
=> [3, 6, 7]
Ruby 2.6 is introducing Array.difference:
[1, 1, 2, 2, 3, 3, 4, 5 ].difference([1, 2, 4]) #=> [ 3, 3, 5 ]
So in the case given here:
Shipment.pluck(:id).difference(Invoice.pluck(:shipment_id))
Seems a nice elegant solution to the problem. I've been a keen follower of a - b | b - a, though it can be tricky to recall at times.
This certainly takes care of that.
Pure ruby solution is
(a + b) - (a & b)
([1,2,3,4] + [1,3]) - ([1,2,3,4] & [1,3])
=> [2,4]
Where a + b will produce a union between two arrays
And a & b return intersection
And union - intersection will return difference
The previous answer here from pgquardiario only included a one directional difference. If you want the difference from both arrays (as in they both have a unique item) then try something like the following.
def diff(x,y)
o = x
x = x.reject{|a| if y.include?(a); a end }
y = y.reject{|a| if o.include?(a); a end }
x | y
end
This should do it in one ActiveRecord query
Shipment.where(["id NOT IN (?)", Invoice.select(:shipment_id)]).select(:id)
And it outputs the SQL
SELECT "shipments"."id" FROM "shipments" WHERE (id NOT IN (SELECT "invoices"."shipment_id" FROM "invoices"))
In Rails 4+ you can do the following
Shipment.where.not(id: Invoice.select(:shipment_id).distinct).select(:id)
And it outputs the SQL
SELECT "shipments"."id" FROM "shipments" WHERE ("shipments"."id" NOT IN (SELECT DISTINCT "invoices"."shipment_id" FROM "invoices"))
And instead of select(:id) I recommend the ids method.
Shipment.where.not(id: Invoice.select(:shipment_id).distinct).ids
When dealing with arrays of Strings, it can be useful to keep the differences grouped together.
In which case, we can use Array#zip to group the elements together and then use a block to decide what to do with the grouped elements (Array).
a = ["One", "Two", "Three", "Four"]
b = ["One", "Not Two", "Three", "For" ]
mismatches = []
a.zip(b) do |array|
mismatches << array if array.first != array.last
end
mismatches
# => [
# ["Two", "Not Two"],
# ["Four", "For"]
# ]
s.select{|x| !ids.include? x.id}