I’m trying to display a related section based on the article’s tags. Any articles that have similar tags should be displayed.
The idea is to iterate the article’s tags and see if any other articles have those tags.
If yes, then add that article to a related = [] array of articles I can retrieve later.
Article A: tags: [chris, mark, scott]
Article B: tags: [mark, scott]
Article C: tags: [alex, mike, john]
Article A has as related the Article B and vice-versa.
Here’s the code:
files = Dir[ROOT + 'articles/*']
# parse file
def parse(fn)
res = meta(fn)
res[:body] = PandocRuby.new(body(fn), from: 'markdown').to_html
res[:pagedescription] = res[:description]
res[:taglist] = []
if res[:tags]
res[:tags] = res[:tags].map do |x|
res[:taglist] << '%s' % [x, x]
'%s' % [x, x]
end.join(', ')
end
res
end
# get related articles
def related_articles(articles)
related = []
articles[:tags].each do |tag|
articles.each do |item|
if item[:tags] != nil && item[:tags].include?(tag)
related << item unless articles.include?(item)
end
end
end
related
end
articles = files.map {|fn| parse(fn)}.sort_by {|x| x[:date]}
articles = related_articles(articles)
Throws this error:
no implicit conversion of Symbol into Integer (TypeError)
Another thing I tried was this:
# To generate related articles
def related_articles(articles)
related = []
articles.each do |article|
article[:tags].each do |tag|
articles.each do |item|
if item[:tags] != nil && item[:tags].include?(tag)
related << item unless articles.include?(item)
end
end
end
end
related
end
But now the error says:
undefined method `each' for "tagname":String (NoMethodError)
Help a Ruby noob? What am I doing wrong? Thanks!
As an aside to the main question, I tried rewriting the tag section of the code, but still no luck:
res[:taglist] = []
if res[:tags]
res[:tags] = res[:tags].map do |x|
res[:taglist] << '' + x + ''
'' + x + ''
end.join(', ')
end
In your first attempt, the problem is in articles[:tags]. articles is an array, so you cannot access it using a symbol key.
The second attempt fails because article[:tags] is a string (from the parse function, you get the original tags, transform to HTML and then join). The :taglist key instead contains an array, you could use it.
Finally, the "related" array should be per-article so neither implementation could possibly solve your issue, as both return a single array for all your set of articles.
You probably need a two pass:
def parse(fn)
res = meta(fn)
res[:body] = PandocRuby.new(body(fn), from: 'markdown').to_html
res[:pagedescription] = res[:description]
res[:tags] ||= [] # and don't touch it
res[:tags_as_links] = res[:tags].map { |x| "#{x}" }
res[:tags_as_string] = res[:tags_as_links].join(', ')
res
end
articles = files.map { |fn| parse(fn) }
# convert each article into a hash like
# {tag1 => [self], tag2 => [self]}
# and then reduce by merge
taggings = articles
.map { |a| a[:tags].product([[a]]).to_h }
.reduce { |a, b| a.merge(b) { |_, v1, v2| v1 | v2 } }
# now read them back into the articles
articles.each do |article|
article[:related] = article[:tags]
.flat_map { |tag| taggings[tag] }
.uniq
# remove the article itself
article[:related] -= [article]
end
This question already has answers here:
Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])
(4 answers)
Closed 2 years ago.
Given this CSV file:
date,name,st,code,num
2020-03-25,AB,53,2585,130
2020-03-26,AB,53,3208,151
2020-03-26,BA,35,136,1
2020-03-27,BA,35,191,1
I want to create the following hash with the given data:
{:AB=>[["2020-03-25", "2585"], ["2020-03-26", "3208"]], :BA=>[["2020-03-26", "136"], ["2020-03-27", "191"]]}
I tried this:
require 'csv'
h=Hash.new([])
CSV.foreach('file.csv', headers: true) do |row|
h[row['st']] << [[row['date'], row['code']]]
end
but all I get is an empty hash h.
Let's first create the CSV file.
str =<<~_
date,name,st,code,num
2020-03-25,AB,53,2585,130
2020-03-26,AB,53,3208,151
2020-03-26,BA,35,136,1
2020-03-27,BA,35,191,1
_
FName = 't'
File.write(FName, str)
#=> 120
Now we can simply read the file line-by-line, using CSV::foreach, which, without a block, returns an enumerator, and build the hash as we go along.
require 'csv'
CSV.foreach(FName, headers: true).
with_object(Hash.new { |h,k| h[k] = [] }) do |row,h|
h[row['name'].to_sym] << [row['date'], row['code']]
end
#=> {:AB=>[["2020-03-25", "2585"], ["2020-03-26", "3208"]],
# :BA=>[["2020-03-26", "136"], ["2020-03-27", "191"]]}
I've used the method Hash::new with a block to create a hash h such that if h does not have a key k, h[k] causes h[k] #=> []. That way, h[k] << 123, when h has no key k results in h[k] #=> [123].
Alternatively, one could write:
CSV.foreach(FName, headers: true).with_object({}) do |row,h|
(h[row['name'].to_sym] ||= []) << [row['date'], row['code']]
end
One could also use a converter to convert the values of name to symbols, but some might see that as over-kill here:
CSV.foreach(FName, headers: true,
converters: [->(v) { v.match?(/\p{Alpha}+/) ? v.to_sym : v }] ).
with_object(Hash.new { |h,k| h[k] = [] }) do |row,h|
h[row['name']] << [row['date'], row['code']]
end
There is no need to read a CSV file as a text file or whatever, you can use the CSV file as you intended and address the actual issues at hand.
There are three issues here:
This won't work:
h = Hash.new([])
use this instead:
h = Hash.new {|h, k| h[k] = [] }
See "Strange, unexpected behavior (disappearing/changing values) when using Hash default value, e.g. Hash.new([])" as #jack commented.
You need headers: true because the first row is a headers row in your case.
You are only pushing to the values array. You need to overwrite it like:
h[row['name']] = h[row['name']] << [row['date'], row['code']]
This will work for you:
require 'csv'
h = Hash.new { |h, k| h[k] = [] }
CSV.foreach('file.csv', headers: true) do |row|
h[row['name']] = h[row['name']] << [row['date'], row['code']]
end
h.transform_keys(&:to_sym)
#=> {:AB=>[["2020-03-25", "2585"], ["2020-03-26", "3208"]], :BA=>[["2020-03-26", "136"], ["2020-03-27", "191"]]}
I create an array from a text file which contains the english irregular verbs. I want the code to ask me the verbs in random order letting me proceed only if I respond correctly. I need to compare a string with an element of array. I wrote this:
a = []
File.open('documents/programmi_test/verbi.txt') do |f|
f.lines.each do |line|
a <<line.split.map(&:to_s)
end
end
puts ''
b = rand(3)
puts a[b][0]
puts 'infinitive'
infinitive = gets.chomp
if infinitive = a[b][1] #--> write like this, I receive alway "true"
puts 'simple past'
else
puts 'retry'
end
pastsimple = gets.chomp
if pastsimple == a[b][2] #--> write like this, I receive alway "false"
puts 'past participle'
else
puts 'retry'
end
pastpart = gets.chomp
if pastpart == a[b][3]
puts 'compliments'
else
puts 'oh, no'
end
can somebody help me?
if infinitive = a[b][1] is assigning to inifinitive the value of a[b][1], unlike pastsimple == a[b][2] that's a comparation between both values.
You could try replacing the = for ==.
a = []
File.open('documents/programmi_test/verbi.txt') do |file|
file.lines.each do |line|
a << line.split.map(&:to_s)
end
end
puts ''
b = rand(3)
puts a[b][0]
puts 'infinitive'
infinitive = gets.chomp
puts infinitive == a[b][1] ? 'simple past' : 'retry'
pastsimple = gets.chomp
puts pastsimple == a[b][2] ? 'past participle' : 'retry'
pastpart = gets.chomp
puts pastpart == a[b][3] ? 'compliments' : 'oh, no'
class Dresser
##all = []
attr_accessor :name, :height, :length, :width, :contents
def initialize (name, height,length, width)
#name = name
#height = height
#length = length
#width = width
#contents = []
##all << self
end
def Dresser.all
##all
end
def add_content(content)
#contents << content
end
end
a = Dresser.new('a', 4, 6, 8)
a.add_content('sock')
a.add_content('hat')
b = Dresser.new('b', 3, 6, 9)
b.add_content('bra')
c = Dresser.new('c', 4, 7, 6)
c.add_content('hat')
How would I go about searching through ##all for the name of dressers that contain a specific item, say a hat?
Edit: Realized the code I initially entered was incorrect. Whoops!
Add a reader contents method then call select.
class Dresser
#rest of your code
def contents
#contents
end
end
#Assuming your instance additions
Dresser.all.select {|d| d.contents.include? 'hat'}
=>
[#<Dresser:0x000000020b0890 #name="a", #height=4, #length=6, #width=8, #contents=["sock", "hat"]>,
#<Dresser:0x000000020b0728 #name="c", #height=4, #length=7, #width=6, #contents=["hat"]>]
I need to use a loop in my code so the user is prompted with "Name?" three times, and each answer is stored as a new hash within the data array. Each answer should also have a new random number generated for it, and an email.
I need puts data to output all three hashes and their contents. I've tried using 3.times do, but I'm having trouble:
data = Array.new()
puts "Name?, eg. Willow Rosenberg"
name = gets.chomp
number = rand(1000..9000) + 1
data = [
{
name: name,
number: number,
email: name.split(' ').last + number.to_s[1..3] + "#btvs.com"
}
]
puts data
data = []
3.times do
puts "Name?, eg. Willow Rosenberg"
name = gets.chomp
number = rand(1000..9000) + 1
hash = {
name: name,
number: number,
email: name.split(' ').last + number.to_s[1..3] + "#btvs.com"
}
data << hash
end
puts data