Rails array index changing after delete_at - ruby-on-rails

There are three categories
categories = ["Category test 1 2", "Category test 1 3", "Category test 1 4"]
When I delete Category test 1 2, the array becomes ["Category test 1 3", "Category test 1 4"] and at this time value of i is 0 but in next iteration value of i is 1 and the value at index position is Category test 1 4 so it is not taking value Category test 1 3 due to which hidden categories of Category test 1 3 is not displaying.
I used method categories.delete_at(i)
I want that in next iteration , it should take value Category test 1 3 not Category test 1 4.

You can put logic here eg.
temp1, temp2 = [1,2,3,4,5,5,6,7], []
temp3 = temp1.clone
4.times { |h| temp2 = temp1.clone; temp2.delete_at(h); temp3.delete((temp1 - temp2)[0]) }
temp1 = temp3.clone
=> [5, 5, 6, 7]

You can set i = 0 just before categories.delete_at(i) if every time you want to delete the first category
i = 0
categories.delete_at(i)
Hope that helps!

As I understood you wanted to delete all elements of Array one by one then you can use Array#delete_if
categories.delete_if{|a| p a}
"Category test 1 2"
"Category test 1 3"
"Category test 1 4"
=> []
If your concern is to delete "Category test 1 3" after "Category test 1 2" only and last element "Category test 1 4" should remain as it is..
> categories.each{|a| p categories.delete_at(0)}
"Category test 1 2"
"Category test 1 3"
=> ["Category test 1 4"]
Let me explain you your issue. You may be doing something like this:
categories.each_with_index do |e,i|
categories.delete_at(i)
end
In first time loop index will be 0 so it delete first element "Category 1 2" from the categories and it remain something like:
["Category test 1 3", "Category test 1 4"]
Now in second loop index will be 1 and in remaining Array elements at index 1 , "Category test 1 4" is stated. so it delete this element instead "Category test 1 3" which is stated on 0 index now..
I hope it helps you. If you have any further query then feel free to comment.

Related

Extracting transaction data from variable length arrays

I have built a small program in ruby that collects table data from my own PDF bank statements. This does so by scanning each PDF statement for tables and then filters out for transactional line item patterns.
Everything is working great and I have managed to collect an array of line items as an array of string arrays. Getting an array of keyed objects would be better but a bit tricky with the format of the statements.
The issue is that the line items have different lengths, so it's kind of tricky to always know the location of the correct values to map.
For example:
["Transaction 1", "1.00"]
["Transaction 2", "Hello World", "3.00"]
["Transaction 3", "Hello World", "feeffe", "5.00"]
["Transaction 4", "Hello World", "feeffe", "5.00", "12.00"]
["Transaction 5", "Hello World # 10.00", "feeffe", "10.00", "12.00"]
The line items only range in between 2 and 5 array items normally.
Is there an efficient/accurate way to map the above to:
{ description: "Transaction 1", amt: "1.00"}
{ description: "Transaction 2 - Hello World", amt: "3.00"}
{ description: "Transaction 3 - Hello World - feeffe", amt: "5.00"}
{ description: "Transaction 4 - Hello World - feeffe", amt: "5.00"}
{ description: "Transaction 5 - Hello World # 10.00 - feeffe", amt: "10.00"}
-Or is the only way to write IF conditions that looks at the array length and makes a "best guess" effort?
If you are having,
row = ["Transaction 2", "Hello World", "3.00"]
You can follow by doing,
{ description: row[0..-2].join(' - '), amt: row[-1] }
You have to further manipulate how these rows get iterated so further logic will vary.
update:
For condition updated specified later, it is seen to have row can have length 5 where actual amount is second last value.
data = (row.length == 5) ? [row[0..-3], row[-2]] : [row[0..-2], row[-1]]
{ description: data[0].join(' - '), amt: data[1] }
Assume your transaction is on a variable tr, i.e.
tr=["Transaction 5", "Hello World", "feeffe", "10.00", "12.00"]
I would first separtate this into those strings which look like an amount, and those which don't:
amounts,texts= tr.partition {|el| /^\d+[.]\d{2}/ =~ el}
Here you can check that !amounts.empty?, to guard agains transaction without amount. Now your hash could be
{
transaction_name: texts.first,
transaction_text: "#{texts[1]}#{amounts.size > 1 ? %( # #{amounts.first}) : ''}#{texts.size > 2 ? %( - #{texts.last}) : ''}",
amt: amounts.last
}
Try this regex:
"\K[^",\]]+
Here is Demo
If the number of items always determines the index of the amount element, you can do something like:
input = [
["Transaction 1", "1.00"],
["Transaction 2", "Hello World", "3.00"],
["Transaction 3", "Hello World", "feeffe", "5.00"],
["Transaction 4", "Hello World", "feeffe", "5.00", "12.00"],
["Transaction 5", "Hello World # 10.00", "feeffe", "10.00", "12.00"]
]
ROW_LENGTH_TO_AMOUNT_INDEX = {
2 => 1,
3 => 2,
4 => 3,
5 => 3,
}
def map(transactions)
transactions.map do |row|
amount_index = ROW_LENGTH_TO_AMOUNT_INDEX[row.length]
{
description: row[0],
amt: row[amount_index]
}
end
end
p map(input)
[{:description=>"Transaction 1", :amt=>"1.00"}, {:description=>"Transaction 2", :amt=>"3.00"}, {:description=>"Transaction 3", :amt=>"5.00"}, {:description=>"Transaction 4", :amt=>"5.00"}, {:description=>"Transaction 5", :amt=>"10.00"}]
Or, perhaps something like this?
MAPPERS = {
2 => lambda { |row| { description: row[0], amt: row[1]} },
3 => lambda { |row| { description: row[0], amt: row[2]} },
4 => lambda { |row| { description: row[0], amt: row[3]} },
5 => lambda { |row| { description: row[0], amt: row[3]} }
}
def map(transactions)
transactions.map do |row|
MAPPERS[row.length].call(row)
end
end
arr = [["Transaction 1", "1.00"],
["Transaction 2", "Hello World", "3.00"],
["Transaction 3", "Hello World", "feeffe", "5.00"]]
arr.map {|*first, last| { description: first.join(' - '), amt: last } }
#=> [{:description=>"Transaction 1", :amt=>"1.00"},
# {:description=>"Transaction 2 - Hello World", :amt=>"3.00"},
# {:description=>"Transaction 3 - Hello World - feeffe", :amt=>"5.00"}]

Ruby Find by column and row in a multidimensional array [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 8 years ago.
Improve this question
So here is what my array looks like
[["Date", "Patient 1", "Patient 2", "Patient 3"],
["8/1/2014",0,0,0]
["8/2/2014",0,0,0]
["8/3/2014",0,0,0]]
I need to be able to find the index for "Patient 2" - "8/2/2014" which would of course be array[2][2] so that I can then change it's value from 0 to something else. How do I find that using the column and row names I've layed out?
Many thanks.
To find the row, you can use find and compare the value to the first element in each row:
matrix.find { |x| x[0] == "8/2/2014" }
# => ["8/2/2014", 0, 0, 0]
To find the column index, you can use index on the first array:
matrix[0].index("Patient 2")
# => 2
You can wrap it in a method:
def change_matrix(matrix, row, col, new_val)
matrix.find { |x| x[0] == row }[matrix[0].index(col)] = new_val
end
change_matrix(matrix, '8/2/2014', 'Patient 2', 5)
matrix
# => [["Date", "Patient 1", "Patient 2", "Patient 3"],
# ["8/1/2014", 0, 0, 0],
# ["8/2/2014", 0, 5, 0],
# ["8/3/2014", 0, 0, 0]]
You can do something like:
a = [["Date", "Patient 1", "Patient 2", "Patient 3"],["8/1/2014",0,0,0],["8/2/2014",0,0,0],["8/3/2014",0,0,0]]
b = a.transpose
print a[0][1] + " " + b[0][1]
Demo: http://runnable.com/U-oULwIJWFYZpeOx/transpose-for-ruby

How to skip over null values in Ruby hashes

I have an array in which each array item is a hash with date values, as shown in my example below. In actuality, it is longer and there are about 20 dates per item instead of 3. What I need to do is get the date interval values for each item (that is, how many days between each date value), and their intervals' medians. My code is as follows:
require 'csv'
require 'date'
dateArray = [{:date_one => "May 1", :date_two =>"May 5", :date_three => " "}, {:date_one => "May 10", :date_two =>"May 10", :date_three => "May 20"}, {:date_one => "May 6", :date_two =>"May 11", :date_three => "May 12"}]
public
def median
sorted = self.sort
len = sorted.length
return (sorted[(len - 1) / 2] + sorted[len / 2]) / 2.0
end
puts dateIntervals = dateArray.map{|h| (DateTime.parse(h[:date_two]) - DateTime.parse(h[:date_one])).to_i}
puts "\nMedian: "
puts dateIntervals.median
Which returns these date interval values and this median:
4
0
5
Median: 4
However, some of these items' values are empty, as in the first item, in its :date_three value. If I try to run the same equations for the :date_three to :date_two values, as follows, it will throw an error because the last :date_three value is empty.
It's okay that I can't get that interval, but I would still would need the next two items date intervals (which would be 10 and 1).
How can I skip over intervals that return errors when I try to run them?
I would recommend adding helper functions that can deal with the types of inputs you're expecting. For instance:
def date_diff(date_one, date_two)
return nil if date_one.nil? || date_two.nil?
(date_one - date_two).to_i
end
def str_to_date(input_string)
DateTime.parse(input_string)
rescue
nil
end
dateArray.map{|h| date_diff(str_to_date(h[:date_three]), str_to_date(h[:date_two])) }
=> [nil, 10, 1]
dateArray.map{|h| date_diff(str_to_date(h[:date_three]), str_to_date(h[:date_two])) }.compact.median
=> 5.5
The bonus here is that you can then add unit tests for the individual components so that you can easily test edge cases (nil dates, empty string dates, etc).
In your map block, you can just add a check to make sure the values aren't blank
dateIntervals = dateArray.map{ |h|
(DateTime.parse(h[:date_two]) - DateTime.parse(h[:date_one])).to_i unless any_blank?(h)
}
def any_blank?(h)
h.each do |k, v|
return true if v == " "
end
end
I would first just filter out the empty values first (I check if the string consists entirely of whitespace or is empty), then compare the remaining values using your existing code. I added a loop which will compare all values in the sequence to the next value.
dateArray = [
{ date_one: "May 1", date_two: "May 5", date_three: " ", date_four: "" },
{ date_one: "May 10", date_two: "May 10", date_three: "May 20" }
]
intervals = dateArray.map do |hash|
filtered = hash.values.reject { |str| str =~ /^\s*$/ }
(0...filtered.size-1).map { |idx| (DateTime.parse(filtered[idx+1]) - DateTime.parse(filtered[idx])).to_i }
end
# => [[4], [0, 10]]

How to parse values in multidimensional array and select one over the other based on condition?

I have an array that behaves like a multidimensional array through spaces, like:
"roles"=>["1 editor 0", "1 editor 1", "2 editor 0", "2 editor 1", "14 editor 0", "15 editor 0"], "commit"=>"Give Access", "id"=>"3"}
Each array value represents [category_id, user.title, checked_boolean], and comes from
form
<%= hidden_field_tag "roles[]", [c.id, "editor", 0] %>
<%= check_box_tag "roles[]", [c.id, "editor", 1 ], !!checked %>
which I process it using splits
params[:roles].each do |role|
cat_id = role[0].split(" ")[0]
title = role.split(" ")[1]
checked_boolean = role.split(" ")[2]
end
Given the array at the top, you can see that the "Category 1" & "Category 2" is checked, while "Cat 14" and "Cat 15" are not.
I would like to compare the values of the given array, and if both 1 & 0 exists for a given category_id, I would like to get rid of the value with "checked_boolean = 0". This way, if the boolean is a 1, I can check to see if the Role already exists, and if not, create it. And if it is 0, I can check to see if Role exists, and if it does, delete it.
How would I be able to do this? I thought of doing something like params[:roles].uniq but didn't know how to process the uniq only on the first split.
Or is there a better way of posting the "unchecks" in Rails? I've found solutions for processing the uncheck action for simple checkboxes that passes in either true/false, but my case is different because it needs to pass in true/false in addition to the User.Title
Let's params[:roles] is:
["1 editor 0", "1 editor 1", "2 editor 0", "2 editor 1", "14 editor 0", "15 editor 0"]
The example of the conversion and filtering is below:
roles = params[:roles].map {| role | role.split " " }
filtered = roles.select do| role |
next true if role[ 2 ].to_i == 1
count = roles.reduce( 0 ) {| count, r | r[ 0 ] == role[ 0 ] && count + 1 || count}
count == 1
end
# => [["1", "editor", "1"], ["2", "editor", "1"], ["14", "editor", "0"], ["15", "editor", "0"]]
filtered.map {| role | role.join( ' ' ) }
Since the select method returns a new filtered role array, so result array you can see above. But of course you can still use and source params[:roles], and intermediate (after map method worked) versions of role array.
Finally you can adduce the result array into the text form:
filtered.map {| role | role.join( ' ' ) }
=> ["1 editor 1", "2 editor 1", "14 editor 0", "15 editor 0"]
majioa's solution is certainly more terse and a better use of the language's features, but here is my take on it with a more language agnostic approach. I have only just started learning Ruby so I used this as an opportunity to learn, but it does solve your problem.
my_array = ["1 editor 0", "1 editor 0", "1 editor 1", "2 editor 0",
"2 editor 1", "14 editor 0", "15 editor 0"]
puts "My array before:"
puts my_array.inspect
# As we're nesting a loop inside another for each loop
# we can't delete from the same array without confusing the
# iterator of the outside loop. Instead we'll delete at the end.
role_to_del = Array.new
my_array.each do |role|
cat_id, checked_boolean = role.split(" ")[0], role.split(" ")[2]
if checked_boolean == "1"
# Search through the array and mark the roles for deletion if
# the category id's match and the found role's checked status
# doesn't equal 1.
my_array.each do |s_role|
s_cat_id = s_role.split(" ")[0]
if s_cat_id != cat_id
next
else
s_checked_boolean = s_role.split(" ")[2]
role_to_del.push s_role if s_checked_boolean != "1"
end
end
end
end
# Delete all redundant roles
role_to_del.each { |role| my_array.delete role }
puts "My array after:"
puts my_array.inspect
Output:
My array before:
["1 editor 0", "1 editor 0", "1 editor 1", "2 editor 0", "2 editor 1", "14 editor 0",
"15 editor 0"]
My array after:
["1 editor 1", "2 editor 1", "14 editor 0", "15 editor 0"]

Sort by specific ids in ActiveRecord

I have inherited another programmer's Rails3 project, and I'm fairly new to rails overall. He's got a query that appears to sort by specific id's. Can somebody explain how this resolves in actual SQL? I think this code is killing the db and subsequently rails. I've tried to output it in the logger but can't seem to get the actual SQL to output even with config set to :debug. Searching heavily here (on SO) didn't turn up a clear explanation of how this query looks. The code looks like:
options = {
select: "SUM(1) AS num_demos, product_id ",
group: "product_id",
order: "num_demos ASC",
}
product_ids = Demo.where("state = 'waitlisted'").find(:all, options).collect{|d| d.product_id}
sort_product_ids = product_ids.collect{|product_id| "id = #{product_id}"}
Product.where(visible: true, id: product_ids).order(sort_product_ids.join(', '))
As far as I can see, the final line will create a query against the product table with an ORDER BY "id = 1, id = 3, ..." etc, which doesn't make a lot of sense to me. All clues appreciated.
A quick breakdown of what's going on, as it'll help you understand what to do for your replacement query.
options = {
select: "SUM(1) AS num_demos, product_id ",
group: "product_id",
order: "num_demos ASC",
}
product_ids = Demo.where("state = 'waitlisted'").find(:all, options).collect{|d| d.product_id}
This line will generate
SELECT SUM(1) as num_demos, product_id FROM "demos" WHERE (state = 'waitlisted') GROUP BY product_id
And returns an array of Demo objects, sorted by the count(*) of rows in the group, where only the product_id attribute has been loaded, and is available to you.
Next,
sort_product_ids = product_ids.collect{|product_id| "id = #{product_id}"}
results in a collection of product_ids mapped to the format "id = x". IE: If the previous result returned 10 results, with product_ids ranging from 1..10, sort_product_ids is now equivalent to ["id = 1", "id = 2", "id = 3", "id = 4", "id = 5", "id = 6", "id = 7", "id = 8", "id = 9", "id = 10"]
Finally,
Product.where(visible: true, id: product_ids).order(sort_product_ids.join(', '))
Selects all Products where the column visible is true, and their id is in the array of product_ids (which, as we found out earlier, is actually an array of Demo objects, not integers - this might be causing the query to fail). Then, it asks SQL to sort that result list by the sort_product_ids (sent in as a string "id = 1, id = 2, ... id = 10" instead of an array ["id = 1", "id = 2", ... "id = 10"]).
More info available at:
http://guides.rubyonrails.org/active_record_querying.html
http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html
To select and sort by a given array of ids you can use this
Product.where(visible: true, id: product_ids)
.order( "field(id,#{product_ids.join(',')})" )
If you are using PostgreSQL, consider to use WITH ORDINALITY, it is the fastest way compared to others. See this thread.
To apply this method to Ruby on Rails, for example:
class SpecifiedByIds
def specified_by_ids(ids)
joins(
<<~SQL
LEFT JOIN unnest(array[#{ids.join(',')}])
WITH ORDINALITY AS t(id,odr) ON t.id = #{table_name}.id
SQL
).order('t.odr')
end
end
class MyModel < ActiveRecord::Base
extend SpecifiedByIds
end

Resources