Ruby - Loop IF x times and change condition - ruby

I would like to loop 20 times over an if statement, but each time it's run, some things must change.
Example:
input = [0,0,0,44,754,22,0,632,2,22,0,2,nil,2,24,nil,666,90909,2,4,6,7,2,7,3,2,2,7,1,8,6,3,2,19,5,46]
Statement = "Statement THIS"
if
input[1] != nil &&
input[2] == 0
Statement.sub! "THIS", "WHERE #{input[8]} #{input[9]} THIS"
else
end
puts Statement #Statement WHERE 2 22 THIS
if
input[5] != nil &&
input[6] == 0
Statement.sub! "THIS", "AND #{input[12]} #{input[13]} THIS"
else
end
puts Statement #Statement WHERE 2 22 AND 2 THIS
if
input[9] != nil &&
input[10] == 0
Statement.sub! "THIS", "AND #{input[16]} #{input[17]} THIS"
else
end
puts Statement #Statement WHERE 2 22 AND 2 AND 666 90909 THIS
In the second IF statement the following things have changed:
Each key has increased by 4 (1,2,8,9 became 5,6,12,13)
The word WHERE has changed to AND
I would like this behaviour to repeat another 18 times, so the third IF statement has:
Each key has increased by 4 (5,6,12,13 became 9,10,16,17)
The word HELLO has changed to GOODBYE (however this rule is now redundant, since the second IF statement took care of it).

input = [
0,0,0,44,754,22,0,632,2,22,0,2,
nil,2,24,nil,666,90909,2,4,6,7,
2,7,3,2,2,7,1,8,6,3,2,19,5,46
]
(1..Float::INFINITY).step(4) do |i|
i = i.to_i # is float here
break Statement if i >= input.size
next if input[i].nil? || !input[i+1].zero?
keyword = Statement =~ /WHERE/ ? 'AND' : 'WHERE'
Statement.sub! "THIS", "#{keyword} #{input[i+7]} #{input[i+8]} THIS"
end
#⇒ "Statement WHERE 2 22 AND 2 AND 666 90909 THIS"

To loop 20 times you can use the times method.
arr_index = 1
20.times do
if arr_index < 1
operator = "WHERE"
else
operator = "AND"
end
if input[arr_index] != nil && input[arr_index + 1] == 0
Statement.sub! "THIS", "#{operator} #{input[arr_index + 7]} #{input[arr_index + 8]} THIS"
end
arr_index += 4
end
Another option would be to change the contents of your input array or create another data structure (e.g., hash, array, array of hashes, hash of arrays) with the exact values you need for the loop. Thus, eliminating the need to increment the index by 4 at each iteration.
Also, unless Stamement is a class or a constant, convention dictates its name should be lower case (e.g., statement).
Good luck!

n = 20
"STATEMENT WHERE %s THEN" % (1..1+4*(n-1)).step(4).with_object([]) { |i,a|
a << "#{ input[i+7] } #{ input[i+8] }" unless input[i].nil? || input[i+1] != 0 }.
join(" AND ")
#=> "Statement WHERE 2 22 AND 2 AND 666 90909 THEN"

Related

Question on how to filter x || y and not x && y

I am having trouble using || ("or").
This is the first time I select using the "or" feature and I have been trying to select the words that are greater than 6 characters long OR start with an "e". I tried everything but I keep getting just one feature or an "and". This is the code so far
def strange_words(words)
selected_words = []
i = 0
while i < words.length
word = words[i]
if word.length < 6
selected_words << word
end
i += 1
end
return selected_words
end
print strange_words(["taco", "eggs", "we", "eatihhg", "for", "dinner"])
puts
print strange_words(["keep", "coding"])
Using the || operator is the same as writing multiple if statements. Let's use a silly example to demonstrate it. Say you wanted to determine if a word started with the letter 'e'. Well there are a few forms of 'e'. There is the lowercase e and the upppercase E. You want to check for both forms so you could do something like this:
def starts_with_e?(string)
result = false
if string[0] == 'e'
result = true
end
if string[0] == 'E'
result = true
end
result
end
Notice however that you're doing the same actions after checking for the condition. This means you could simplify this code using the OR/|| operator, like such:
def starts_with_e?(string)
result = false
if string[0] == 'e' || string[0] == 'E'
result = true
end
end
For your specific question, you can do the following:
def strange_words(words)
words.select { |word| word.length < 6 || word[0] == 'e' }
end
When you run with your example, it gives you this output:
> strange_words(["taco", "eggs", "we", "eatihhg", "for", "dinner"])
=> ["taco", "eggs", "we", "eatihhg", "for"]
This is still not good code. You'll want to protect the methods from bad input.

trouble appending hash value ruby

I'm writing a program which takes input, stores it as a hash and sorts the values.
I'm having trouble comparing a current hash value with a variable.
Sample Input:
3
A 1
B 3
C 5
A 2
B 7
C 2
Sample Output:
A 1 2
B 3 7
C 2 5
Everything works apart from this part, and I'm unsure why.
if values.key?(:keys)
if values[keys] >= val
values.store(keys,val.prepend(val + " "))
else
values.store(keys,val.concat(" " + val))
end
else
values.store(keys,val)
end
i = i + 1
end
Rest of code:
#get amount of records
size = gets.chomp
puts size
size = size.to_i
values = Hash.new(0)
i = 0
while i < (size * 2)
text = gets.chomp
#split string and remove space
keys = text.split[0]
val = text.split[1]
#check if key already exists,
# if current value is greater than new value append new value to end
# else put at beginning of current value
if values.key?(:keys)
if values[keys] >= val
values.store(keys,val.prepend(val + " "))
else
values.store(keys,val.concat(" " + val))
end
else
values.store(keys,val)
end
i = i + 1
end
#sort hash by key
values = values.sort_by { |key, value| key}
#output hash values
values.each{|key, value|
puts "#{key}:#{value}"
}
Could anyone help me out? It would be most appreciated.
The short answer is that there are two mistakes in your code. Here is the fixed version:
if values.key?(keys)
if values[keys] >= val
values.store(keys,values[keys].prepend(val + " "))
else
values.store(keys,values[keys].concat(" " + val))
end
else
values.store(keys,val)
end
The if statement was always evaluating as false, because you were looking for hash key named :keys (which is a Symbol), not the variable you've declared named keys.
Even with that fixed, there was a second hidden bug: You were storing a incorrect new hash value. val.concat(" " + val) would give you results like A 2 2, not A 1 2, since it's using the new value twice, not the original value.
With that said, you code is still very confusing to read... Your variables are size, i, text, val, values, key and keys. It would have been a lot easier to understand with clearer variable names, if nothing else :)
Here is a slightly improved version, without changing the overall structure of your code:
puts "How may variables to loop through?"
result_length = gets.chomp.to_i
result = {}
puts "Enter #{result_length * 2} key-value pairs:"
(result_length * 2).times do
input = gets.chomp
input_key = input.split[0]
input_value = input.split[1]
#check if key already exists,
# if current value is greater than new value append new value to end
# else put at beginning of current value
if result.key?(input_key)
if result[input_key] >= input_value
result[input_key] = "#{input_value} #{result[input_key]}"
else
result[input_key] = "#{result[input_key]} #{input_value}"
end
else
result[input_key] = input_value
end
end
#sort hash by key
result.sort.to_h
#output hash result
result.each{|key, value|
puts "#{key}:#{value}"
}
h = Hash.new { |h,k| h[k] = [] }
input = ['A 1', 'B 3', 'C 5', 'A 2', 'B 7', 'C 2'].join("\n")
input.each_line { |x| h[$1] << $2 if x =~ /^(.*?)\s+(.*?)$/ }
h.keys.sort.each do |k|
puts ([k] + h[k].sort).join(' ')
end
# A 1 2
# B 3 7
# C 2 5
This would be a more Ruby-ish way to write your code :
input = "A 1
B 3
C 5
A 2
B 7
C 2"
input.scan(/[A-Z]+ \d+/)
.map{ |str| str.split(' ') }
.group_by{ |letter, _| letter }
.each do |letter, pairs|
print letter
print ' '
puts pairs.map{ |_, number| number }.sort.join(' ')
end
#=>
# A 1 2
# B 3 7
# C 2 5

How should I refactor this conditional code in ruby?

How to refactor this function?
def split_description(first_n)
description_lines = description.split "\n"
line_num = description_lines.length
if line_num > first_n
#description_first = to_html(description_lines[0..first_n].join("\n"))
#description_remain = to_html(description_lines[first_n + 1..line_num].join("\n"))
elsif line_num > 1
#description_first = to_html(description_lines[0..first_n].join("\n"))
#description_remain = ''
else
#description_first = ''
#description_remain = ''
end
end
I am a Ruby starter and encounter this rubocup warning: Method has too many lines. [13/10]
The following is whole code url:
https://github.com/RubyStarts3/YPBT-app/blob/master/views_objects/video_info_view.rb
Code
def split_description(description, first_n)
#description_first, #description_remain =
case description.count("\n")
when 0..first_n
[description, '']
else
partition_description(description, first_n)
end.map(&:to_html)
end
def partition_description(description, first_n)
return ['', description] if first_n.zero?
offset = 0
description.each_line.with_index(1) do |s,i|
offset += s.size
return [description[0,offset], description[offset..-1]] if i == first_n
end
end
I've assumed to_html('') #=> '', but if that's not the case the modification is straightforward.
Example
So that we can see the effect of to_html, let's define it thusly.
def to_html(description)
description.upcase
end
description =<<_
It was the best of times
it was the worst of times
it was the age of wisdom
it was the age of fools
_
split_description(description, 0)
#description_first
#=> ""
#description_remain
#=> "IT WAS THE BEST OF TIMES\n..WORST OF TIMES\n..AGE OF WISDOM\n..AGE OF FOOLS\n"
split_description(description, 1)
#description_first
#=> "IT WAS THE BEST OF TIMES\n"
#description_remain
#=> "IT WAS THE WORST OF TIMES\n..AGE OF WISDOM\n..AGE OF FOOLS\n"
split_description(description, 2)
#description_first
#=> "IT WAS THE BEST OF TIMES\nIT WAS THE WORST OF TIMES\n"
#description_remain
#=> "IT WAS THE AGE OF WISDOM\nIT WAS THE AGE OF FOOLS\n"
split_description(description, 3)
#description_first
#=> "IT WAS THE BEST OF TIMES\n..WORST OF TIMES\n..AGE OF WISDOM\n"
#description_remain
#=> "IT WAS THE AGE OF FOOLS\n"
split_description(description, 4)
#description_first
#=> "IT WAS THE BEST OF TIMES\n..WORST OF TIMES\n..AGE OF WISDOM\n..AGE OF FOOLS\n"
#description_remain
#=> ""
Explanation
Firstly, is appears that description is a local variable holding a string. If so, it must be an argument of the method (along with first_n).
def split_description(description, first_n)
We want to assign values to two instance variables, so let's begin by writing
#description_first, #description_remain =
There are really two steps: obtaining the desired strings and then mapping them with to_html. So let's first concentrate on the first step.
We will now condition on the number of lines in the string
case description.count("\n")
First, let's deal with the case where the string contains no newlines
when 0
[description, '']
If the string is empty this will be ['', '']; otherwise it will contain a single string without a newline.
Next, suppose the number of newlines in the string is between 1 and first_n. In this case #description_first is to be the entire string and #description_remain is to be empty.
when 1..first_n
[description, '']
As both when 0 and when 1..first_n return the same two-element array, we can combine them:
when 0..first_n
[description, '']
To get this far, first_n is less than the number of newlines. I've used another method for the case where the number of newlines is greater than first_n.
else
partition_description(description, first_n)
partition_description simply determines the offset into description of the first_nth newline, and then partitions the string accordingly.
Lastly, we need to end the case statement, map the array of two strings returned with to_html and end the method
end.map(&:to_html)
end
As I mentioned earlier, I've assumed to_html('') #=> ''. That seems to me to be the best place do deal with empty strings.
Note that I've dealt with the string directly, rather than splitting the string into lines, manipulating the lines and then rejoining them.
Since it's used or blanked in every condition, initialize the instance variables to blank.
def split_description(first_n)
description_lines = description.split "\n"
line_num = description_lines.length
#description_first = ''
#description_remain = ''
if line_num > first_n
#description_first = to_html(description_lines[0..first_n].join("\n"))
#description_remain = to_html(description_lines[first_n + 1..line_num].join("\n"))
elsif line_num > 1
#description_first = to_html(description_lines[0..first_n].join("\n"))
end
end
I'd also move the logic for description_lines[first_n + 1..line_num].join("\n") to a method like to_html( whatever_that_is( lines, from, to) ) or the like. Then it's not so bad if you repeat the same call and the name will describe what it's doing.
If first_n is always greater than 1 I think you can modify a little the Schwern's answer:
...
#description_first = to_html(description_lines[0..first_n].join("\n")) if line_num > 1
if line_num > first_n
#description_remain = to_html(description_lines[first_n + 1..line_num].join("\n"))
end
end
This should work :
def split_description(description, first_n = 0)
lines = description.each_line
#description_first = to_html(lines.take(first_n).join)
#description_remain = to_html(lines.drop(first_n).join)
end
take and drop replace all your logic, because, as #Cary Swoveland mentionned in a comment :
if you take too much, you end up with the complete array, without error message
if you drop too much, you end ud with an empty array, without error message
Example :
[1,2].take(99) #=> [1, 2]
[1,2].drop(99) #=> []
Also each_line outputs an Array of Strings, with newlines still present. No split, chomp or join("\n") is needed.

What happen with a condition on a case?

I was playing today and accidentally end up writting this, now I'm courious.
i = 101
case i
when 1..100
puts " will never happen "
when i == 101
puts " this will not appear "
else
puts " this will appear"
end
How ruby internally process when i == 101 is like i == (i == 101) ?
Your code is equivalent to:
if (1..100) === 101
puts " will never happen "
elsif (101 == 101) === 101
puts " this will not appear "
else
puts " this will appear"
end
If we look at Range#=== we see that
(1..100) === 101
is equivalent to:
(1..100).include?(101)
#=> false
(101 == 101) === 101 reduces to:
true === 101
We see from the doc for TrueClass#=== that this is equivalent to:
true == 101
#=> false
Hence, the else clause is executed.
The structure
case a
when x
code_x
when y
code_y
else
code_z
end
evaluates identically to
if x === a
code_x
elsif y === a
code_y
else
code_z
end
Each when calls the method === on the argument of when, passing the argument of case as the parameter (x === a is identical to x.===(a)). The === method is slightly different than ==: it is typically called "case subsumption". For simple types like numbers and strings, it is the same thing as ==. For Range and Array objects, it is a synonym for .include?. For Regexp objects, it is quite similar to match. For Module objects, it tests whether the argument is an instance of that module or one of its descendants (basically, if x === a then a.instance_of?(x)). Thus, in your code,
if (1..101) === i
...
elsif (i == 101) === i
...
else
...
end
which performs pretty much the same tests as
if (1..101).include?(i)
...
elsif (i == 101) == i
...
else
...
end
Note that there is another form of case that does not employ ===:
case
when x
code_x
when y
code_y
else
code_z
end
which is identical to
if x
code_x
elsif y
code_y
else
code_z
end
If you do when i == 101 its equivalent to:
i == (i == 101)
which for your code is equal to
101 == true # false
if you do the when case as follows:
when i == 101 ? i : false
It will enter to that block section
i = 101
case i
when 1..100
puts " will never happen "
when i == 101 ? i : false
puts " THIS WILL APPEAR "
else
puts " this will now NOT appear"
end
#> THIS WILL APPEAR

Regular expression to match number formats

I'm trying to write a small program that asks a user to input a number and the program will determine if it's a valid number. The user can enter any kind of number (integer, float, scientific notation, etc.).
My question is what regular expression should be used to keep something like "7w" from being matched as a valid number? Also I would like the number 0 to exit the loop and end the program, but as it's written now 0 is matching valid like any other number. Any insight please?
x = 1
while x != 0
puts "Enter a string"
num = gets
if num.match(/\d+/)
puts "Valid"
else
puts "invalid"
end
if num == 0
puts "End of program"
x = num
end
end
You need to execute this script from the command line, not a text editor, as you will be prompt to enter a number.
This is the ultra compact version
def validate(n)
if (Float(n) != nil rescue return :invalid)
return :zero if n.to_f == 0
return :valid
end
end
print "Please enter a number: "
puts "#{num = gets.strip} is #{validate(num)}"
Output:
Please enter a number: 00
00 is zero
Here is a longer version that problably you can extend it to tweak it to your needs.
class String
def numeric?
Float(self) != nil rescue false
end
end
def validate(n)
if n.numeric?
return :zero if n.to_f == 0
return :valid
else
return :invalid
end
end
print "Please enter a number: "
puts "#{num = gets.strip} is #{validate(num)}"
And here is a test for all the possible cases
test_cases = [
"33", #int pos
"+33", #int pos
"-33", #int neg
"1.22", #float pos
"-1.22", #float neg
"10e5", #scientific notation
"X", #STRING not numeric
"", #empty string
"0", #zero
"+0", #zero
"-0", #zero
"0.00000", #zero
"+0.00000", #zero
"-0.00000", #zero
"-999999999999999999999995444444444444444444444444444444444444434567890.99", #big num
"-1.2.3", #version number
" 9 ", #trailing spaces
]
puts "\n** Test cases **"
test_cases.each do |n|
puts "#{n} is #{validate(n)}"
end
Which outputs:
Please enter a number: 12.34
12.34 is valid
** Test cases ** 33 is valid
+33 is valid
-33 is valid
1.22 is valid
-1.22 is valid
10e5 is valid
X is invalid
is invalid
0 is zero
+0 is zero
-0 is zero
0.00000 is zero
+0.00000 is zero
-0.00000 is zero
-999999999999999999999995444444444444444444444444444444444444434567890.99 is valid
-1.2.3 is invalid
9 is valid
Source for the idea of checking if its numeric:
How can I see if the string is numeric?
http://mentalized.net/journal/2011/04/14/ruby_how_to_check_if_a_string_is_numeric/
To start with, you are overcomplicating the loop, secondly, your regular expression needs to be a bit more explicit. Try something like this:
x = 1
while x != 0
puts "Enter a string"
num = gets.chomp
if num == "0"
puts "exiting with 0"
exit 0
elsif num.match(/^[0-9](\.[0-9])?$+/)
puts "Valid"
else
puts "invalid"
end
end
This expression will match any number that contains a digit or a decimal, and will fail to match anything containing letters. If the string entered is exactly 0, the script will exit with status 0.

Resources