Feel like the title summed it up pretty well. I have two large arrays, all containing ids.
One is the old_list and one is the current_list. What I would like to do is this:
delete all the values in the old_list, that are not present in the current_list
if the value in the current_list is present in the old_list don't do anything
if the value in current_list is new then create it and add it to the old_list
This is set as a background job that updates every 4 hours. Thus I want to see if any new value have appeared, or been removed since I last checked.
Here is what I have currently, which is not complete:
twitter.follower_ids("#{uid}").each do |f_id|
# unless user already has follower id saved
unless followers.map(&:follower_id).include?(f_id.to_s)
followers.create do |follower|
follower.follower_id = f_id
end
end
end
You need to do the below Set operation :
(old_list & current_list) | current_list
Example :
old_v = [1,2,43]
new_v = [1,11,21]
(old_v & new_v) | new_v # => [1, 11, 21]
Array#& and Array#|.
Related
I do the following so I am able to group all LineItem's together by count and display the LineItem by count along with the vendor_name
line_items = LineItem.all
vendor_line_items = line_items.group(:vendor_name).select('COUNT(*) as count', 'vendor_name').order('count desc')
My issue is that I am only able to receive the following params: id: nil, vendor_name: "name_here"
Is there a way to accomplish the same thing but allow all params from the model to be passed?
You can't select the rest of the columns since you have different values for each coulmn inside the group (like... if you have 2 LineItem in the same group, which ID do you expect to have?)
You could apply aggregate functions (like COUNT, MAX, MIN, etc) to other columns on the SELECT to tell the database which columns you want for each column I guess.
Personally, I would first get the groups ordered by count and then do more queries when needed to fetch the actual record for the groups.
counts = LineItem.group(:vendor_name).count
# counts should be something like: {vendor_1: X, vendor_2, Y, vendor_3: Z}
# order the vendors using the count for each vendor
ordered_vendors = counts.keys.sort_by { |ven| counts[ven] }
ordered_vendors.each do |vendor|
# do something with each vendor, fetch LineItems, etc
end
The reason why you only see the count and the vendor name is because that is all you are grouping by. Suppose in the database, you have 5 different Vendor A shown below.
vendor_name | product_name
-----------------------------
Vendor A | test
Vendor A | test2
Vendor A | test3
Vendor A | test4
Vendor A | test5
...
When you run your query, SQL will not know what to display for product_name as the group_by will only show 1 row instead of 5. Have a read about it here.
To achieve this you will need to either to group by the other columns too or use a min/max select to pick a value to display. Here is an example:
vendor_line_items = LineItem.select('COUNT(*) AS count', 'vendor_name', 'MAX(product_name)').group(:vendor_name).order('count DESC')
Now each of those results, you can call the attributes method.
Which will give you the following hash:
vendor_line_items.each do |x|
result = x.attributes
# Here result will be a hash.
# {"count" => 5, "vendor_name" => "Vendor A", "product_name" => "test5"}
end
(Not accepted answer unless a better way is received)
I did:
vendor_line_items = Vendor.joins(:line_items).group(:id).order('COUNT(line_items.id) DESC')
This gives me what I want by ordering the results by vendor.line_items.count and allowing me to get all of the associations to display any param I want.
I assume this way is much slower than what I was previously doing as it fetches all records and then on the front end goes through associations to get more records.
In the original way I was doing this. It is what I want minus an extra parameter that I would want the SUM of. The parameter is a decimal attribute. In the same way I count the LineItem that have the same vendor_name, I want to sum of the LineItem.attribute that share the same vendor_name.
Better Answer:
LineItem.select(:vendor_name, 'sum(line_item_revenue) as line_item_revenue', 'COUNT(*) as count').group(:vendor_name)
This seems to get me what I want with less queries (i believe) --- correct me if I am wrong on the queries.
I am quite confused about your code and your expectation. You are selecting the COUNT but the expected result is id instead of count?
If you want to group by vendor_name and show the count of group_by you can try
line_items.group(:vendor_name).count
I'm going to preface that I'm still learning ruby.
I'm writing a script to parse a .csv and identify possible duplicate records in the data-set.
I have a .csv file with headers, so I'm parsing the data so that I can access each row using a header title as such:
#contact_table = CSV.parse(File.read("app/data/file.csv"), headers: true)
# Prints all last names in table
puts contact_table['last_name']
I'm trying to iterate over each row in the table and identify if the last name I'm currently iterating over is similar to the next last name, but I'm having trouble doing this. I guess the way I'm handling it is as if it's an array, but I checked the type and it's a CSV::Row.
example (this doesn't work):
#contact_table.each_with_index do |c, i|
puts "first contact is #{c['last_name']}, second contact is #{c[i + 1]['last_name']}"
end
I realized this doesn't work like this because the table isn't an array, it's a CSV::Row like I previously mentioned. Is there any method that can achieve this? I'm really blanking right now.
My csv looks something like this:
id,first_name,last_name,company,email,address1,address2,zip,city,state_long,state,phone
1,Donalt,Canter,Gottlieb Group,dcanter0#nydailynews.com,9 Homewood Alley,,50335,Des Moines,Iowa,IA,515-601-4495
2,Daphene,McArthur,"West, Schimmel and Rath",dmcarthur1#twitter.com,43 Grover Parkway,,30311,Atlanta,Georgia,GA,770-271-7837
#contact_table should be a CSV::Table which is a collection of CSV::Rows so in this:
#contact_table.each_with_index do |c, i|
...
end
c is a CSV::Row. That's why c['last_name'] works. The problem is that here:
c[i + 1]['last_name']
you're looking at c (a single row) instead of #contact_table, if you said:
#contact_table[i + 1]['last_name']
then you'd get the next last name or, when c is the last row, an exception because #contact_table[i+1] will be nil.
Also, inside the iteration, c is the current (or (i+1)th) row and won't always be the first.
What is your use case for this? Seems like a school project?
I recommend for_each instead of parse (see this comparison). I would probably use a Set for this.
Create a Set outside of the scope of parsing the file (i.e., above the parsing code). Let's call it rows.
Call rows.include?(row) during each iteration while parsing the file
If true, then you know you have a duplicate
If false, then call rows.add(row) to add the new row to the set
You could also just fill your set with an individual value from a column that must be distinct (e.g., row.field(:some_column_name)), such as email or phone number, and do the same inclusion check for that.
(If this is for a real app, please don't do this. Use model validations instead.)
I would use #read instead of #parse and do something like this:
require 'csv'
LASTNAME_INDEX = 2
data = CSV.read('data.csv')
data[1..-1].each_with_index do |row, index|
puts "Contact number #{index + 1} has the following last name : #{row[LASTNAME_INDEX]}"
end
#~> Contact number 1 has the following last name : Canter
#~> Contact number 2 has the following last name : McArthur
I've flirted with learning web dev in the past and haven't had the time as I am a full time Business Student.
I started digging back in today and decided to take a break from the learning and practice what I've learned today by writing a simple program that allows the user to enter in their bills and will eventually calculate how much disposable income they have after their bills are paid each month.
My problem is that the program runs through perfectly, the loop is continuing/exiting when it should, but either the program is not storing the users input in the hash like I'm wanting it to or it's not displaying all the bills entered as it should. Here is my program:
# This program allows you to assign monthly payments
# to their respective bills and will automatically
# calculate how much disposable income you have
# after your bills are paid
# Prompts user to see if they have any bills to enter
puts "Do you have any bills you would like to enter, Yes or No?"
new_bill = gets.chomp.downcase
until new_bill == 'no'
# Creates a hash to store a key/value pair
# of the bill name and the respection payment amount
bills = {}
puts "Enter the bill name: "
bill_name = gets.chomp
puts "How much is this bill?"
pay_amt = gets.chomp
bills[bill_name] = pay_amt
puts "Would you like to add another bill, Yes or No?"
new_bill = gets.chomp.downcase
end
bills.each do |bill_name, pay_amt|
puts "Your #{bill_name} bill is $#{pay_amt}."
end
My questions are:
Is my hash set up properly to store the key/value pairs from the users input?
If not, how can I correct it?
I'm getting only the last bill that was entered by the user. I've tried several bills at a time but only getting the last entry.
As I stated, I'm a noob but I'm extremely ambitious to learn. I've referred to to the ruby docs on hashes to see if there is an error in my code but was able to locate a solution (still finding my way around ruby docs).
Any help is appreciated! Also, if you have any recommendations on ways I can make my code more efficient, could you point me in the direction where I can obtain the appropriate information to do so?
Thank you.
Edit:
The main question has been answered. This is a follow up question to the same program - I'm getting an error message budget_calculator.rb:35:in -': Hash can't be coerced into Float (TypeError)
from budget_calculator.rb:35:in'
From the following code (keep in mind of the program above) -
# Displays the users bills
bills_hash.each {|key,value| puts "Your #{key} bill is $#{value}."}
# Get users net income
puts "What is your net income?"
net_income = gets.chomp.to_f
#Calculates the disposable income of the user
disposable_income = net_income - bills_hash.each {|value| value}
puts disposable_income
I understand the error is appearing from this line of code:
disposable_income = net_income - bills_hash.each {|value| value}
I'm just not understanding why this is unacceptable. I'm trying to subtract all of the values in the hash (pay_amt) from the net income to derive the disposable income.
This is the part that's getting you:
bills = {}
You're resetting the hash every time the program loops. Try declaring bills at the top of the program.
As to your second question about bills_hash, it's not working because the program is attempting to subtract a hash from a float. You've got the right idea, but the way it's set up, it's not going to just subtract each key from the net_income in turn.
The return value of #each is the original hash that you were looping over. You can see this if you open IRB and type
[1,2,3].each {|n| puts n}
The block is evaluated for each element of the list, but the final return value is the original list:
irb(main):007:0> [1,2,3].each {|n| puts n}
1
2
3
=> [1, 2, 3] # FINAL RETURN VALUE
So according to the order of operations, your #each block is iterating, then returning the original bills_hash hash, and then trying to subtract that hash from net_income, which looks like this (assuming my net_income is 1000):
1000 - {rent: 200, video_games: 800}
hence the error.
There are a couple ways you could go about fixing this. One would be to sum all of the values in bills_hash as its own variable, then subtract that from the net_income:
total_expenditures = bills_hash.values.inject(&:+) # sum the values
disposable_income = net_income - total_expenditures
Using the same #inject method, this could also be done in one function call:
disposable_income = bills_hash.values.inject(net_income, :-)
# starting with net_income, subtract each value in turn
See the documentation for Enumerable#inject.
It's a very powerful and useful method to know. But make sure you go back and understand how return values work and why the original setup was raising an exception.
I am attempting to make a batch process which will take a parameter that specifies the number of background workers, and split a collection into that many arrays. For example if
def split_for_batch(number_of_workers)
<code>
end
array = [1,2,3,4,5,6,7,8,9,10]
array.split_for_batch(3)
=> [[1,2,3],[4,5,6],[7,8,9,10]]
the thing is that I don't want to have to load all of the users into memory at once because it is a batch. What I have now is
def initialize_audit_run_threads
total_users = tax_audit_run_users.count
partition_size = (total_users / thread_count).round
tax_audit_run_users.in_groups_of(partition_size).each do |group|
thread = TaxAuditRunThread.create(:tax_audit_run_id => id, :status_code => 1)
group.each do |user|
if user
user.tax_audit_run_thread_id = thread.id
user.save
end
end
end
where the thread_count is an attribute of the class that determines the number of background workers. Currently this code will create 4 threads rather than 3. I have also tried using find_in_batches but I am having the same problem where if I have 10 tax_audit_run_users in the array I have no way to let the last worker know to process the last record. Is there a way in ruby or rails to divide a collection into n parts and have the last part include the stragglers?
How to split (chunk) a Ruby array into parts of X elements?
You will of course need to modify it a bit to add the last chunk if it's less than the chunk size, or not, up to you.
I want to check by an unique ID whether a record is already created. If it is, I want to add 1 to the amount attribute. If it is not, I want to create it and set the amount attribute to 1.
I already have this: ##have = current_user.haves.create_or_update_by_id(params[:have]) but I'm not quite sure how I would set the amount right.
Thanks
Use ActiveRecord's increment method (Documentation):
#have = current_user.haves.find_or_initialize_by_id(params[:have])
#have.increment(:amount)
#have.save
It initializes amount attribute to zero if nil and adds the value passed as by (default is 1). If not nil, it just increment the attribute value.
I would probably split this into two operations.
#have = current_user.haves.find_or_initialize_by_id(params[:have]) do |h|
h.amount = 0
end
#have.amount += 1
#have.save
The first statemnt will find the record by its unique ID or initialize a new record with the provided ID and amount set to 0. The block is only run on initialize, not on find. Then we increment the amount no matter what the state was originally and save the record back to the db.