Find sum of all elements using ruby and selenium - ruby-on-rails

In my web page there are 5 values given in the text field(like $10, $20, $30, $40 and $50) and I am trying to sum the values using ruby and selenium WebDriver.
Here is my code:
def get_sum_of_all_elements()
#logger.info("Searching element #{value1, value2, value3, value4, value5}");
allelements = #driver.find_elements(:id = "lbl_val_")
#logger.info("Total Elements Found with locator #{locator} are : #{allelements.size}");
if allelements.start_with?("$")
allelements = "((allelements))".tr('$', '') #removing '$' sign from values
iSum =0
allelements.each do|i|
iSum += i
end
end
end
I am expecting to see output as 150. Do I need to store values in an array?
Any help would be appreciated.

There a couple of things you should modify in your code to make it work:
Fix how arguments are passed to find_elements; it should be id: "lbl_val_".
find_elements returns an array of WebDriver::Element objects, so you must check the value for each object.
The string "Searching element #{value1, value2, value3, value4, value5}" is not valid since you are trying to interpolate the value of 5 variables chained with a comma. You either need to interplate only the variable (keeping commas as strings) or use square brackets ([]) to interpolate an array.
Now your code should look something like this1:
def get_sum_of_all_elements
#logger.info("Searching element #{[value1, value2, value3, value4, value5]}")
allelements = #driver.find_elements(id: "lbl_val_")
#logger.info("Total Elements Found with locator #{locator} are : #{allelements.size}");
if allelements.all? { |elem| elem.value.start_with?("$") }
elements = allelements.map { |elem| elem.value.tr('$', '').to_i }
elements.reduce(:+)
end
end
A few things to note:
Parenthesis (()) were removed in method definition, ruby doesn't need them when no arguments are passed.
There is no longer need to assign the final value to a variable (e.g iSum) since ruby will return the result of last evaluated code.
If any value doesn't start with "$", it will return false. You could change this by adding a default value after if block.
Semicolons (;) were removed, you don't need them in ruby (unless you want to chain multiple statements in a singe line).
One more thing, the variables value1, value2, value3, value4, value5 and locator doesn't seem to be set anywhere in your method; you must set them within your method (or pass them as arguments) or you will get an error.
1 This considers the same logic that you seemed to be looking for in your code, that is, sum all values only if all of them start with "$".

It's hard to say exactly what you are trying to do, but this might help. I assume you have an array of string values with dollar signs:
>> allelements = ["$10", "$20", "$30", "$40", "$50"]
=> ["$10", "$20", "$30", "$40", "$50"]
We can make a new array stripping out all non-numeric characters and transforming the string values to integers:
>> integers = allelements.map { |e| e.gsub(/[^\d]/, '').to_i }
=> [10, 20, 30, 40, 50]
Now use inject to sum the values:
>> integers.inject(:+)
=> 150

Related

Ruby/Rails dictionary app - 6 letter words finder that are built of two concatenated smaller words

I need to create functionality which is going to process the dictionary (dictionary.txt file). The goal is to find all six-letter words that are built of two concatenated smaller words e.g.:
con + vex => convex
tail + or => tailor
we + aver => weaver
Of course, there may be some words inside the file that are not 6 letters long, but these can be easily sifted out using a simple method:
def cleanup_file
file_data = File.read('dictrionary.txt').split
file_data.reject! { |word| word.size < 6 }
end
But now comes the problem - how to find if the other strings in the array are made of two connected smaller words ?
[Edit]
Sample dictionary.txt file here
Thinking just in a pseudo code solution, but you should:
Iterate every line of the dictionary and store the words in 6 different arrays by the length of each word.
Make sure that all words are downcased, there are no duplicates and all the values are sorted, so later you could properly use .bsearch in the arrays.
Iterate the length-6 array (for example convex) and look for a match of the first character of the current word in the length-1 array (c for the given example) and in the length-5 array (onvex). If there's a match, save the words.
Then keep looking in the length-2 and length-4 arrays for matches (co and nvex correspondingly) and save a match.
Finally, look both parts of the string in the length-3 array (con and vex) and save any match
Look for the next 6 characters string until you've finished.
Most likely there are better ways to solve this, like in the first iteration inserting each word in its corresponding array using .bsearch_index to sort and not inserting duplicates in the same iteration, but most of the workload is going to be in the 2nd iteration and binary searches work in O(log n) time, so I guess it should work quick enough.
Suppose the dictionary is as follows.
dictionary = [
"a", "abased", "act", "action", "animal", "ape", "apeman",
"art", "assertion", "bar", "barbed", "barhop", "based", "be",
"become", "bed", "come", "hop", "ion", "man"
]
I assume that, like most dictionaries, dictionary is sorted.
First compute the following hash.
by_len = dictionary.each_with_object({}) do |w,h|
len = w.length
(h[len] ||= []) << w if len < 7
end
#=> {1=>["a"],
# 6=>["abased", "action", "animal", "apeman", "barbed",
# "barhop", "become"],
# 3=>["act", "ape", "art", "bar", "bed", "hop", "ion", "man"],
# 5=>["based"],
# 2=>["be"],
# 4=>["come"]}
Each key is a word length (1-6) and each value is an array of words from dictionary whose length is the value of the key.
Next I will define a helper function that returns true or false depending on whether a given array of words (list) contains a given word (word).
def found?(list, word)
w = list.bsearch { |w| w >= word }
w && w == word
end
For example:
found?(by_len[3], "art")
#=> true
found?(by_len[3], "any")
#=> false
See Array#bsearch.
We now extract the words of interest:
by_len[6].select { |w| (1..5).any? { |i|
found?(by_len[i], w[0,i]) && found?(by_len[6-i], w[i..-1]) } }
#=> ["abased", "action", "apeman", "barbed", "barhop", "become"]

Ruby method returns hash values in binary

I wrote a method that takes six names then generates an array of seven random numbers using four 6-sided dice. The lowest value of the four 6-sided dice is dropped, then the remainder is summed to create the value. The value is then added to an array.
Once seven numbers have been generated, the array is then ordered from highest to lowest and the lowest value is dropped. Then the array of names and the array of values are zipped together to create a hash.
This method ensures that the first name in the array of names receives the highest value, and the last name receives the lowest.
This is the result of calling the method:
{:strength=>1, :dexterity=>1, :constitution=>0, :intelligence=>0, :wisdom=>0, :charisma=>1}
As you can see, all the values I receive are either "1" or "0". I have no idea how this is happening.
Here is the code:
module PriorityStatGenerator
def self.roll_stats(first_stat, second_stat, third_stat, fourth_stat, fifth_stat, sixth_stat)
stats_priority = [first_stat, second_stat, third_stat, fourth_stat, fifth_stat, sixth_stat].map(&:to_sym)
roll_array = self.roll
return Hash[stats_priority.zip(roll_array)]
end
private
def self.roll
roll_array = []
7.times {
roll_array << Array.new(4).map{ 1 + rand(6) }.sort.drop(1).sum
}
roll_array.reverse.delete_at(6)
end
end
This is how I'm calling the method while I'm testing:
render plain: PriorityStatGenerator.roll_stats(params[:prioritize][:first_stat], params[:prioritize][:second_stat], params[:prioritize][:third_stat], params[:prioritize][:fourth_stat], params[:prioritize][:fifth_stat], params[:prioritize][:sixth_stat])
I added require 'priority_stat_generator' where I'm calling the method, so it is properly calling it.
Can someone help me make it return proper values between 1 and 18?
Here's a refactoring to simplify things and use an actually random number generator, as rand is notoriously terrible:
require 'securerandom'
module PriorityStatGenerator
def self.roll_stats(*stats)
Hash[
stats.map(&:to_sym).zip(self.roll(stats.length).reverse)
]
end
private
def self.roll(n = 7)
(n + 1).times.map do
4.times.map { 1 + SecureRandom.random_number(6) }.sort.drop(1).inject(:+)
end.sort.last(n)
end
end
This makes use of inject(:+) so it works in plain Ruby, no ActiveSupport required.
The use of *stats makes the roll_stats function way more flexible. Your version has a very rigid number of parameters, which is confusing and often obnoxious to use. Treating the arguments as an array avoids a lot of the binding on the expectation that there's six of them.
As a note it's not clear why you're making N+1 roles and then discarding the last. That's the same as generating N and discarding none. Maybe you meant to sort them and take the N best?
Update: Added sort and reverse to properly map in terms of priority.
You need to learn to use IRB or PRY to test snippets of your code, or better, learn to use a debugger. They give you insight into what your code is doing.
In IRB:
[7,6,5,4,3,2,1].delete_at(6)
1
In other words, delete_at(6) is doing what it's supposed to, but that's not what you want. Instead, perhaps slicing the array will behave more like you expect:
>> [7,6,5,4,3,2,1][0..-2]
[
[0] 7,
[1] 6,
[2] 5,
[3] 4,
[4] 3,
[5] 2
]
Also, in your code, it's not necessary to return a value when that operation is the last logical step in a method. Ruby will return the last value seen:
Hash[stats_priority.zip(roll_array)]
As amadan said, I can't see how you are getting the results you are, but their is a definite bug in your code.
The last line in self.roll is the return value.
roll_array.reverse.delete_at(6)
Which is going to return the value that was deleted. You need to add a new lines to return the roll_array instead of the delete_at value. You are also not sorting your array prior to removing that last item which will give you the wrong values as well.
def self.roll
roll_array = []
7.times {
roll_array << Array.new(4).map{ 1 + rand(6) }.sort.drop(1).sum
}
roll_array.sort.drop(1)
roll_array
end

lua - table.concat with string keys

I'm having a problem with lua's table.concat, and suspect it is just my ignorance, but cannot find a verbose answer to why I'm getting this behavior.
> t1 = {"foo", "bar", "nod"}
> t2 = {["foo"]="one", ["bar"]="two", ["nod"]="yes"}
> table.concat(t1)
foobarnod
> table.concat(t2)
The table.concat run on t2 provides no results. I suspect this is because the keys are strings instead of integers (index values), but I'm not sure why that matters.
I'm looking for A) why table.concat doesn't accept string keys, and/or B) a workaround that would allow me to concatenate a variable number of table values in a handful of lines, without specifying the key names.
Because that's what table.concat is documented as doing.
Given an array where all elements are strings or numbers, returns table[i]..sep..table[i+1] ยทยทยท sep..table[j]. The default value for sep is the empty string, the default for i is 1, and the default for j is the length of the table. If i is greater than j, returns the empty string.
Non-array tables have no defined order so table.concat wouldn't be all that helpful then anyway.
You can write your own, inefficient, table concat function easily enough.
function pconcat(tab)
local ctab, n = {}, =1
for _, v in pairs(tab) do
ctab[n] = v
n = n + 1
end
return table.concat(ctab)
end
You could also use next manually to do the concat, etc. yourself if you wanted to construct the string yourself (though that's probably less efficient then the above version).

Difference between passing &:method and :method as function arguments in ruby

I'm struggling in understanding when to use the ampersand in passing symbols to functions representing a method. For example, If I wanted to calculate the sum of the range 1..10, I could do the following:
(1..10).inject(:+)
This originally lead me to believe that if you wanted to pass a symbol to define a method to "Magically" be used in the function, you would pass the function name as a symbol. But then I see something like this in rails:
total = Product.find(product_list).sum(&:price)
If I understand correctly, &:price is the same as calling :price.to_proc. I don't understand how the above works.
In actual parameter list of a method call, &object is built-in syntax, which will
convert object to a Proc using object.to_proc
pass the Proc as the block parameter of the method
Symbol#to_proc converts the symbol (eg. :the_symbol) to proc {|obj| obj.send(:the_symbol)}. And you can use this syntax whenever object responds to to_proc method and returns a Proc.
abc = "aha~"
class << abc
def to_proc
proc {|obj| obj.to_i * 2 }
end
end
p ["1", "2", "3"].map(&abc)
#=> [2, 4, 6]
(1..10).inject(:+) shows that inject accepts a symbol as parameter. How the symbol is used is method specific behavior. In inject's special case, it has the same effect as (1..10).inject{|a, b| a.send(:+, b)}. It's just a simple, normal parameter, the effect depends on the implementation of the method that accept the symbol as parameter.
Note that sum in ActiveSupport accepts a block with single parameter, which has the effect "map values in the original sequence to new ones and calculate the sum of them", so
total = Product.find(product_list).sum(&:price)
is equivalent as
total = Product.find(product_list).sum(&proc{|p| p.send(:price)})
# or you may write
total = Product.find(product_list).sum{|p| p.price }
which has the same return value as the following but won't produce intermediate temp array:
total = Product.find(product_list).map{|p| p.price}.sum

Sorting an array in Ruby (Special Case)

I have an array in Ruby which has values as follows
xs = %w(2.0.0.1
2.0.0.6
2.0.1.10
2.0.1.5
2.0.0.8)
and so on. I want to sort the array such that the final result should be something like this :
ys = %w(2.0.0.1
2.0.0.6
2.0.0.8
2.0.1.5
2.0.1.10)
I have tried using the array.sort function, but it places "2.0.1.10" before "2.0.1.5". I am not sure why that happens
Using a Schwartzian transform (Enumerable#sort_by), and taking advantage of the lexicographical order defined by an array of integers (Array#<=>):
sorted_ips = ips.sort_by { |ip| ip.split(".").map(&:to_i) }
Can you please explain a bit more elaborately
You cannot compare strings containing numbers: "2" > "1", yes, but "11" < "2" because strings are compared lexicographically, like words in a dictionary. Therefore, you must convert the ip into something than can be compared (array of integers): ip.split(".").map(&:to_i). For example "1.2.10.3" is converted to [1, 2, 10, 3]. Let's call this transformation f.
You could now use Enumerable#sort: ips.sort { |ip1, ip2| f(ip1) <=> f(ip2) }, but check always if the higher abstraction Enumerable#sort_by can be used instead. In this case: ips.sort_by { |ip| f(ip) }. You can read it as "take the ips and sort them by the order defined by the f mapping".
Split your data into chunks by splitting on '.'. There is no standard function to do it as such so you need to write a custom sort to perform this.
And the behaviour you said about 2.0.1.10 before 2.0.1.5 is expected because it is taking the data as strings and doing ASCII comparisons, leading to the result that you see.
arr1 = "2.0.0.1".split('.')
arr2 = "2.0.0.6".split('.')
Compare both arr1 and arr2 element by element, for all the data in your input.

Resources