Ruby Count Smiley Face - Exit Code = 1 - arrays

The count smiley face question on Codewars, my codes passed all the tests, but an "Exit Code = 1" error message keeps popping up, what does that mean? What went wrong?
countSmileys([':)', ';(', ';}', ':-D']); // should return 2;
countSmileys([';D', ':-(', ':-)', ';~)']); // should return 3;
countSmileys([';]', ':[', ';*', ':$', ';-D']); // should return 1;
def count_smileys(arr)
first = ";:"
second = "-~"
third = ")D"
arr.select{|x|
third.include?(x[1]) or (second.include?(x[1]) && third.include?(x[2].to_s))
}.count
end
EDIT:
The error message is as below:
main.rb:8:in `include?': no implicit conversion of nil into String (TypeError)
from main.rb:8:in `block in count_smileys'
from main.rb:7:in `select'
from main.rb:7:in `count_smileys'
from main.rb:16:in `block in <main>'
from /runner/frameworks/ruby/cw-2.rb:55:in `block in describe'
from /runner/frameworks/ruby/cw-2.rb:46:in `measure'
from /runner/frameworks/ruby/cw-2.rb:51:in `describe'
from main.rb:11:in `<main>'

As the message describes, there's no implicit conversion of nil to string. Explicit does exist though:
2.3.1 :001 > nil.to_s
=> ""
You could parse your array for nil first, and then put it through the select method.
def count_smileys(arr)
first = ";:"
second = "-~"
third = ")D"
# parse your array for nil values here
arr.map {|x| x.nil? ? "" : x }
arr.select{|x|
third.include?(x[1]) or (second.include?(x[1]) && third.include?(x[2].to_s))
}.count
end

I realized what the problem is - there is a test count_smileys([";", ")", ";*", ":$", "8-D"]) where x[1] and x[2] would not be valid for the first 2 items in the array, so I need to fix the array inside the select method:
def count_smileys(arr)
first = ";:"
second = "-~"
third = ")D"
arr.select{|x|
x[1] = " " if x[1] == nil
x[2] = "" if x[2] == nil
(first.include?(x[0]) && third.include?(x[1])) || (first.include?(x[0]) && second.include?(x[1]) && third.include?(x[2]))
}.count
end
Joseph Cho was correct in the sense that nils need to be converted, but we should do it in the iteration, and common items x[1] should be set to an empty string with a space to avoid being counted, while x[2] is uncommon enough that an empty string would work.

Related

Get related articles based on tags in Ruby

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

Iterate over array of objects. Then access object method if correct one is found. Otherwise create a new object in the array

I start with an empty array, and a Hash of key, values.
I would like to iterate over the Hash and compare it against the empty array. If the value for each k,v pair doesn't already exist in the array, I would like to create an object with that value and then access an object method to append the key to an array inside the object.
This is my code
class Test
def initialize(name)
#name = name
#values = []
end
attr_accessor :name
def values=(value)
#values << value
end
def add(value)
#values.push(value)
end
end
l = []
n = {'server_1': 'cluster_x', 'server_2': 'cluster_y', 'server_3': 'cluster_z', 'server_4': 'cluster_x', 'server_5': 'cluster_y'}
n.each do |key, value|
l.any? do |a|
if a.name == value
a.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
end
p l
I would expect to see this:
[
#<Test:0x007ff8d10cd3a8 #name=:cluster_x, #values=["server_1, server_4"]>,
#<Test:0x007ff8d10cd2e0 #name=:cluster_y, #values=["server_2, server_5"]>,
#<Test:0x007ff8d10cd1f0 #name=:cluster_z, #values=["server_3"]>
]
Instead I just get an empty array.
I think that the condition if a.name == value is not being met and then the add method isn't being called.
#Cyzanfar gave me a clue as to what to look for, and I found the answer here
https://stackoverflow.com/a/34904864/5006720
n.each do |key, value|
found = l.detect {|e| e.name == value}
if found
found.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
#ARL you're almost there! The last thing you need to consider is when found actually returns an object since detect will find a matching one at some point.
n.each do |key, value|
found = l.detect {|e| e.name == value}
if found
found.add(key)
else
t = Test.new(value)
t.add(key)
l << t
end
end
You actually only want to add a new instance of Test when found return nil. This code should yield your desired output:
[
#<Test:0x007ff8d10cd3a8 #name=:cluster_x, #values=["server_1, server_4"]>,
#<Test:0x007ff8d10cd2e0 #name=:cluster_y, #values=["server_2, server_5"]>,
#<Test:0x007ff8d10cd1f0 #name=:cluster_z, #values=["server_3"]>
]
I observe two things in your code :
def values=(value)
#values << value
def add(value)
#values.push(value)
two methods do the same thing, pushing a value, as << is a kind of syntactic sugar meaning push
you have changed the meaning of values=, which is usually reserved for a setter method, equivalent to attire_writer :values.
Just to illustrate that there are many ways to do things in Ruby, I propose the following :
class Test
def initialize(name, value)
#name = name
#values = [value]
end
def add(value)
#values << value
end
end
h_cluster = {} # intermediate hash whose key is the cluster name
n = {'server_1': 'cluster_x', 'server_2': 'cluster_y', 'server_3': 'cluster_z',
'server_4': 'cluster_x', 'server_5': 'cluster_y'}
n.each do | server, cluster |
puts "server=#{server}, cluster=#{cluster}"
cluster_found = h_cluster[cluster] # does the key exist ? => nil or Test
# instance with servers list
puts "cluster_found=#{cluster_found.inspect}"
if cluster_found
then # add server to existing cluster
cluster_found.add(server)
else # create a new cluster
h_cluster[cluster] = Test.new(cluster, server)
end
end
p h_cluster.collect { | cluster, servers | servers }
Execution :
$ ruby -w t.rb
server=server_1, cluster=cluster_x
cluster_found=nil
server=server_2, cluster=cluster_y
cluster_found=nil
server=server_3, cluster=cluster_z
cluster_found=nil
server=server_4, cluster=cluster_x
cluster_found=#<Test:0x007fa7a619ae10 #name="cluster_x", #values=[:server_1]>
server=server_5, cluster=cluster_y
cluster_found=#<Test:0x007fa7a619ac58 #name="cluster_y", #values=[:server_2]>
[#<Test:0x007fa7a619ae10 #name="cluster_x", #values=[:server_1, :server_4]>,
#<Test:0x007fa7a619ac58 #name="cluster_y", #values=[:server_2, :server_5]>,
#<Test:0x007fa7a619aac8 #name="cluster_z", #values=[:server_3]>]

Trying to define a function with a loop in ruby, and I'm getting an error where there isn't a function

I wrote a program to extract all (over 1000) the comments from a reddit post, and I'm having trouble defining a function.
The program:
require "rubygems"
require "json"
require "net/http"
require "uri"
require 'open-uri'
require 'neatjson'
#The URL, which changes.
url = ("https://www.reddit.com/r/AskReddit/comments/46n0zc.json")
#Sets up the JSON reader.
result = JSON.parse(open(url).read)
post = result[0]["data"]["children"]
children = result[1]["data"]["children"]
#Sets up the base location.
base = ("https://www.reddit.com" + post[0]["data"]["permalink"].to_s)
#Sets up the arrays.
mainIDs = Array.new
reIDs = Array.new
#Collects the main jsons.
children.each do |child|
if child["data"].has_key? "body"
mainIDs.push(child["data"]["id"].to_s)
end
end
mainINT = mainIDs.count
#Collects the remaining.
children.each do |child|
if child["data"].has_key? "children"
reIDs = child["data"]["children"]
end
end
remainINT = reIDs.count
puts "Main Comments: " + mainINT.to_s
puts "Total Comments: " + (mainINT + remainINT).to_s
#Divides the page.
puts ("__" * 50)
puts ("\n")
#Creates a function for collection.
def printAllComments(array, comINT)
for i in array do i
url = base + i
puts "Post URL: " + url
result = JSON.parse(open(url).read)
children = result[1]["data"]["children"]
int = comINT
for i in children do child
if child["data"].has_key? "body"
puts "Comment Number: " + int.to_s
puts "Author: " + child["data"]["author"]
puts "Body: " + neatBD(child["data"]["body"].to_s)
puts "ID: " + child["data"]["id"]
puts "Ups: " + child["data"]["ups"].to_s
puts "\n\n"
int += 1
end
end
end
end
printAllComments(mainINT, 1)
The "Creates a function for collection" is where the error is. When I run this, I get:
Main Comments: 64
Total Comments: 1676
____________________________________________________________________________________________________
007----extractallredditpostcomments.rb:51:in `printAllComments': undefined method `each' for 64:Fixnum (NoMethodError)
from 007----extractallredditpostcomments.rb:72:in `<main>'
When I should be getting the first 64 comments from the main array, instead it just breaks after it prints the line. What's weird is that the error is on line 51, and line 51 is the:
url = base + i
there's no 'each' function there.
What am I missing?
for i in array do is equivalent to array.each do |i|. If array is not, actually, an array, you will get the error you are getting. Proof:
for i in 64 do puts i end
# NoMethodError: undefined method `each' for 64:Fixnum
Stylistically... no Rubyist uses for; everyone uses each directly.
so there are a couple items that I would point out.
First the loop syntax feels a bit funny. If you want to access each element of the array I would use array.each do |item|, same of the inner loop children.each do |child|.
Next you are actually passing the mainINT instead of mainIDs which is an int, not an array. This is what was throwing the error you asked about.
Lastly you are trying to reference base from within your method but it doesn't exist within the method's scope, try passing it into the method or making the variable global with #base.
Changing these few items I was able to get one URL to print out and then ran into an issue with parsing the JSON but I will leave that to you. This should hopefully get you moving forward.
Patrick

Filtering a Cell Array with Recursion

I'm pretty close on this problem. What I have to do is filter out a cell array. The cell array can have a variety of items in it, but what I want to do is pull out the strings, using recursion. I am pretty close on this one. I just have an issue when the cells have spaces in them. This is what I should get:
Test Cases:
cA1 = {'This' {{{[1:5] true} {' '}} {'is '} false true} 'an example.'};
[filtered1] = stringFilter(cA1)
filtered1 => 'This is an example.'
cA2 = {{{{'I told '} 5:25 'her she'} {} [] [] ' knows'} '/take aim and reload'};
[filtered2] = stringFilter(cA2)
filtered2 => 'I told her she knows/take aim and reload'
Here is what I have:
%find the strings in the cArr and then concatenate them.
function [Str] = stringFilter(in)
Str = [];
for i = 1:length(in)
%The base case is a single cell
if length(in) == 1
Str = ischar(in{:,:});
%if the length>1 than go through each cell and find the strings.
else
str = stringFilter(in(1:end-1));
if ischar(in{i})
Str = [Str in{i}];
elseif iscell(in{i})
str1 = stringFilter(in{i}(1:end-1));
Str = [Str str1];
end
end
end
end
I tried to use 'ismember', but that didn't work. Any suggestions? My code outputs the following:
filtered1 => 'This an example.'
filtered2 => '/take aim and reload'
You can quite simplify your function to
function [Str] = stringFilter(in)
Str = [];
for i = 1:length(in)
if ischar(in{i})
Str = [Str in{i}];
elseif iscell(in{i})
str1 = stringFilter(in{i});
Str = [Str str1];
end
end
end
Just loop through all elements in the cell a test, whether it is a string or a cell. In the latter, call the function for this cell again. Output:
>> [filtered1] = stringFilter(cA1)
filtered1 =
This is an example.
>> [filtered2] = stringFilter(cA2)
filtered2 =
I told her she knows/take aim and reload
Here is a different implememntation
function str = stringFilter(in)
if ischar(in)
str = in;
elseif iscell(in) && ~isempty(in)
str = cell2mat(cellfun(#stringFilter, in(:)', 'uni', 0));
else
str = '';
end
end
If it's string, return it. If it is a cell apply the same function on all of the elements and concatenate them. Here I use in(:)' to make sure it is a row vector and then cell2mat concatenates resulting strings. And if the type is anything else return an empty string. We need to check if the cell array is empty or not because cell2mat({}) is of type double.
The line
Str = ischar(in{:,:});
is the problem. It doesn't make any sense to me.
You're close to the getting the answer, but made a few significant but small mistakes.
You need to check for these things:
1. Loop over the cells of the input.
2. For each cell, see if it itself is a cell, if so, call stringFilter on the cell's VALUE
3. if it is not a cell but is a character array, then use its VALUE as it is.
4. Otherwise if the cell VALUE contains a non character, the contribution of that cell to the output is '' (blank)
I think you made a mistake by not taking advantage of the difference between in(1) and in{1}.
Anyway, here's my version of the function. It works.
function [out] = stringFilter(in)
out = [];
for idx = 1:numel(in)
if iscell (in{idx})
% Contents of the cell is another cell array
tempOut = stringFilter(in{idx});
elseif ischar(in{idx})
% Contents are characters
tempOut = in{idx};
else
% Contents are not characters
tempOut = '';
end
% Concatenate the current output to the overall output
out = [out, tempOut];
end
end

Fastest and most effective way of comparing two array of hashes of different format

I have two arrays of hashes with the format:
hash1
[{:root => root_value, :child1 => child1_value, :subchild1 => subchild1_value, bases => hit1,hit2,hit3}...]
hash2
[{:path => root_value/child1_value/subchild1_value, :hit1_exist => t ,hit2_exist => t,hit3_exist => f}...]
IF I do this
Def sample
results = nil
project = Project.find(params[:project_id])
testrun_query = "SELECT root_name, suite_name, case_name, ic_name, executed_platforms FROM testrun_caches WHERE start_date >= '#{params[:start_date]}' AND start_date < '#{params[:end_date]}' AND project_id = #{params[:project_id]} AND result <> 'SKIP' AND result <> 'N/A'"
if !params[:platform].nil? && params[:platform] != [""]
#yell_and_log "platform not nil"
platform_query = nil
params[:platform].each do |platform|
if platform_query.nil?
platform_query = " AND (executed_platforms LIKE '%#{platform.to_s},%'"
else
platform_query += " OR executed_platforms LIKE '%#{platform.to_s},%'"
end
end
testrun_query += ")" + platform_query
end
if !params[:location].nil? &&!params[:location].empty?
#yell_and_log "location not nil"
testrun_query += "AND location LIKE '#{params[:location].to_s}%'"
end
testrun_query += " GROUP BY root_name, suite_name, case_name, ic_name, executed_platforms ORDER BY root_name, suite_name, case_name, ic_name"
ic_query = "SELECT ics.path, memberships.pts8210, memberships.sv6, memberships.sv7, memberships.pts14k, memberships.pts22k, memberships.pts24k, memberships.spb32, memberships.spb64, memberships.sde, projects.name FROM ics INNER JOIN memberships on memberships.ic_id = ics.id INNER JOIN test_groups ON test_groups.id = memberships.test_group_id INNER JOIN projects ON test_groups.project_id = projects.id WHERE deleted = 'false' AND (memberships.pts8210 = true OR memberships.sv6 = true OR memberships.sv7 = true OR memberships.pts14k = true OR memberships.pts22k = true OR memberships.pts24k = true OR memberships.spb32 = true OR memberships.spb64 = true OR memberships.sde = true) AND projects.name = '#{project.name}' GROUP BY path, memberships.pts8210, memberships.sv6, memberships.sv7, memberships.pts14k, memberships.pts22k, memberships.pts24k, memberships.spb32, memberships.spb64, memberships.sde, projects.name ORDER BY ics.path"
if params[:ic_type] == "never_run"
runtest = TestrunCache.connection.select_all(testrun_query)
alltest = TrsIc.connection.select_all(ic_query)
(alltest.length).times do |i|
#exec_pltfrm = test['executed_platforms'].split(",")
unfinishedtest = comparison(runtest[i],alltest[i])
yell_and_log("test = #{unfinishedtest}")
yell_and_log("#{runtest[i]}")
yell_and_log("#{alltest[i]}")
end
end
end
I get in my log:
test = true
array of hash 1 = {"root_name"=>"BSDPLATFORM", "suite_name"=>"cli", "case_name"=>"functional", "ic_name"=>"cli_sanity_test", "executed_platforms"=>"pts22k,pts24k,sv7,"}
array of hash 2 = {"path"=>"BSDPLATFORM/cli/functional/cli_sanity_test", "pts8210"=>"f", "sv6"=>"f", "sv7"=>"t", "pts14k"=>nil, "pts22k"=>"t", "pts24k"=>"t", "spb32"=>nil, "spb64"=>nil, "sde"=>nil, "name"=>"pts_6_20"}
test = false
array of hash 1 = {"root_name"=>"BSDPLATFORM", "suite_name"=>"infrastructure", "case_name"=>"bypass_pts14k_copper", "ic_name"=>"ic_packet_9", "executed_platforms"=>"sv6,"}
array of hash 2 = {"path"=>"BSDPLATFORM/infrastructure/build/copyrights", "pts8210"=>"f", "sv6"=>"t", "sv7"=>"t", "pts14k"=>"f", "pts22k"=>"t", "pts24k"=>"t", "spb32"=>"f", "spb64"=>nil, "sde"=>nil, "name"=>"pts_6_20"}
test = false
array of hash 1 = {"root_name"=>"BSDPLATFORM", "suite_name"=>"infrastructure", "case_name"=>"bypass_pts14k_copper", "ic_name"=>"ic_status_1", "executed_platforms"=>"sv6,"}
array of hash 2 = {"path"=>"BSDPLATFORM/infrastructure/build/ic_1", "pts8210"=>"f", "sv6"=>"t", "sv7"=>"t", "pts14k"=>"f", "pts22k"=>"t", "pts24k"=>"t", "spb32"=>"f", "spb64"=>nil, "sde"=>nil, "name"=>"pts_6_20"}
test = false
array of hash 1 = {"root_name"=>"BSDPLATFORM", "suite_name"=>"infrastructure", "case_name"=>"bypass_pts14k_copper", "ic_name"=>"ic_status_2", "executed_platforms"=>"sv6,"}
array of hash 2 = {"path"=>"BSDPLATFORM/infrastructure/build/ic_files", "pts8210"=>"f", "sv6"=>"t", "sv7"=>"f", "pts14k"=>"f", "pts22k"=>"t", "pts24k"=>"t", "spb32"=>"f", "spb64"=>nil, "sde"=>nil, "name"=>"pts_6_20"}
SO I get only the first to match but rest becomes different and I get result of one instead of 4230
I would like some way to match by path and root/suite/case/ic and then compare the executed platforms passed in array of hashes 1 vs platforms set to true in array of hash2
Not sure if this is fastest, and I wrote this based on your original question that didn't provide sample code, but:
def compare(h1, h2)
(h2[:path] == "#{h1[:root]}/#{h1[:child1]}/#{h1[:subchild1]}") && \
(h2[:hit1_exist] == ((h1[:bases][0] == nil) ? 'f' : 't')) && \
(h2[:hit2_exist] == ((h1[:bases][1] == nil) ? 'f' : 't')) && \
(h2[:hit3_exist] == ((h1[:bases][2] == nil) ? 'f' : 't'))
end
def compare_arr(h1a, h2a)
(h1a.length).times do |i|
compare(h1a[i],h2a[i])
end
end
Test:
require "benchmark"
h1a = []
h2a = []
def rstr
# from http://stackoverflow.com/a/88341/178651
(0...2).map{65.+(rand(26)).chr}.join
end
def rnil
rand(2) > 0 ? '' : nil
end
10000.times do
h1a << {:root => rstr(), :child1 => rstr(), :subchild1 => rstr(), :bases => [rnil,rnil,rnil]}
h2a << {:path => '#{rstr()}/#{rstr()}/#{rstr()}', :hit1_exist => 't', :hit2_exist => 't', :hit3_exist => 'f'}
end
Benchmark.measure do
compare_arr(h1a,h2a)
end
Results:
=> 0.020000 0.000000 0.020000 ( 0.024039)
Now that I'm looking at your code, I think it could be optimized by removing array creations, and splits and joins which are creating arrays and strings that need to be garbage collected which also will slow things down, but not by as much as you mention.
Your database queries may be slow. Run explain/analyze or similar on them to see why each is slow, optimize/reduce your queries, add indexes where needed, etc. Also, check cpu and memory utilization, etc. It might not just be the code.
But, there are some definite things that need to be fixed. You also have several risks of SQL injection attack, e.g.:
... start_date >= '#{params[:start_date]}' AND start_date < '#{params[:end_date]}' AND project_id = #{params[:project_id]} ...
Anywhere that params and variables are put directly into the SQL may be a danger. You'll want to make sure to use prepared statements or at least SQL escape the values. Read this all the way through: http://guides.rubyonrails.org/active_record_querying.html
([element_being_tested].each do |el|
[hash_array_1, hash_array_2].reject do |x, y|
x[el] == y[el]
end
end).each {|x, y| puts (x[bases] | y[bases])}
Enumerate the hash elements to test.
[element_being_tested].each do |el|
Then iterate through the hash arrays themselves, comparing the given hashes by the elements of the given comparison defined by the outer loop, rejecting those not appropriately equal. (The == may actually need to be != but you can figure that much out)
[hash_array_1, hash_array_2].reject do |x, y|
x[el] == y[el]
end
Finally, you again compare the hashes taking the set union of their elements.
.each {|x, y| puts (x[bases] | y[bases])}
You may need to test the code. It's not meant for production so much as demonstration because I wasn't sure I read your code right. Please post a larger sample of the source including the data structures in question if this answer is unsatisfactory.
Regarding speed: if you're iterating through a large data set and comparing multiple there's probably nothing you can do. Perhaps you can invert the loops I presented and make the hash arrays the outer loop. You're not going to get lightning speed here in Ruby (really any language) if the data structure is large.

Resources