I am trying to figure out how can i distribute particular elements of a list, based on an if statement.
This is the list:
#x = ["american", "assistive", "audio", "blind", "braille", "closed-captioning", "closed-captioning", "deaf", "low", "phone", "question-circle", "question-circle", "sign", "tty", "universal", "wheelchair"]
This is my haml code:
%ul.list-inline
- #x.each do |i|
- if i[0].length < i[1].length
%li
%i{:class=>"fas fa-#{i[0]} fa-2x"}
%span.f6 #{i[0]}
- else
%li
%i{:class=>"far fa-#{i[1]} fa-2x"}
%span.f6 #{i[1]}
What i am trying to do, is to determine the length of each string in the list and compare it to the length of the next string in the list.
Once the second string is determined as being a duplicate, it should go under the else statement.
The problem i am facing, is that by going with i[0], instead of the first string in the list, i am getting the first letter of the each string in the list.
I don't know if my way of using length is the best way to solve this issue, so if anyone else has a better solution i am open for it, as long as it gets the job done.
I am thinking that maybe if i could filter the elements in the list based on which elements are unique and which are duplicates, i could then distribute them accordingly.
But how i do that?
Thank you.
To answer the first part of your question where i[0] or i[1] is returning a single letter instead of the element, let us examine your code:
#x.each do |i|
Here i is the element. So in the first iteration, i is 'american'. So when you call i[0] it returns the first character of the string which is a and i[1] returns m and both their lengths are 1.
Instead, you should modify your code like this:
%ul.list-inline
- #x.each_cons(2) do |current, next|
- if current.length < next.length
%li
%i{:class=>"fas fa-#{current} fa-2x"}
%span.f6 #{current}
- else
%li
%i{:class=>"far fa-#{next} fa-2x"}
%span.f6 #{next}
Regarding the second part of your question, you have defined #x as:
#x = ["american", "assistive", "audio", "blind", "braille", "closed-captioning", "closed-captioning", "deaf", "low", "phone", "question-circle", "question-circle", "sign", "tty", "universal", "wheelchair"]
To fetch unique elements:
#x_uniq = #x.uniq
To fetch duplicates:
#x_dup = #x.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }.select{ |k,v| v > 1 }.keys
which returns
["closed-captioning", "question-circle"]
In my opinion, using the second way to filter the data and using it is a better solution than comparing elements with their lengths.
Hope this helped.
Use Enumerable#each_cons:
- #x.each_cons(2) do |cur, nxt|
- if cur.length < nxt.to_s.length
...
- else
...
Related
I have a spreadsheet of members designed as below:
My aim is to upload some columns and exclude others. In this case, I wish to upload only the name, age and email and exclude the others. I have been able to achieve this using the slice method as shown below:
def load_imported_members
spreadsheet = open_spreadsheet
spreadsheet.default_sheet = 'Worksheet'
header = spreadsheet.row(1)
(2..spreadsheet.last_row).map do |i|
row = Hash[[header, spreadsheet.row(i)].transpose]
member = Member.find_by_id(row["id"]) || Member.new
member.attributes = row.to_hash.slice("id", "name", "age", "email")
member
end
end
The problem is that last_row considers all the rows upto the last one (13), and since there are validations on the form, there are errors due to missing data as a result of the empty rows (which shouldn’t be considered). Is there a way I can upload only specific columns as I have done, yet limit to only the rows that have data?
You might want to chain the map call off of a reject filter like this example
You may just need to change the map line to this (assuming the missing rows all look like those above):
(2..spreadsheet.last_row).reject{|i| spreadsheet.row(i)[0] }.map do |i|
This is assuming the blank rows return as nil and that blank rows will always have all four desired fields blank as shown in the image. The reject call tests to see if spreadsheet.row(i)[0], the id column, is nil, if so the item is rejected from the list output given to map
Thanks for this question. I have learned some things from this question.
I have shortlisted your answer [note: use 'ROO' gem]
def load_imported_members(member)
spreadsheet = open_spreadsheet(member)
spreadsheet.each do |records|
record = #spreadsheet ? Hash[[#header, #spreadsheet.row(records)].transpose] : Hash[records] # transpose for xlsx records and
attributes = {id: record['id'], name: record['name'], email: record['email'], age: record['age']}
member_object = Member.new(attributes)
if member_object.valid?
if Member.find(attributes[:id])
Member.find(attributes[:id]).update(attributes)
else
member_object.save
end
end
end
end
You can parse your uploaded file using Roo gem.
def self.open_spreadsheet(member)
case File.extname(member.file.original_filename)
when ".csv" then
Roo::CSV.new(member.file.expiring_url, csv_options: {headers: true, skip_blanks: true, header_converters: ->(header) { header.strip }, converters: ->(data) { data ? data.strip : nil }})
when ".xlsx", ".xls" then
#spreadsheet = Roo::Spreadsheet.open(member.file.expiring_url)
#header = #spreadsheet.row(1)
(2..#spreadsheet.last_row)
end
end
Here I have used s3 uploaded url i.e expiring_url. Hope This will helpful. I have not tested. sorry, for small errors.
If you have used validations for name, email, and age. This will surely help u..
(See edit at the bottom of this post)
I'm making a program in Elixir that counts the types of HTML tags from a list of tags that I've already obtained. This means that the key should be the tag and the value should be the count.
e.g. in the following sample file
<html><head><body><sometag><sometag><sometag2><sometag>
My output should be something like the following:
html: 1
head: 1
body: 1
sometag: 3
sometag2: 1
Here is my code:
def tags(page) do
taglist = Regex.scan(~r/<[a-zA-Z0-9]+/, page)
dict = Map.new()
Enum.map(taglist, fn(x) ->
tag = String.to_atom(hd(x))
Map.put_new(dict, tag, 1)
end)
end
I know I should be probably using Enum.each instead but when I do that my dictionary ends up just being empty instead of incorrect.
With Enum.map, this is the output I receive:
iex(15)> A3.test
[%{"<html" => 1}, %{"<body" => 1}, %{"<p" => 1}, %{"<a" => 1}, %{"<p" => 1},
%{"<a" => 1}, %{"<p" => 1}, %{"<a" => 1}, %{"<p" => 1}, %{"<a" => 1}]
As you can see, there are duplicate entries and it's turned into a list of dictionaries. For now I'm not even trying to get the count working, so long as the dictionary doesn't duplicate entries (which is why the value is always just "1").
Thanks for any help.
EDIT: ------------------
Okay so I figured out that I need to use Enum.reduce
The following code produces the output I'm looking for (for now):
def tags(page) do
rawTagList = Regex.scan(~r/<[a-zA-Z0-9]+/, page)
tagList = Enum.map(rawTagList, fn(tag) -> String.to_atom(hd(tag)) end)
Enum.reduce(tagList, %{}, fn(tag, acc) ->
Map.put_new(acc, tag, 1)
end)
end
Output:
%{"<a": 1, "<body": 1, "<html": 1, "<p": 1}
Now I have to complete the challenge of actually counting the tags as I go...If anyone can offer any insight on that I'd be grateful!
First of all, it is not the best idea to parse html with regexes. See this question for more details (especially the accepted answer).
Secondly, you are trying to write imperative code in functional language (this is about first version of your code). Variables in Elixir are immutable. dict will always be an empty map. Enum.map takes a list and always returns new list of the same length with all elements transformed. Your transformation function takes an empty map and puts one key-value pair into it.
As a result you get a list with one element maps. The line:
Map.put_new(dict, tag, 1)
doesn't update dict in place, but creates new one using old one, which is empty. In your example it is exactly the same as:
%{tag => 1}
You have couple of options to do it differently. Closest approach would be to use Enum.reduce. It takes a list, an initial accumulator and a function elem, acc -> new_acc.
taglist
|> Enum.reduce(%{}, fn(tag, acc) -> Map.update(acc, tag, 1, &(&1 + 1)) end)
It looks a little bit complicated, because there are couple of nice syntactic sugars. taglist |> Enum.reduce(%{}, fun) is the same as Enum.reduce(taglist, %{}, fun). &(&1 + 1) is shorthand for fn(counter) -> counter + 1 end.
Map.update takes four arguments: a map to update, key to update, initial value if key doesn't exist and a function that does something with the key if it exists.
So, those two lines of code do this:
iterate over list Enum.reduce
starting with empty map %{}
take current element and map fn(tag, acc) and either:
if key doesn't exist insert 1
if it exists increment it by one &(&1 + 1)
before even explaining anything, I'll just leave you here what I have (a summary tho):
rowsTable1 (it has the result of a SELECT, it contains X rows with Y columns)
rowsTable2 (it has the result of a SELECT, it contains W rows with Z columns)
What I want, is to insert into each rowsTable1 row, a row from rowsTable2, but I need to filter rowsTable2 by an ID I have in rowsTable1.
For example, in rowsTable1, I have an element called superID and another called ultraID, and rowsTable2 has those elements too, and that's what I need to merge both rows.
My question is, how can I filter rowsTable2 to only get the row I want?
rowsTable1.eachWithIndex{ rowT1, i ->
rowT1 << rowT2.firstRow([rowT1.superID],[rowT1.ultraID])
}
Like this?
Thanks!
It looks like you want to use "collect" to create a composite map, as I don't think you'll be allowed to change the list of Rows that came back from the SQL query.
Here's a sample that should do what you want:
// if we start with this data (pretend it's what came back from your queries)
def rowsTable1 = [ [superID :1, ultraID :2, c:'a1b2'], [superID :1, ultraID :3, c:'a1b3'] ]
def rowsTable2 = [ [superID :1, ultraID :3, d:'a1b3extra'], [superID :3, ultraID :4, d:'unfound'], [superID :1, ultraID :2, d:'a1b2extra']]
// Now, join the rows to a List of new "row-like" Maps when superID and ultraID are equal.
def ret = rowsTable1.collect { T1row ->
[:] << T1row << rowsTable2.find { it.superID == T1row.superID && it.ultraID == T1row.ultraID }
}
ret should now be a List of Maps, each of which is the "merged" Row data for all the unique combinations of superID and ultraID in rowsTable1. Note that any data in rowsTable2 that doesn't have a superID/ultraID combo that exists in rowsTable1 is ignored, and that all combos that exist in rowsTable1 should exist in rowsTable2.
First off, I apologize in advance for any incorrect terms or general misunderstanding of ruby/rails/html that I may display in this question, as I'm still learning the languages and how they work. I've been given a gui to work on and fix bugs for and after upgrading from rails 3.0 to 3.2 I'm seeing artifacts in many places.
Specifically what I'm asking in this post though is a string display issue. The short version of what I have is a message thread displayed with in a web page that looks close to what you'd see in most text message clients on smartphones now days. The page displays a persons name if the system knows who they are (registered with us) and just a mobile number if they are not.
However the issue is that instead of displaying 5554440002, it's displaying as:
["(", "5", "5", "5", ")", " ", "4", "4", "4", "-", "0", "0", "0", "1"]
Looking around the net, and on stack overflow I found this post To be the closest thing to answering my issue. However the suggestion of dropping the = out of <%= ends in the mobile number no longer displaying.
The line of code that generates this output is:
<%= find_associated_account conversation_message['from'] -%>
Edit 1 (definition):
def find_associated_account(input)
if input[0..0] == $operator.domestic_code.to_s
input = input[1..10]
end
if #account.associated_accounts and #account.associated_accounts.count > 0
if !(input.match(/#/)) && !(input.match(/MISSING_MAILBOX/))
input = input[0..9]
end
begin
acc = #account.associated_accounts.find_by_number(input)
if acc
return get_display_name_for_account(acc)
end
end
return format_mdn(input)
end
def format_mdn(input)
domestic_length = $operator.domestic_mask.count('#')
number_cc_stripped = input.sub(/\A[#{$operator.domestic_code}]/, '')
if /^[0-9]{#{domestic_length}}$/.match(number_cc_stripped)
result = []
count = 0
$operator.domestic_mask.each_char do |c|
if c == '#'
result << number_cc_stripped[count,1]
count += 1
else
result << c
end
end
return result.to_s
end
return input
end
Any advice given will be greatly appreciated and any other information needed will be provided.
Fixed this after messing with the return value of format_mdn. I changed the initialization of result to "" instead of [] which I'm guessing changed the type of the variable? In any case the final code for this definition is:
def format_mdn(input)
domestic_length = $operator.domestic_mask.count('#')
number_cc_stripped = input.sub(/\A[#{$operator.domestic_code}]/, '')
if /^[0-9]{#{domestic_length}}$/.match(number_cc_stripped)
result = []
count = 0
$operator.domestic_mask.each_char do |c|
if c == '#'
result << number_cc_stripped[count,1]
count += 1
else
result << c
end
end
return result.join("")
end
result = number_cc_stripped
return result
end
Is it possible to transpose an html table (without javascript).
I m generating a table with rails (and erb) from a list of object. So it's really easy and natural to do it when each row correspond to one object. However , I need each object to be represented as a column. I would like to have only one loop and describe each column rather than doing the same loop for every columns. (That doesn't necessarily needs to be a real table , could be a list or anything which does the trick).
update
To clarify the question. I don't want to transpose an array in ruby, but to display a html table with the row vertically. My actual table is actually using one partial per row, wich generate a list of cell (td). That can be change to a list if that help. Anyway this is HTML question not a ruby one : how to display a table with the rows vertically (rather than horizontally).
You may need something like this?
class Array
def transpose
# Check here if self is transposable (e.g. array of hashes)
b = Hash.new
self.each_index {|i| self[i].each {|j, a_ij| b[j] ||= Array.new; b[j][i] = a_ij}}
return b
end
end
a = [{:a => 1, :b => 2, :c => 3}, {:a => 4, :b => 5, :c => 6}]
a.transpose #=> {:a=>[1, 4], :b=>[2, 5], :c=>[3, 6]}
Apparently, the answer is no :-(