Why are non-ASCII characters not equal? - ruby

I have two test cases where a data_valid? method is called. The first returns false and the second returns true, why?
55: def data_valid? d
56: crc = d[-1]
57: data = d[1..-2]
58: len = d[0]
=> 59: binding.pry
60: (data ^ len) == crc
61: end
2.0.0 (#<MicroAeth::Message:0x007fbefc3ceae8>):0 > (data ^ len) == crc
=> false
2.0.0 (#<MicroAeth::Message:0x007fbefc3ceae8>):0 > (data ^ len)
=> "\xB1"
2.0.0 (#<MicroAeth::Message:0x007fbefc3ceae8>):0 > crc
=> "\xB1"
2.0.0 (#<MicroAeth::Message:0x007fbefc3ceae8>):0 > exit
have a good day!
F
From: /Users/rudolph9/Projects/CombustionEmissionsTesting/micro_aeth.rb # line 59 MicroAeth::Message#data_valid?:
55: def data_valid? d
56: crc = d[-1]
57: data = d[1..-2]
58: len = d[0]
=> 59: binding.pry
60: (data ^ len) == crc
61: end
2.0.0 (#<MicroAeth::Message:0x007fbefe83a8c8>):0 > (data ^ len) == crc
=> true
2.0.0 (#<MicroAeth::Message:0x007fbefe83a8c8>):0 > (data ^ len)
=> "+"
2.0.0 (#<MicroAeth::Message:0x007fbefe83a8c8>):0 > crc
=> "+"
The following is my extension of the String class where I'm comparing the return of the custom XOR method ^.
class ::String
###
# #return the first charater in the string as an integer
def byte
self.bytes[0]
end
###
# XOR two strings
# #str assumed to be a one byte string or integer
def ^ str
if str.class == String
str = str.byte
elsif str.class == Fixnum
nil
else
raise "invalid arg: #{str.class} \n Must be String or Fixnum"
end
self.bytes.each do |i|
str = str ^ i
end
str.chr
end
end
I believe it has something to do with the first comparing non-ASCII characters. How do I properly set up the conditional?

You can use String#force_encoding to force a string into a specified encoding
2.0.0-p195 :001 > "\xB1".encoding
=> #<Encoding:UTF-8>
2.0.0-p195 :002 > eight_bit = "\xB1".force_encoding(Encoding::ASCII_8BIT)
=> "\xB1"
2.0.0-p195 :003 > eight_bit.encoding
=> #<Encoding:ASCII-8BIT>
2.0.0-p195 :004 > eight_bit == "\xB1"
=> false
2.0.0-p195 :005 > eight_bit.force_encoding(Encoding::UTF_8) == "\xB1"
=> true
2.0.0-p195 :006 > eight_bit.force_encoding("\xB1".encoding) == "\xB1"
=> true
Note the default encoding for Ruby 2.0.0 is UTF-8

Related

How to delete a row from CSV in ruby with Table.delete_if()

I'm reading in a csv file
require 'csv'
recipients = CSV.read('recipients.csv', headers: true)
found = []
if User.find_by(email: recipients['email'])
found << recipients['email']
end
table = CSV.table('recipients.csv')
table.delete_if do |t|
found.each { |f| t['email'] == f['email']}
end
CSV.open('/tmp/users.csv', 'wb') do |w|
w << found.to_csv
end
The following line: found.each { |f| t['email'] == f['email']}
results in a
TypeError: no implicit conversion of String into Integer
from lib/scripts/foo.rb:13:in[]'`
It's obvious I don't grok how to use delete_if and how to resolve the type mismatch in this case.
So, help is very much appreciated.
in line number 13
found.each { |f| t['email'] == f['email']}
f is an instance of Array and your are trying to pass a String as an index, so this might have occurred.
Explanation
> a=[]
=> []
2.2.1 :015 > a['email']
TypeError: no implicit conversion of String into Integer
from (irb):15:in `[]'
> a=['email', 'ram']
=> ["email", "ram"]
2.2.1 :018 > a['email']
TypeError: no implicit conversion of String into Integer
from (irb):18:in `[]'

Globbing using braces on Ruby 1.9.3

Recent versions of Ruby support the use of braces in globbing, if you use the File::FNM_EXTGLOB option
From the 2.2.0 documentation
File.fnmatch('c{at,ub}s', 'cats', File::FNM_EXTGLOB) #=> true # { } is supported on FNM_EXTGLOB
However, the 1.9.3 documentation says it isn't supported in 1.9.3:
File.fnmatch('c{at,ub}s', 'cats') #=> false # { } isn't supported
(also, trying to use File::FNM_EXTGLOB gave a name error)
Is there any way to glob using braces in Ruby 1.9.3, such as a third-party gem?
The strings I want to match against are from S3, not a local file system, so I can't just ask the operating system to do the globbing as far as I know.
I'm in the process of packaging up a Ruby Backport for braces globbing support. Here are the essential parts of that solution:
module File::Constants
FNM_EXTGLOB = 0x10
end
class << File
def fnmatch_with_braces_glob(pattern, path, flags =0)
regex = glob_convert(pattern, flags)
return regex && path.match(regex).to_s == path
end
def fnmatch_with_braces_glob?(pattern, path, flags =0)
return fnmatch_with_braces_glob(pattern, path, flags)
end
private
def glob_convert(pattern, flags)
brace_exp = (flags & File::FNM_EXTGLOB) != 0
pathnames = (flags & File::FNM_PATHNAME) != 0
dot_match = (flags & File::FNM_DOTMATCH) != 0
no_escape = (flags & File::FNM_NOESCAPE) != 0
casefold = (flags & File::FNM_CASEFOLD) != 0
syscase = (flags & File::FNM_SYSCASE) != 0
special_chars = ".*?\\[\\]{},.+()|$^\\\\" + (pathnames ? "/" : "")
special_chars_regex = Regexp.new("[#{special_chars}]")
if pattern.length == 0 || !pattern.index(special_chars_regex)
return Regexp.new(pattern, casefold || syscase ? Regexp::IGNORECASE : 0)
end
# Convert glob to regexp and escape regexp characters
length = pattern.length
start = 0
brace_depth = 0
new_pattern = ""
char = "/"
loop do
path_start = !dot_match && char[-1] == "/"
index = pattern.index(special_chars_regex, start)
if index
new_pattern += pattern[start...index] if index > start
char = pattern[index]
snippet = case char
when "?" then path_start ? (pathnames ? "[^./]" : "[^.]") : ( pathnames ? "[^/]" : ".")
when "." then "\\."
when "{" then (brace_exp && (brace_depth += 1) >= 1) ? "(?:" : "{"
when "}" then (brace_exp && (brace_depth -= 1) >= 0) ? ")" : "}"
when "," then (brace_exp && brace_depth >= 0) ? "|" : ","
when "/" then "/"
when "\\"
if !no_escape && index < length
next_char = pattern[index += 1]
special_chars.include?(next_char) ? "\\#{next_char}" : next_char
else
"\\\\"
end
when "*"
if index+1 < length && pattern[index+1] == "*"
char += "*"
if pathnames && index+2 < length && pattern[index+2] == "/"
char += "/"
index += 2
"(?:(?:#{path_start ? '[^.]' : ''}[^\/]*?\\#{File::SEPARATOR})(?:#{!dot_match ? '[^.]' : ''}[^\/]*?\\#{File::SEPARATOR})*?)?"
else
index += 1
"(?:#{path_start ? '[^.]' : ''}(?:[^\\#{File::SEPARATOR}]*?\\#{File::SEPARATOR}?)*?)?"
end
else
path_start ? (pathnames ? "(?:[^./][^/]*?)?" : "(?:[^.].*?)?") : (pathnames ? "[^/]*?" : ".*?")
end
when "["
# Handle character set inclusion / exclusion
start_index = index
end_index = pattern.index(']', start_index+1)
while end_index && pattern[end_index-1] == "\\"
end_index = pattern.index(']', end_index+1)
end
if end_index
index = end_index
char_set = pattern[start_index..end_index]
char_set.delete!('/') if pathnames
char_set[1] = '^' if char_set[1] == '!'
(char_set == "[]" || char_set == "[^]") ? "" : char_set
else
"\\["
end
else
"\\#{char}"
end
new_pattern += snippet
else
if start < length
snippet = pattern[start..-1]
new_pattern += snippet
end
end
break if !index
start = index + 1
end
begin
return Regexp.new("\\A#{new_pattern}\\z", casefold || syscase ? Regexp::IGNORECASE : 0)
rescue
return nil
end
end
end
This solution takes into account the various flags available for the File::fnmatch function, and uses the glob pattern to build a suitable Regexp to match the features. With this solution, these tests can be run successfully:
File.fnmatch('c{at,ub}s', 'cats', File::FNM_EXTGLOB)
#=> true
File.fnmatch('file{*.doc,*.pdf}', 'filename.doc')
#=> false
File.fnmatch('file{*.doc,*.pdf}', 'filename.doc', File::FNM_EXTGLOB)
#=> true
File.fnmatch('f*l?{[a-z].doc,[0-9].pdf}', 'filex.doc', File::FNM_EXTGLOB)
#=> true
File.fnmatch('**/.{pro,}f?l*', 'home/.profile', File::FNM_EXTGLOB | File::FNM_DOTMATCH)
#=> true
The fnmatch_with_braces_glob (and ? variant) will be patched in place of fnmatch, so that Ruby 2.0.0-compliant code will work with earlier Ruby versions, as well. For clarity reasons, the code shown above does not include some performance improvements, argument checking, or the Backports feature detection and patch-in code; these will obviously be included in the actual submission to the project.
I'm still testing some edge cases and heavily optimizing performance; it should be ready to submit very soon. Once it's available in an official Backports release, I'll update the status here.
Note that Dir::glob support will be coming at the same time, as well.
That was a fun Ruby exercise!
No idea if this solution is robust enough for you, but here goes :
class File
class << self
def fnmatch_extglob(pattern, path, flags=0)
explode_extglob(pattern).any?{|exploded_pattern|
fnmatch(exploded_pattern,path,flags)
}
end
def explode_extglob(pattern)
if match=pattern.match(/\{([^{}]+)}/) then
subpatterns = match[1].split(',',-1)
subpatterns.map{|subpattern| explode_extglob(match.pre_match+subpattern+match.post_match)}.flatten
else
[pattern]
end
end
end
end
Better testing is needed, but it seems to work fine for simple cases :
[2] pry(main)> File.explode_extglob('c{at,ub}s')
=> ["cats", "cubs"]
[3] pry(main)> File.explode_extglob('c{at,ub}{s,}')
=> ["cats", "cat", "cubs", "cub"]
[4] pry(main)> File.explode_extglob('{a,b,c}{d,e,f}{g,h,i}')
=> ["adg", "adh", "adi", "aeg", "aeh", "aei", "afg", "afh", "afi", "bdg", "bdh", "bdi", "beg", "beh", "bei", "bfg", "bfh", "bfi", "cdg", "cdh", "cdi", "ceg", "ceh", "cei", "cfg", "cfh", "cfi"]
[5] pry(main)> File.explode_extglob('{a,b}c*')
=> ["ac*", "bc*"]
[6] pry(main)> File.fnmatch('c{at,ub}s', 'cats')
=> false
[7] pry(main)> File.fnmatch_extglob('c{at,ub}s', 'cats')
=> true
[8] pry(main)> File.fnmatch_extglob('c{at,ub}s*', 'catsssss')
=> true
Tested with Ruby 1.9.3 and Ruby 2.1.5 and 2.2.1.

Strictly convert string to integer (or nil)

For web programming, numbers come in as strings. but to_i will convert "5abc" to 5 and "abc" to 0, both wrong answers. To catch these, I wrote:
def number_or_nil( s )
number = s.to_i
number = nil if (number.to_s != s)
return number
end
Is there a neater, more Ruby-natural way of accomplishing this conversion and detecting that the string wasn't intended as a number?
Use Integer(string)
It will raise an ArgumentError error if the string cannot convert to an integer.
Integer('5abc') #=> ArgumentError: invalid value for Integer(): "5abc"
Integer('5') #=> 5
You'd still need your number_or_nil method if you want the behavior to be that nil is returned when a string cannot be converted.
def number_or_nil(string)
Integer(string || '')
rescue ArgumentError
nil
end
You should be careful to rescue from a particular exception. A bare rescue (such as "rescue nil") will rescue from any error which inherits from StandardError and may interfere with the execution of your program in ways you don't expect. Integer() will raise an ArgumentError, so specify that.
If you'd rather not deal with exceptions and just prefer a shorter version of your number_or_nil you can take advantage of implicit return values and write it as:
def number_or_nil(string)
num = string.to_i
num if num.to_s == string
end
number_or_nil '5' #=> 5
number_or_nil '5abc' #=> nil
This will work the way you expect.
Since at least Ruby 2.6, the kernel functions Integer, Float, etc. accept an exception keyword argument that does the job:
> Integer('42', exception: false)
=> 42
> Integer('x42', exception: false)
=> nil
> Integer('x42')
ArgumentError (invalid value for Integer(): "x42")
> Integer('', exception: false)
=> nil
> Integer('')
ArgumentError (invalid value for Integer(): "")
> Integer(nil, exception: false)
=> nil
> Integer(' 42 ', exception: false)
=> 42
> Integer(' 4 2 ', exception: false)
=> nil
Note that Integer also leaves you the control of the base, something that to_i does not support:
> '0xf'.to_i
=> 0
> Integer('0xf')
=> 15
> Integer('10', 8)
=> 8
When the base is specified, the radix-prefix (0x... etc.) must be consistent (if present):
> Integer('0xf', 10)
=> ArgumentError(invalid value for Integer(): "0xf")
> Integer('0xf', 16)
=> 15
> Integer('f', 16)
=> 15
Use a simple regex to check str is an integer.
def number_or_nil(str)
str.to_i if str[/^-?\d+$/] and str.line.size == 1
end

undefined method 'reverse' for 500

I am just learning ruby and this seems to be an easy mistake I am doing here right?
def palindromic(str)
str.to_s
if str.reverse == str
puts "it is a palindromic number!"
end
end
palindromic(500)
Instead I am getting an error
Project4.rb:5:in `palindromic': undefined method `reverse' for 500:Fixnum (NoMet
hodError)
from Project4.rb:10:in `<main>'
You need to change the line str.to_s to str=str.to_s. One example to show you why so is below :
num = 12
num.to_s # => "12"
num # => 12
num=num.to_s
num # => "12"
Basically String#to_s change the receiver instance to the instance of String.But if the receiver is already the String instance,in that case receiver itself will be returned.
ar = [1,2]
ar.object_id # => 77603090
ar.to_s.object_id # => 77602480
str = 'Hello'
str.object_id # => 77601890
str.to_s.object_id # => 77601890

escape \\\ to \ in ruby

ruby-1.9.2-p180 :023 > buffer = ''
ruby-1.9.2-p180 :024 > i = "buffer << \\\"[#user_id,#account_id]\\\""
=> "buffer << \\\"[#user_id,#account_id]\\\""
ruby-1.9.2-p180 :025 > eval i
SyntaxError: (eval):1: syntax error, unexpected $undefined
buffer << \"[#user_id,#account_id]\"
^
(eval):1: unterminated string meets end of file
from (irb):25:in `eval'
ruby-1.9.2-p180 :026 > j = "buffer << \"[#user_id,#account_id]\""
=> "buffer << \"[#user_id,#account_id]\""
ruby-1.9.2-p180 :027 > eval j
=> "[#user_id,#account_id]"
How do I convert i into j?
or
How to convert "buffer << \\"[#user_id,#account_id]\\"" into "buffer << \"[#user_id,#account_id]\" ?
The answer to your question "how do i convert i into j":
i.gsub(/\\/, '')
However, it very much looks as if the question is wrong and should rather be "how to I rewrite the surrounding code in order not having to do stuff like this in the first place".

Resources