How to use .ord and .chr properly in a loop? - ruby

I am trying to make a function that takes a jumbled sequence of letters and returns English. For some reason, I can't get word = (word.ord-4).chr to work properly. The secret to the code is that the letters are shifted 4 slots backwards, which is why I'm converting it to an integer first, subtracting 4, and then turning it back to a string.
The loop also appears to be ignoring the fact that I told it to skip a word if it's any of those special characters. What am I doing wrong?
Any suggestions or sources that will bring me closer to solving this problem?
def north_korean_cipher(coded_mesage)
input = coded_mesage.split('') # splits the coded message into array of letters
input.each do |word|
word = (word.ord - 4).chr
if word == '#' || '#' || '$' || '%' || '^' || '&' || '*'
next
end
end
print input
end
north_korean_cipher('m^aerx%e&gsoi!')

You want a mapping like this:
input: abcdefghijklmnopqrstuvwxyz
output: wxyzabcdefghijklmnopqrstuv
Unfortunately your approach doesn't work for the first 4 letters:
("a".ord - 4).chr #=> "]"
("b".ord - 4).chr #=> "^"
("c".ord - 4).chr #=> "_"
("d".ord - 4).chr #=> "`"
I'd use String#tr. It replaces each occurrence in the first string with the corresponding character in the second string:
"m^aerx%e&gsoi!".tr("abcdefghijklmnopqrstuvwxyz", "wxyzabcdefghijklmnopqrstuv")
#=> "i^want%a&coke!"
There's also a "c1-c2 notation to denote ranges of characters":
"m^aerx%e&gsoi!".tr("a-z", "w-za-v")
#=> "i^want%a&coke!"
The documentation further says:
If to_str is shorter than from_str, it is padded with its last character in order to maintain the correspondence.
So it can be used to easily replace the "special characters" with a space:
"m^aerx%e&gsoi!".tr("a-z##$%^&*", "w-za-v ")
#=> "i want a coke!"

This:
if word == '#' || '#' || '$' || '%' || '^' || '&' || '*'
does not do what you expect it to do, because '#' as a condition will always be true. You can't compare objects like that. You should do something like
if word == '#' || word == '#' || word == '$' || word == '%' || word == '^' || word == '&' || word == '*'
You can write it in a more succinct way by asking:
if %w(# # $ % ^ & *).include? word
Which checks if word is in the collection of options...

Related

Writing an iterpreter in Ruby, not able to consume whitespace properly

So I am following the "Writing an interpreter book" and implementing it in Ruby instead of Go. I am able to scan tokens like ; =, +, etc but it seems to behave differently when I have identifiers like let, 10, etc in my input string. Tried to hunt this bug this whole week but in vain so I thought a fresh pair of eyes might be able to catch it.
Here is an overview.
The codebase is very small and most of the logic resides in lib/lexer/lexer.rb
The class Lexer maintains the following state a cursor for the current character in the input string, a cursor for the next character and the current character in the input string
Lexer has the following methods
read_char which sets the data members to appropriate values
read_indentifier which is used to extract all the characters belonging to strings that are not reserved keywords but identifiers and call read_char before returning
read_number same as read_identifier but for numbers
consume_whitespace to skip over spaces, newlines, etc
next_token used to match the current character with the appropriate case and return its Token object defined in lib/token/token.rb and call read_char to increment the cursors before returning
require_relative '../token/token'
def is_letter(ch) #basically decides syntax acceptable for variable names
#puts ch.class
'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_'
end
def is_digit(ch) #checks if digit
'0' <= ch && ch <= '9'
end
class Lexer
def initialize(input)
#input = input
#position = 0
#readPosition = 0
#ch =''
read_char
end
def read_char
#puts caller[0]
#ch = #readPosition >= #input.length ? '' : #input[#readPosition]
#position = #readPosition
#readPosition += 1
#puts "INSIDE READ_CHAR #{#position} #{#readPosition} #{#ch}"
end
# SUPPOSED TO BE A LOOP WAS JUST A CONDITION. NOW FIXED.
def consume_whitespace
while #ch == ' ' || #ch =='\t' || #ch == '\n' || #ch == '\r' do
read_char
end
end
def read_identifier
pos = #position
#puts "RI: char #{#ch} pos #{pos} position #{#position}"
while is_letter(#ch) do
#puts #ch
read_char
end
puts "METHOD read_identifier: char #{#ch} pos #{pos} position #{#position}\n"
#input[pos..#position-1]
end
def read_number
pos = #position
#puts "RN: char #{#ch} pos #{pos} position #{#position}"
while is_digit(#ch) do
read_char
end
puts "METHOD read_number: char #{#ch} pos #{pos} position #{#position}\n"
#input[pos..#position-1]
end
def next_token
#puts #ch, #ch.class
#puts "\nX=X=X=X=X=X=X=X=X=: #{#ch}, #{#ch.ord}, X=X=X=X=X=X=X=X=X=\n"
tok = nil
consume_whitespace
tok =
case #ch
when '=' then Token.new(ASSIGN, #ch)
when '+' then Token.new(PLUS, #ch)
when '-' then Token.new(MINUS, #ch)
when '/' then Token.new(DIVIDE, #ch)
when '*' then Token.new(MULTIPLY, #ch)
when '%' then Token.new(MODULO, #ch)
#when '==' then Token.new(EQUAL_TO, #ch)
when '>' then Token.new(GREATER_THAN, #ch)
when '<' then Token.new(LESS_THAN, #ch)
#when '!=' then Token.new(UNEQUAL_TO, #ch)
#when '&&' then Token.new(AND, #ch)
#when '||' then Token.new(OR, #ch)
when '!' then Token.new(NOT, #ch)
when ',' then Token.new(COMMA, #ch)
when ';' then Token.new(SEMICOLON, #ch)
when '?' then Token.new(QUESTION, #ch)
when '(' then Token.new(LPAREN, #ch)
when ')' then Token.new(RPAREN, #ch)
when '[' then Token.new(LSQUARE, #ch)
when ']' then Token.new(RSQUARE, #ch)
when '{' then Token.new(LCURLY, #ch)
when '}' then Token.new(RCURLY, #ch)
else
#puts 'hello from next_token', #ch.ord
# STATE WAS BEING MUTATED NOW FIXED
puts "letter #{#ch}"
puts "letter ascii #{#ch.ord}"
#puts "isletter "
if is_letter(#ch)
literal = read_identifier
Token.new(look_up_ident(literal), literal)
elsif is_digit(#ch)
Token.new(INT, read_number)
else
Token.new(ILLEGAL, "ILLEGAL")
end
end
read_char
return tok
end
end
Now the rake test failures weren't helpful in debugging so I decided to do it simply by writing a main.rb script which would import and run my lexer and a sprinkled a lot of putss throughout the codebase
This is my main.rb
require_relative 'lib/lexer/lexer'
lex = Lexer.new('five = 5;
ten = 10;')
i = 1
while i <= 8
tok = lex.next_token
puts "\nIN_MAIN: #{tok.type} ==> #{tok.literal}\n\n"
i=i+1
end
This is the output of ruby main.rb
letter f
letter ascii 102
METHOD read_identifier: char pos 0 position 4
IN_MAIN: IDENTIFIER ==> five
IN_MAIN: = ==> =
letter 5
letter ascii 53
METHOD read_number: char ; pos 7 position 8
IN_MAIN: INT ==> 5
letter
letter ascii 10
IN_MAIN: ILLEGAL ==> ILLEGAL
letter t
letter ascii 116
METHOD read_identifier: char pos 27 position 30
IN_MAIN: IDENTIFIER ==> ten
IN_MAIN: = ==> =
letter 1
letter ascii 49
METHOD read_number: char ; pos 33 position 35
IN_MAIN: INT ==> 10
letter
Traceback (most recent call last):
2: from main.rb:8:in `<main>'
1: from /home/palash25/gundoochy/lib/lexer/lexer.rb:89:in `next_token'
/home/palash25/gundoochy/lib/lexer/lexer.rb:89:in `ord': empty string (ArgumentError)
We can ignore the last line because I am not able to handle how to return an object for EOF right now but here is the gist of what is happening before that
The lexer is able to scan the tokens correctly till five = 5 after that it skips over the next immediate character which was ; and does return a token object for that and instead returns a token object of ILLEGAL type for the \n that is right after ; (I even printed out the ascii values of the character to know for sure it was the \n returning and ILLEGAL)
This should not have happened since consume_whitespace is supposed to skip over all kinds of whitespace but it still didn't for newlines anyway after that we are able to scan the next line that is ten = 10 but the last semicolon is nowhere to be seen in the output just like the first one
If I use an input string without any identifier or number it works perfectly fine.
Here is the link to the full codebase https://gitlab.com/palash25/gundoochy
In your Lexer code (which you should include in your original question), you have the following method:
def consume_whitespace
while #ch == ' ' || #ch =='\t' || #ch == '\n' || #ch == '\r' do
read_char
end
end
Here, you attempt to specify various whitespace characters. However, since you have named them with single quotes, the escape sequences with the backslash are not applied. Instead, you consume a literal backslash followed by an t, n, or r character.
If you use double quotes here, the characters in your source code are interpreted as tab, newline, or carriage return characters respectively:
def consume_whitespace
while #ch == ' ' || #ch == "\t" || #ch == "\n" || #ch == "\r" do
read_char
end
end

Condense several conditional statement comparing the same value

I want to know if there is a way to condense this line of code:
elsif i == '+' || i == '-' || i == '/' || i == '*'
A case when control structure allows such a condensed line:
case i
when '+', '-', '/', '*' # <= condensed line of code
puts "operator!"
end
you could do
"+-/*".include?(i)
Similar to #Subash but you can also do this since:
#this returns the match string of i which is truthy or false if no match.
elsif "+-/*"[i]
if you want to return a boolean true or false you can also double bang
elsif !!"+-/*"[i] #true if matched, false if not
There are many variants of this in ruby, if you had a regex or some other type of string match you might also use
i = '/'
!!"+-/*".match(i) #true

Longest word test from appacademy practice in ruby

I am trying to do this test and there are bunch of solutions online and here but I first want to figure out why my solution is wrong even though it seems that it puts right results when I enter certain strings :
Here is what they are asking :
Write a method that takes in a string. Return the longest word in the
string. You may assume that the string contains only letters and
spaces.
You may use the String split method to aid you in your quest.
Here is my solution where I thought I could turn string into array, sort it from max length descending and then just print first element in that new string like this :
def longest_word(sentence)
sentence = sentence.split
sentence.sort_by! { |longest| -longest.length }
return sentence[0]
end
That doesn't seem to work obviously since their test gives me all false..here is the test :
puts("\nTests for #longest_word")
puts("===============================================")
puts(
'longest_word("short longest") == "longest": ' +
(longest_word('short longest') == 'longest').to_s
)
puts(
'longest_word("one") == "one": ' +
(longest_word('one') == 'one').to_s
)
puts(
'longest_word("abc def abcde") == "abcde": ' +
(longest_word('abc def abcde') == 'abcde').to_s
)
puts("===============================================")
So the question is why? And can I just fix my code or the idea is all wrong and I need to do it completely different?
str = "Which word in this string is longest?"
r = /[[:alpha:]]+/
str.scan(r).max_by(&:length)
#=> "longest"
This regular expression reads, "match one or more characters". The outer brackets constitute a character class, meaning one of the characters within the brackets must be matched.
To deal with words that are hyphenated or contain single quotes, the following is an imperfect modification1:
str = "Who said that chicken is finger-licken' good?"
r = /[[[:alpha:]]'-]+/
str.scan(r).max_by(&:length)
#=> "finger-licken'"
This regular expression reads, "match one or more characters that are a letter, apostrophe or hyphen". The outer brackets constitute a character class, meaning one of the characters within the brackets must be matched.
1 I've successfully used "finger-licken'" in scrabble.
I'd write it something like:
str = "Write a method that takes in a string"
str.split.sort_by(&:length).last # => "string"

Ruby, looping through a string deleting groups of characters until a desired output is achieved

I have a coding problem I solved and want to refactor. I know there has to be a cleaner way of doing what I did.
The goal is to write a method that takes a string of "!" and "?" and reduces the string by eliminating all odd groupings of each symbol.
Example - a string "????!!!" would have an odd grouping of "!!!" because there are three in a row. These would be deleted from the string.
If there is only one "!" or "?" its left because it is not in a group.
Ex -
remove("!????!!!?") answer == "!"
# => ("!????!!!?" --> "!?????" --> "!")
In the first string, the only odd grouping is "!!!", once removed, it leaves a new string with an odd grouping "?????". You remove the next odd grouping so you're left with "!". This fits the desired output.
Another example
remove("!???!!") == ""
# => ("!???!!" --> "!!!" --> "")
Current code:
def remove(s)
arr = [s]
i = 0
until i == arr[0].length
s = s.chars.chunk{|c|c}.map{ |n,a| a.join }.select{|x| x if x.length.even? || x.length <= 1}.join
arr << s
i += 1
end
return arr[-1]
end
My code solves this problem and all test cases. I have a suspicion that my until loop can be removed/refactored so that I could solve this problem in one line and have spent hours trying to figure it out with no luck.
Suppose
str = "???!!!???!"
If we first remove the two groups "???" we are left with "!!!!", which cannot be reduced further.
If we first remove the group "!!!" we are left with "??????!", which cannot be reduced further.
If we are permitted to remove all odd groups of either character without reference to the effect that either has on the other, we obtain !, which cannot be reduced further.
It's not clear what rule is to be used. Here are three possibilities and code to implement each.
I will use the following two regular expressions, and in the first two cases a helper method.
Rq = /
(?<!\?) # do not match a question mark, negative lookbehind
\? # match a question mark
(\?{2})+ # match two question marks one or more times
(?!\?) # do not match a question mark, negative lookahead
/x # free-spacing regex definition mode
which is commonly written /(?<!\?)\?(\?{2})+(?!\?)/.
Similarly,
Rx = /(?<!!)!(!{2})+(?!!)/
def sequential(str, first_regex, second_regex)
s = str.dup
loop do
size = s.size
s = s.gsub(first_regex,'').gsub(second_regex,'')
return s if s.size == size
end
end
I apply each of the three methods below to two example strings:
str1 = "???!!!???!"
str2 = 50.times.map { ['?', '!'].sample }.join
#=> "?!!!?!!!?!??????!!!?!!??!!???!?!????!?!!!?!?!???!?"
Replace all odd groups of "?" then odd groups of "!" then repeat until no further removals are possible
def question_before_exclamation(str)
sequential(str, Rq, Rx)
end
question_before_exclamation str1 #=> "!!!!"
question_before_exclamation str2 #=> "??!??!?!!?!?!!?"
Replace all odd groups of "!" then odd groups of "?" then repeat until no further removals are possible
def exclamation_before_question(str)
sequential(str, Rx, Rq)
end
exclamation_before_question str1 #=> "??????!"
exclamation_before_question str2 #=> "??!????!!?!?!!?!?!!?"
Replace all odd groups of both "?" and "!" then repeat until no further removals are possible
Rqx = /#{Rq}|#{Rx}/
#=> /(?-mix:(?<!\?)\?(\?{2})+(?!\?))|(?-mix:(?<!!)!(!{2})+(?!!))/
def question_and_explanation(str)
s = str.dup
loop do
size = s.size
s = s.gsub(Rqx,'')
return s if s.size == size
end
end
question_and_explanation str1 #=> "!"
question_and_explanation str2 #=> "??!?!!?!?!!?!?!!?"
I don't know the exact Ruby syntax for this, but you could simplify your solution by using regular expressions:
Gather all matches of consecutive characters
if all matches are of even length or 1 exit
Test if matches are an odd length
if an odd length, replace with the empty string
else do nothing
Goto step 1
A solution in Perl would be:
#!perl
use strict;
use warnings;
use feature qw(say);
my $string = '!????!!!?';
sub reduce {
my ($s) = #_;
while ( my #matches = $s =~ m/((.)\2+)/g ) {
last if ! grep { length($_) > 1 && length($_) % 2 == 1 } #matches;
foreach my $match ( #matches ) {
$s =~ s/\Q$match// if length($match) > 1 && length($match) % 2 == 1;
}
}
return $s;
}
say reduce($string);
I could be wrong (this is ruby, after all) but I don't think you'll find a one-liner for this because ruby's utility functions generally aren't recursive. But you can use regex to simplify your logic, at the very least:
def remove(s)
while s =~ /(?<!\!)\!([\!]{2})+(?!\!)/ || s =~ /(?<!\?)\?([\?]{2})+(?!\?)/
s.gsub! /(?<!\!)\!([\!]{2})+(?!\!)/, "" # remove odd !
s.gsub! /(?<!\?)\?([\?]{2})+(?!\?)/, "" # remove odd ?
end
return s
end
To make the regex less mind-boggling, it helps to look at them with 'a' instead of '?' and '!':
/(?<!a)a([a]{2})+(?!a)/ #regex for 'a'
(?<!a) #negative lookbehind: the match cannot start with an 'a'
a([a]{2})+ #the match should be an 'a' followed by 1 or more pairs
(?!a) #negative lookahead: the match cannot end with an 'a'
It should be simple enough with a regular expression replacement
def remove(string)
begin
original = string
string.gsub!(/(\!{3,})|(\?{3,})/) { |s| s.length.even? ? s : '' }
end until original == string
string
end
puts remove("!????!!!?").inspect # answer == "!"
puts remove("!???!!").inspect # answer == ""
puts remove("!????!!").inspect # answer == "!????!!"

writing ruby regular expression

I would like to know how I can write a Ruby regular expression that indicates it should start with alphanumeric characters followed by only alphanumeric and - (in any sequence) after that.
So to begin with alphanumeric I know it's:
/\A[A-Za-z0-9]/
How do I say I can only allow alphanumeric characters and - after this? I am new to Ruby and regular expressions. Any suggestions ?
Are there any links I can look into for learning about regular expressions and Ruby in much more depth? I found http://rubylearning.com/satishtalim/ruby_regular_expressions.html to be useful.
You already have the initial alphanumeric character class [A-Za-z0-9]. For the next characters, you just want to add - to this: [A-Za-z0-9-]. Hence the final regex is:
[A-Za-z0-9][A-Za-z0-9-]*
Note that X* means "X 0 or more times". If you want "X 1 or more times", use X+.
r1 = /^[A-Za-z0-9][A-Za-z0-9-]*$/ #=> /^[A-Za-z0-9][A-Za-z0-9-]*$/
r2 = /^[A-Za-z0-9-]*$/ #=> /[A-Za-z0-9-]*$/
str = "3birdsweresittingonawire-nowtherearebuttwo"
str =~ r1 #=> 0 (truey)
or
(str =~ r2) and str[0] != '-' #=> true
str = " % 3birdsweresittingonawire-nowtherearebuttwo"
str =~ r1 #=> nil
(str =~ r2) and str[0] != '-' #=> nil
The second example shows why you need the anchors ^ and $.

Resources