Find matching values of 2 columns in Ruby on Rails - ruby-on-rails

I am reading a CSV file with RoR and printing out an HTML table but need to mark when the values of 2 separate columns match with another row.
The CSV looks like
name | value1 | value2| value3
bob | 2 | 3 | foo
jim | 4 | 5 | bar
tim | 2 | 7 | foo
I want to find when VALUE1 and VALUE3 match the values of VALUE1 and VALUE3 of another row in this CSV file (in this case: "2" & "foo" match on bob and tim)
The result would be something like:
name | value1 | value2| value3 | duplicate
bob | 2 | 3 | foo | Y
jim | 4 | 5 | bar | N
tim | 2 | 7 | foo | Y
I'm printing the table out
<% file.each do |row| %>
<tr>
<% row.each do |k, v| %>
<td><% v %></td>
<% end %>
</tr>
<% end %>
What I'd like to do is flag the table row when I find the matching columns.

Another version of the answer above, in plain ruby rather than ERB, so it's easier to test
I wanted to show my small ruby program to show how I did this. It's not as easy to read in the ERB version and this can be copied and pasted into IRB or piped into a file.
puts '<head></head>'
puts '<body>'
puts '<table>'
puts '<tr><th>#</th><th>col1</th><th>col2</th><th>col3</th><th>col4</th><th>Matching Previous Rows:</th></tr>'
file = [%w(Bob foo 32 3), %w(Joe zip 4 foo), %w(Joe baz 4 foo), %w(Steve foo 44 3), %w(Bob baz 32 foo), %w(Mary baz 4 wow), %w(Lisa 34 wow 2), %w(Art 45 foo E),%w(Bob foo 32 3)]
output_file = []
file.each do |row|
row.unshift 0x00FFFF #make first value the background color of 0xFFFFFF
row.push '' #add a column for matches
output_file.each_with_index do |prev_row,i| # look for current row values in previous entries
if prev_row[2] == row[2] and prev_row[4] == row[4]
if prev_row[0] == 0x00FFFF
prev_row[0] = rand(0xFFFF0)
row[0] = prev_row[0]
else
row[0] = prev_row[0]
end
row[-1] += "#{(i + 1)}, "
end
end
output_file << row #add the new row to the look-back array
end
output_file.each_with_index do |output_row,i|
print "<tr bgcolor=##{output_row[0].to_s(16).upcase}F >"
output_row.shift
print "<td>#{i + 1}</td>"
output_row.each do |v| #why the k? you don't use it here.
print '<td>' + "#{v}" + ' </td>'
end
puts '</tr>'
end
puts '</table>'
puts '</body>'

I am reminded of a certain XKCD comic about determining if a photo was taken in a National Park,
And if the photo is of a bird... LOL
So you need a bit more complex data structure to do comparison of each row with all previous rows.
OK, final edit: I added code to only set a new color if this is the first time a set of values repeats, that way if it repeats again it will get the same color. Also added code that appends columns showing what rows also match.
<% output_file = [] %>
<% color_offset = 26214 %>
<% file.each do |row| %>
<% row.unshift 0x00FFFF # add last 4 digits of bgcolor as first item in each row %>
<% row.push '' #add a column at the end for row match numbers
<% output_file.each_with_index do |prev_row,i| # look for current row values in previous entries %>
<% if prev_row[2] == row[2] and prev_row[4] == row[4] %>
<% if prev_row[0] == 0x00FFFF %>
<% prev_row[0] -= color_offset %>
<% row[0] = prev_row[0] %>
<% else %>
<% row[0] = prev_row[0] %>
<% end %>
<% color_offset -= 52 %>
<% row[-1] += "#{(i + 1)}, " %>
<% end %>
<% end %>
<% output_file << row # add the new row to the look-back array %>
<% end %>
<% output_file.each do |output_row| %>
<tr bgcolor=#FF<% output_row[0].to_s(16).upcase %> >
<% output_row.shift %>
<% output_row.each do |v| # why the k? you don't use it here. %>
<td> <% v %> </td>
<% end %>
</tr>
<% end %>
So you take each row, loop through the array of rows previously checked, if it matches change both their first items to a matching 4 digit color in hex (G and B, R is added at the end), then copy the new row into the array of rows previously checked.
This breaks if a row matches more than once, as it will only give the same color to the new match and the one previous. BUT I added a final column that tells you which row each one matched previously. So if it is highlighted you can jump back to that row.
There are other ways to do the highlighting, maybe copy the new color forward, that way more than two of the exact same row would be the same color.

Related

How to convert Ruby data structure to a HTML file

I'm new to Stack overflow so kindly excuse if my question deviates from the expected standard.
I processed a log file to a below format and I would like to form a HTML representation from the below data structure.
holder = [
{:states=>"Texas"}
{:cities=>"Antonio", :data=>{"Ron"=>"26", "Rock"=>"23", "Jake"=>"33"}}
{:cities=>"West_CIT", :data=>{}}
{:cities=>"Austin", :data=>{"Ron"=>"26", "Mike"=>"53", "Jake"=>"36"}}
{:states=>"California"}
{:cities=>"Sacramento", :data=>{"Jill"=>"584", "Rudy"=>"3"}}
{:cities=>"Los Angeles", :data=>{"Jill"=>"4", "Rudy"=>"2"}}
{:states=>"Georgia"}
{:cities=>"Atlanta", :data=>{"Ron"=>"6", "Ross"=>"2", "Jake"=>"35"}}
{:cities=>"Athens", :data=>{"Jill"=>"16", "Mike"=>"4", "Reeves"=>"8"}} ]
I'm trying to create a HTML file which displays the States only on the Top page, like below
<h1> States and Associate Demat details <h1>
Texas
California
Georgia
but on clicking any State it should expand to a below format holding the sub details
Texas
Antonio Ron 26
Rock 23
Jake 33
West_CIT
Austin Ron 26
Mike 53
Jake 36
California
Georgia
Likewise the other states.
I've gone thru the Ruby documentation as well as the Nokogiri gem but with my limited knowledge in ruby (or any programming language) hampers my progress.. Is it really possible with Ruby or should I go with PHP (learn again). Looking for the guidance here, thank you.
P.S I've taken this as a self assignment in an attempt to improve my Ruby (or programming skills)
What I wrote :
holder = []
counter = -1
text = File.open("states.log").read
text.each_line do |line|
line.strip!
next if line.empty?
next if line.include?('persons') || line.include?('demat')
next if ['-','*'].include? line[0]
chip = line.split ' '
if chip.size == 1 and line.start_with?('state')
holder[counter += 1] = {states: line, data: {}}
next
elsif chip.size == 1 and chip = /^(?!.*state_).*$/
holder[counter += 1] = {cities: line, data: {}}
next
end
chip.each_cons(2) do |key, value|
holder[counter][:data][key] = value
end
end
puts holder
Adding the sample log file (raw)
state_Texas
Antonio
persons demat
------------------------------ ----------
Ron 26
Ron 23
Jake 33
=========================================
----Persons
West_CIT
persons demat
------------------------------ ----------
=========================================
----Persons
Austin
persons demat
------------------------------ ----------
Ron 26
Mike 53
Jake 36
=========================================
state_California
Sacramento
persons demat
------------------------------ ----------
Jill 584
Rudy 3
=========================================
---- Persons
Los Angeles
persons demat
------------------------------ ----------
Jill 4
Rudy 2
=========================================
Likewise .......
First thing first: if you need to exchange data between applications I suggest to stick with standard formats like JSON or YAML. I don't know if you can control the data logging, but if you can, I suggest to change the code there.
That log file is really a mess but it contains enough information for convert it into a ruby data structure like arrays and hashes.
There is always a better way but I ended up with this solution.
REJECT_THIS = ["", "------------------------------ ----------", "----Persons", "---- Persons", "persons demat"]
holder = []
separator = '|||'
# here we store the file into the holder, skipping rows in REJECT_THIS
text = File.open("_states.log").read
text.each_line do |line|
line = line.split.join(' ')
holder << line unless REJECT_THIS.include? line
end
# just to change the separator mark into a shorter one
holder.map! { |e| e == "=========================================" ? separator : e}
# map where to split the array grouping by state
split_idxs = holder.map.with_index { |e, i| e [0..4] == 'state' ? i : 0}.uniq[1..-1]<<holder.size
# split the array in states using the index map using the my_split_at_index method and building the states hash
holder = holder.my_split_at_index(split_idxs).map { |element| {element.shift => element} }
# remove 'state_' and convert the key to symbol
holder.map! { |e| e.transform_keys { |key| key[6..-1].to_sym } }
# splits subarrays by separator then build the nested hash
holder.map! do |array|
array.transform_values do |sub_array|
split_idxs = sub_array.map.with_index { |e, i| e == separator ? i : 0 }.uniq[1..-1]
sub_array = sub_array.my_split_at_index(split_idxs).map! { |e| e[0] == separator ? e[1..-1] : e }
sub_array.map { |element| {city: element.shift, people: element} }
end
end
# splits the people string
holder.map do |state|
state.transform_values do |array|
array.map do |hash|
hash[:people].map! { |string| string.split(' ') }
end
end
end
p holder
In the code I used this Array monkey patch
class Array
def my_split_at_index(indexes = [])
shift = 0
splitted = []
indexes.map do |index|
splitted << self[shift..index-1]
shift = index
end
splitted
end
end
The variable holder now is an array of nested hashes that you can use with ERB in a code like Dan Hilton posted. The data structure of holder is not the same, so you need to tweak the code.
One last thing, to see how the structure of your data as a YAML looks:
require 'yaml'
puts holder.to_yaml
As iGian said you can use ERB templates to create your HTML. I would also recommend changing your data structure to be easier to iterate. You are currently using the array order to determine where the cities are. This requires additional checks on the template side. Instead, the cities should be nested in the states hash to look like: {:state => "Texas", :cities=>[{:name => "Antonio", :data=>{"Ron"=>"26", "Rock"=>"23", "Jake"=>"33"}]}. This way you have an array of cities for a given state. Using this data structure, your template would look something like:
template = ERB.new <<-EOF
<h1> States and Associate Demat details <h1>
<% Holder.each do |state| %>
<h2><%= state[:state] %></h2>
<% state[:cities].each do |city| %>
<h3><%= city[:name] %></h3>
<% city[:data].each do |name, value| %>
<%= name %> - <%= value %>
<% end %>
<% end %>
<% end %>
EOF
Note that using <%= %> with the = is used to wrap ruby expressions that you want to show up on your template. On the other hand <% %> is used to set variables and iterate through your data structure, but will not show up in your template.
In your case, the use of templating is probably overkill and you can get away with using File.write('index.html') but getting ERB practice is useful because it will be directly applicable if you decide to learn Ruby on Rails.

Printing Specific Data From Table Using Ruby

This is my first time posting a question on this site; be gentle, please. This isn't homework. I'll try to be as concise as possible.
I have a table with 5 keyword columns, a date column, and a user ID column for identifying what user added that specific row of data. My goal is to print and count the number of matched keywords from a row iff:
1) Another row(s) contains those keywords AND has the same date.
2) The user ID for each row is unique.
For example:
Row1=> keyword1:(K1) keyword2:(K2) keyword3:(K3) keyword4:(K4) keyword5:(K5) date:(D1) user_id:(U1)
Row2=> keyword1:(K6) keyword2:(K7) keyword3:(K1) keyword4:(K2) keyword5:(K8) date:(D1) user_id:(U2)
Row3=> keyword1:(K6) keyword2:(K7) keyword3:(K1) keyword4:(K2) keyword5:(K8) date:(D2) user_id:(U2)
Row4=> keyword1:(K1) keyword2:(K2) keyword3:(K3) keyword4:(K4) keyword5:(K5) date:(D2) user_id:(U3)
Output:
K1 (2 times), K2 (2 times), on D1
K1 (2 times), K2 (2 times), on D2
Row3 should be excluded from the first count because even though the words matched, the user was a duplicate.
Here's how I've started:
<% #prophecies.each do |prophecy| %>
<% date1 = prophecy.datetwo %>
<% #prophecies.each do |prophecy| %>
<% if date1.eql?(prophecy.datetwo) == true %>
<tr>
<td><%= prophecy.keyone %></td>
<td><%= prophecy.keytwo %></td>
<td><%= prophecy.keythree %></td>
<td><%= prophecy.keyfour %></td>
<td><%= prophecy.keyfive %></td>
</tr>
<% end %>
<% end %>
<% end %>
But it's nowhere near what I'd like to accomplish. If anyone could even help me sort out the pseudocode for this I'd be happy.
First of all, it's better to do it in your model, not in your view.
You can define methods in your prophecy class something like that
def self.keywords_at_day(date)
#find prophecies for particular dae
prophecies = self.find_by(date:date)
# create hash with keywords for user
user_keywords = {}
prophecies.each do |prophecy|
user_keywords[prophecy.user] ||= []
user_keywords[prophecy.user] << prophecy.keyword
end
# create general array with all keywords
keywords = []
user_keywords.each do |user, kw|
keywords << kw.uniq
end
# count keywords
keywords_count = {}
keywords.each do |keyword|
keywords_count[keyword] ||= 0
keywords_count[keyword] += 1
end
keywords_count
end
def self.keywoards_lists
dates = Prophecy.all.map{|p| p.date}.uniq
keywords_lists = {}
dates.each do |date|
keywords_lists[date] = keywords_at_day(date)
end
keywords_lists
end
You controller
def index
#keywords_lists = Prophecy.keywords_lists
end
View
<%= #keywords_lists.each do |date, keywords_list| %>
<tr>
<td> <%= date %> </td>
<td>
<%= keywords_list.each do |keyword, count| %>
<%= "#{keyword} encountered #{count} times" %>
<% end %>
</td>
</tr>
<% end %>
If it's really not homework, I suggest you to read about MVC, ruby name convention(date_two instead of datetwo), booleans (you don't need "==true" for eql?).
And besides all it's not good type of question for stackoverflow
Cheers
My suggestion is to create a data structure that match your need. BAsically a hash where key is keyword, and content the list of uid that uniquely use your keyword:
r=[]<<{:keyword1=>"K1",keyword2:"K2",keyword3:"K3",keyword4:"k4",:date=>d1,uid:1}
r<<{:keyword1=>"K6",keyword2:"K7",keyword3:"K1",keyword4:"k2",:date=>d1,uid:2}
r<<{:keyword1=>"K6",keyword2:"K7",keyword3:"K1",keyword4:"k2",:date=>d2,uid:2}
(just for test purposes)
then:
r.inject({}) do |sum,aRow|
# loop for all keywords in the row.
[:keyword1,:keyword2,:keyword3,:keyword4,:keyword4].each do |keyword|
# get each entry or create it
sum[aRow[keyword]]=elem=sum[aRow[keyword]]||{}
count=elem[aRow[:date]]||{:users=>Set.new}
count[:users]<<aRow[:uid]
elem[aRow[:date]]=count
end
sum
end
The result if the following:
=> {"K1"=>
{2014-09-04 15:05:21 +0200=>{:users=>#<Set: {1, 2}>},
2014-01-01 00:00:00 +0100=>{:users=>#<Set: {2}>}},
"K2"=>{2014-09-04 15:05:21 +0200=>{:users=>#<Set: {1}>}},
"K3"=>{2014-09-04 15:05:21 +0200=>{:users=>#<Set: {1}>}},
"k4"=>{2014-09-04 15:05:21 +0200=>{:users=>#<Set: {1}>}},
"K6"=>
{2014-01-01 00:00:00 +0100=>{:users=>#<Set: {2}>},
2014-09-04 15:05:21 +0200=>{:users=>#<Set: {2}>}},
"K7"=>
{2014-01-01 00:00:00 +0100=>{:users=>#<Set: {2}>},
2014-09-04 15:05:21 +0200=>{:users=>#<Set: {2}>}},
"k2"=>
{2014-01-01 00:00:00 +0100=>{:users=>#<Set: {2}>},
2014-09-04 15:05:21 +0200=>{:users=>#<Set: {2}>}}
So you know that keyword "K1" has been used with two different date, and by two users (1,2) for first one, and 2 for second date.
Than, it will be easy to display this array.

Ruby each do loop 'n' number of times

Hi I'm having trouble with the below loop in a .erb view
<% my_list.each do | list | %>
.. loop stuff.....
<% end %.
This works fine for looping through my list, but I only want to loop through the first 4 items in 'my_list' and not the whole list. I tried some things like:
<% my_list.each do | product | 3.times %>
but didn't seem to work as I think my ruby knowledge is limited to say the least!
Use Array#take like this:
<% my_list.take(4).each do | product | %>
Take first 4 element from my_list Array#first:
<% my_list.first(4).each do | product | %>
use Array#each_slice for slice your array
<% my_list.each_slice(4) do | products | %>
<% products.each do | product | %>
It is apparent that you want to iterate through your list in groups of four (you really should amend your question, because this is an important piece of information). It is also apparent you are using Rails. Fortunately Rails has in_groups_of built into ActiveSupport:
<% my_list.in_groups_of(4) do |products| %>
<% products.each do | product | %>
One advantage of this approach (over alternatives such as each_slice) is that in_groups_of will pad the end to make sure you always get four groups. It will pad with nil by default, but you can specify the padding yourself:
<% my_list.in_groups_of(4, " ") do |products| %>
If you pass false as the pad, it will not pad at all and behaves just like each_slice.
<% my_list[0..3].each do | list | %>

Rails - Group using 2 tables and count

I have the following models: students , groups_by_student and groups.
A row of students table is city_id, so I have to show an html table
Total group1 group2 group3
city1 30 10 5 15
city2 2 0 0 2
city3 20 10 10 0
city4 5 0 5 0
city5 10 0 2 8
This is what I did:
groups = Group.find([1,4,6]) #Array of groups id's
total = []
groups.each do |g|
total << Student.joins(:groups_by_students).where(:groups_by_students => {:group_id => g.descendants}).count(:group => :city_id)
end
#I'm using AwesomeNestedSet gem, so g.descendants gives group children.
So now I have an array of 3 hashes that contain the city id as key and the total of students as the value, but now I'm not sure how to present this data in a html table.
How can I iterate per each "total" element? or is there another way of getting this information?
Thanks in advance
Javier
EDIT:
This is the total array
total = [
{city1 =>10, city3 => 10},
{city1 => 5, city3=>10, city4=>5, city5 => 2},
{city1 => 15, city2 => 2}
]
and now I have to place each in a td label inside a html table with the 0 if theres no value for that group.
I've traversed an array of hashes like;
ary.each do |hash| puts "<tr>#{hash.keys} : #{hash.values}</tr>" end
Can you hack that to suit your needs? Am afraid your question doesn't provide a lot to work with.
This is what i did, may be it might help you a little bit: (here the total value is the last column though)
<table>
<% i = 1%>
<% total = 0%>
<% city=""%>
<% 5.times do %>
<tr>
<% city = 'city'+ "#{i}" %>
<% #total.each do |hash| %>
<% if(hash[city].nil?)%>
<% hash[city] = 0 %>
<%end%>
<% total += hash[city].to_i %>
<td><%= hash[city] %></td>
<%end %>
<td> <%= total %></td>
<% total = 0 %>
<% i += 1 %>
</tr>
<%end%>
</table>
Here the row is controlled by city and not the group. Hence i could not find any other way other than a double loop. If you need that total to be printed in the first column and then rest of the information next, then i think you need to display the total first and then loop again and display city values of each group
Also, for this you need to know the number of cities before hand or else we will not know to print '0' for a particular city in a particular group

How do I do a grouping by year?

I have a books model with a date type column named publish_date. On my views I'm iterating through the books and I want to group the books by year such that I have a heading for every year and books that were published on that year to be listed below the year heading.
So by starting with "2010" all books published on 2010 would be listed, then another heading "2009" with all books published in 2009 listed below it and so forth.
<% #all_books.each do |book| %>
<%=link_to book.title + ", (PDF, " + get_file_size(book.size) + ")" %>
<% end %>
By doing a book.publish_date.strftime("%Y") I am able to get the year but I do not know how to group the entries by year. Any help on this would be appreciated.
You can use group_by (see API) like (of the top of my head
<% #all_books.group_by(&:year).each do |year, book| %>
...
<% end %>
def year
self.created_at.strftime('%Y')
end
< % #all_books.group_by(&:year).each do |year, book| %>
Year < %= year %>
# render books here
< % end %>
What say?
You can use group_by for convenience, but your need can be better served by relying on DB for sorting and a each loop. This avoids the cost of client side sorting and hash manipulations for grouping.
Somewhere in your controller
#all_books = Book.all(:order => "publish_date DESC")
In your view
<%year = nil
#all_books.each do |book|
if year.nil? or year > book.publish_date.year
year = book.publish_date.year
%>
<h1> <%=year%><h1>
<%end % >
<%=link_to book.title + ", (PDF, " + get_file_size(book.size) + ")" %>
<%end %>
The quick and dirty approach is to simply group_by the year and iterate over those:
#all_books.group_by { |b| b.created_at.year }.each do |year, books|
# All books for one year, so put heading here
books.each do |book|
# ...
end
end
This requires sorting within the Rails application, so you will need to retrieve all relevant records in order to have the data properly organized. To do the sort on the server you will probably need to introduce a year column and keep it in sync with the created_at time.

Resources