How to calculate a formula with undefined variable - ruby

I am trying to eval a string with undefined variable. For example: Formula = 2 * 3 + a The result should return a string of 6 + a. Can the eval method do something like that? Or, can you give me some ideas on how to do this?
Update: Thank you for all the inputs. I guess this is not as simple as i thought it would be. Let's say if I don't need to simplify the formula and all i need to do is to replace the variable with value in string?
Example:
a = { "Bob" => 82,
"Jim" => 94,
"Billy" => 58, ........ and more}
How do I convert this string
"2 * 3 + a["Bob"] * b"
to this: "2 * 3 + 82 * b"
Thanks again for your help.

What you are trying to do is complicated, and I don't think it is worth doing it.
You can use some gem to parse the string into a tree of tokens. Then, look for any node under which there is no undefined variable, and replace the node with the calculated value. After doing that, put the tree back to a string.

What about using something like Parslet and making a parsing expression grammar to simplify your expressions? Look at the get started page where you are walked through a simple example in which Parslet reduces integer expressions.

Here is a naïve solution:
str = "+ 1 * 2 - 3 / 2 + b - a"
terms = str.gsub(/^[^+-]+|[+-][^+-]+/).to_a
=> ["+ 1 * 2 ",
"- 3 ",
"+ b ",
"- a"]
numbers, variables = terms.partition { |exp| eval(exp.to_s) rescue false }
=> [["+ 1 * 2 ", "- 3 "],
["+ b ", "- a"]]
numbers.map! { |exp| exp.gsub(/\d+/, &:to_f) }
=> ["+ 1.0 * 2.0 ",
"- 3.0 / 2.0 "]
numbers.map! { |exp| eval(exp) }
=> [2.0, -1.5]
sum = numbers.reduce(:+)
=> "0.5"
result = "#{sum} #{variables.join}"
=> "0.5 + b - a"

Related

Use repeat of format for many variables on the same line

puts "%-30s%2s%3d%2s%3d%2s%3d%2s%3d%2s%3d" % [tn,ln,a,ln,b,ln,c,ln,d,ln,e]
This is Ruby, but many languages use this formatting. I have forgotten how to output several variables in the same format, without repeating the format in each case. Here, I want "%3d%2s" for 5 integers, each separated by a '|'
You could write the following.
def print_my_string(tn, ln, *ints)
fmt = "%-10s" + ("|#{"%2s" % ln}%3d" * ints.size) + "|"
puts fmt % [tn, *ints]
end
Then, for example,
print_my_string("hello", "ho", 2, 77, 453, 61, 999)
displays
hello |ho 2|ho 77|ho453|ho 61|ho999|
after having computed
fmt = "%-10s" + ("|#{"%2s" % ln}%3d" * ints.size) + "|"
#=> %-10s|ho%3d|ho%3d|ho%3d|ho%3d|ho%3d|"

Expected: 0, instead got: 1 Ruby

I've been working on a task to create a function that returns the total number of smiley faces. Valid smiley faces look like: ":) :D ;-D :~)" and invalid smiling faces: ";( :> :} :] ".
My solution below throws the following error message "expected 0 instead got 1".
def count_smileys(arr)
arr.count do |element|
[":)", ":D", ";-D", ":~)", ";~D", "8~(", ";(", ":>", ":}", ":]",
"8~P", "8-(", "; )", ";-P", ":~P", "~P", ":~P", "~P", "; (", ":-)",
"8~D", "~)", "8D", "~)", "8 )", "; )", "~)", ":-D", " (", ";D",
"8-D", "8-P", ";-D", ": D", ";~D", " ("].include?(element)
end
end
As it stands my solution passes 4 out of 5 basic tests:
Basic tests
Test Passed: Value == 0
Test Passed: Value == 4
Test Passed: Value == 2
Test Passed: Value == 1
Expected: 0, instead got: 1
I've tried removing the parameters but that only worked for the last test and failed me on the rest. Any suggestions? Thanks
Example tests below:
Test.describe("Basic tests") do
Test.assert_equals(count_smileys([]), 0)
Test.assert_equals(count_smileys([":D",":~)",";~D",":)"]), 4)
Test.assert_equals(count_smileys([":)",":(",":D",":O",":;"]), 2)
Test.assert_equals(count_smileys([";]", ":[", ";*", ":$", ";-D"]), 1)
Test.assert_equals(count_smileys([";", ")", ";*", ":$", "8-D"]), 0)
end
Compare or Inspect Using Array Intersection
You don't show your actual test setup, so I won't address that. However, it seems like you're trying to count the number of smileys in a given array using the block form of Array#count. It's arguably simpler to measure the size of an Array intersection, or to inspect the contents of the intersection, using Array#&. For example:
SMILEYS = [
":)", ":D", ";-D", ":~)", ";~D", "8~(", ";(", ":>", ":}", ":]",
"8~P", "8-(", "; )", ";-P", ":~P", "~P", ":~P", "~P", "; (", ":-)",
"8~D", "~)", "8D", "~)", "8 )", "; )", "~)", ":-D", " (", ";D",
"8-D", "8-P", ";-D", ": D", ";~D", " ("
]
SMILEYS.&([":D", ":~)", ";~D", ":)"]).size == 4
#=> true
SMILEYS.&([":)", ":(", ":D", ":O", ":;"]).size == 2
#=> true
SMILEYS.&([";]", ":[", ";*", ":$", ";-D"]).size == 1
#=> true
SMILEYS.&([";", ")", ";*", ":$", "8-D"]).size == 0
#=> false
Note that the last comparison is false because 8-D is defined in your list of SMILEYS. Specifically:
SMILEYS.&([";", ")", ";*", ":$", "8-D"])
#=> ["8-D"]
so the expected value should be 1 unless 8-D doesn't really belong in your array of acceptable values. If that's the case, remove it from the variable or constant containing your allowable values.

Array can't be coerced into Float (TypeError) on my Ruby code

I am supposed to make variables that convert standard units into metric units. This is an exercise for a lesson on Learn Ruby the Hard Way. I'm trying to run the following code in PowerShell. The information inside the code is from the author of the book.
name = 'Zed A. Shaw'
age = 35 # not a lie in 2009
height = 74 # inches
weight = 180 # lbs
eyes = 'Blue'
teeth = 'White'
hair = 'Brown'
cm = 2.54
kg_1 = 2
kg_2 = 1/10
puts "Let's talk about #{name}."
puts "He's #{height * cm} inches tall."
puts "He's #{(weight * kg_1) - kg_2} pounds heavy."
puts "Actually that's not too heavy."
puts "He's got #{eyes} and #{hair} hair."
puts "His teeth are usually #{teeth} depending on the coffee."
# this line is tricky, try to get it exactly right
puts "If I add #{age}, #{height * cm}, and #{(weight * kg_1) - kg_2 }"
puts "I get #{age + (height * cm) + [(weight * kg_1) - kg_2]}."
It failed when I had to add up everything at the end. When I try to run it in PowerShell, this comes up:
Traceback (most recent call last):
1: from ex5.rb:20:in `<main>'
ex5.rb:20:in `+': Array can't be coerced into Float (TypeError).
What is my error, and how do I fix it?
The problem lays in this line:
"I get #{age + (height * cm) + [(weight * kg_1) - kg_2]}."
You probably want to see sth like
I get 1234.12.
You use [] brackets to group operations (as you'd do in math class). In Ruby you can only use () to group. [] is a notation for introducing an array. Try this one:
"I get #{age + (height * cm) + ((weight * kg_1) - kg_2)}."
Looks like the error occurs on line:
puts "I get #{age + (height * cm) + [(weight * kg_1) - kg_2]}."
The issue here is using [] indicates the program should be looking for an array object. By using [] here, it is interpreted that you're looking to add an array object to a float object using the + method (operators are methods in Ruby). Ruby spits out that error message. It seems what you're looking to do is to:
add age to (height * cm) to return a float
add that float value to the difference between (weight * kg_1) and kg_2
By replacing:
[(weight * kg_1) - kg_2]
with
((weight * kg_1) - kg_2)`
you should receive the intended interpolated value of:
I get 582.96

Parsing a string field

I have these Syslog messages:
N 4000000 PROD 15307 23:58:12.13 JOB78035 00000000 $HASP395 GGIVJS27 ENDED\r
NI0000000 PROD 15307 23:58:13.41 STC81508 00000200 $A J78036 /* CA-JOBTRAC JOB RELEASE */\r
I would like to parse these messages into various fields in a Hash, e.g.:
event['recordtype'] #=> "N"
event['routingcode'] #=> "4000000"
event['systemname'] #=> "PROD"
event['datetime'] #=> "15307 23:58:12.13"
event['jobid'] #=> "JOB78035"
event['flag'] #=> "00000000"
event['messageid'] #=> "$HASP395"
event['logmessage'] #=> "$HASP395 GGIVJS27 ENDED\r"
This is the code I have currently:
message = event["message"];
if message.to_s != "" then
if message[2] == " " then
array = message.split(%Q[ ]);
event[%q[recordtype]] = array[0];
event[%q[routingcode]] = array[1];
event[%q[systemname]] = array[2];
event[%q[datetime]] = array[3] + " " +array[4];
event[%q[jobid]] = message[38,8];
event[%q[flags]] = message[47,8];
event[%q[messageid]] = message[57,8];
event[%q[logmessage]] = message[56..-1];
else
array = message.split(%Q[ ]);
event[%q[recordtype]] = array[0][0,2];
event[%q[routingcode]] = array[0][2..-1];
event[%q[systemname]] = array[1];
event[%q[datetime]] = array[2] + " "+array[3];
event[%q[jobid]] = message[38,8];
event[%q[flags]] = message[47,8];
event[%q[messageid]] = message[57,8];
event[%q[logmessage]] = message[56..-1];
end
end
I'm looking to improve the above code. I think I could use a regular expression, but I don't know how to approach it.
You can't use split(' ') or a default split to process your fields because you are dealing with columnar data that has fields that have no whitespace between them, resulting in your array being off. Instead, you have to pick apart each record by columns.
There are many ways to do that but the simplest and probably fastest, is indexing into a string and grabbing n characters:
'foo'[0, 1] # => "f"
'foo'[1, 2] # => "oo"
The first means "starting at index 0 in the string, grab one character." The second means "starting at index 1 in the string, grab two characters."
Alternately, you could tell Ruby to extract by ranges:
'foo'[0 .. 0] # => "f"
'foo'[1 .. 2] # => "oo"
These are documented in the String class.
This makes writing code that's easily understood:
record_type = message[ 0 .. 1 ].rstrip
routing_code = message[ 2 .. 8 ]
system_name = message[ 10 .. 17 ]
Once you have your fields captured add them to a hash:
{
'recordtype' => record_type,
'routingcode' => routing_code,
'systemname' => system_name,
'datetime' => date_time,
'jobid' => job_id,
'flags' => flags,
'messageid' => message_id,
'logmessage' => log_message,
}
While you could use a regular expression there's not much gained using one, it's just another way of doing it. If you were picking data out of free-form text it'd be more useful, but in columnar data it tends to result in visual noise that makes maintenance more difficult. I'd recommend simply determining your columns then cutting the data you need based on those from each line.

Referencing array of arrays in while loop

I am trying to reference and set variables equal to specific elements within an array of arrays, shown here :
student = [["Last", "Doofus"], ["First", "Douglas"], ["Exam", "75"], ["Homework", "65"], ["Attendance", "60"]]
The ultimate goal is to find out the final grade based on these grades. for example, here is my code :
while line = gets
examRaw = student[2][1].to_i
hwRaw = student[3][1].to_i
exam = (examRaw * 0.5) / 100
hw = (hwRaw * 0.3) / 100
final = exam + hw
puts "Final Numeric Grade = " + final
end
I am receiving an error on the examRaw line. The error I am receiving is :
`<main>': undefined method [] for #<Enumerator:0x000000021059d0> (NoMethodError)`.
I have tested this in irb with the same exact array and it seems to have no problem finding the numbers that I need to be referenced. For example :
irb(main):016:0> student[2][1].to_i
=> 75
What is causing the error?

Resources