What's the most-efficient manner to remove beginning and ending spaces around a string, then convert the string to nil if the resulting value is zero-length?
For example:
> a=''
> squash(a)
=> nil
> a=' '
> squash(a)
=> nil
> a=' xyz '
> squash(a)
=> 'xyz'
> a=nil
> squash(a)
=> nil
Thus far:
def squash(value)
return nil if value.nil?
value.strip!
(value.blank? ? nil : value)
end
Seems like there could be a more-terse way of implementing this.
** edit **
While I am working in Rails, it would be nice if the answer would contain a Ruby-only implementation, too.
I should emphasize that the implementation needs to be able to handle a string with a nil value.
Assuming you want this for rails (otherwise blank? is undefined) you can use presence method:
def squash(value)
value && value.strip.presence
end
In pure ruby, I would do:
def squash(value)
return unless value
value = value.strip
value unless value.empty?
end
This will work with plain Ruby:
def squash(str)
str = str.to_s.strip
str unless str.empty?
end
Here's one way:
def squash(str)
(str && str[/\S/]) ? str.strip : nil
end
/\S/ looks for a character that is not whitespace.
squash " My dog has fleas. " #=> "My dog has fleas."
squash " " #=> nil
squash nil #=> nil
Reader challenge
I tried to also implement squash!, that would convert the argument str in place. If str is nil, just leave it alone. If str contains a least one non-whitespace character, then str.strip!. However, I could not figure out a way to convert a string to nil. I wanted to do this when the string is empty or contains only whitespace, but the problem is to convert any string, or more generally, any non-nil object, to nil, when the object is received as a method argument. Can it be done? [Edit: #Stefan says the type cannot be changed. I'm sure he's right, but I would like to see where that is written and understand why it is not permitted. Anyone? tidE].
This handles all your examples.
def squash(value)
value.to_s.strip.empty? ? nil : value.strip
end
Just adding this because it's short:
def squash(str)
str.to_s[/\S(.*\S)?/]
end
squash(nil) #=> nil
squash("") #=> nil
squash(" ") #=> nil
squash("a") #=> "a"
squash(" a") #=> "a"
squash("a ") #=> "a"
squash(" a ") #=> "a"
squash(" foo ") #=> "foo"
squash(" foo bar ") #=> "foo bar"
Here's a plain ruby version:
def squash(str)
str && str.strip! && (str unless str.empty?)
end
Update - If you want a version without side effects:
def squash(str)
str && (x = str.strip) && (x unless x.empty?)
end
If you want the method name squash with argument value.
def squash(value)
return value unless value.instance_of?(String)
return if value.strip!&.empty?
value
end
Features:
Works either on pure Ruby or Ruby on Rails
Works with other data types than string as well, for example, you can pass a number if you want
Testing:
squash('ok')
#=> ok
squash('')
#=> nil
squash(' ')
#=> nil
squash(' xyz ')
#=> 'xyz'
squash('xyz ')
#=> 'xyz'
squash(' xyz')
#=> 'xyz'
squash(123)
#=> 123
squash(nil)
#=> nil
Note:
I use safe navigation operator, which was released in Ruby 2.3.0. So make sure before using it.
irb(main):001:0> s = " string "
=> " string "
irb(main):002:0> s.strip!
=> "string"
irb(main):003:0> s.blank?
NoMethodError: undefined method `blank?' for "string":String
from (irb):3
from C:/RUBY/BIN/irb:12:in `'
irb(main):004:0>
I think blank is not Ruby but Rails? Anyway, what's wrong with
(value.length == 0 ? nil : value)
or even better
value.empty? ? nil : value
At least everybody would understand what the intention is here.
Related
is there a built in way to get first match or nil for a character from string.
i can get a first character like this
'word'.match('o').to_s
but if there is no match it gives "" not nil
Use String#[match_str]:
If a match_str is given, that string is returned if it occurs in the string.
Returns nil if (...) the match string cannot be found.
'word'['o'] #=> 'o'
'word'['q'] #=> nil
The [] method is very versatile, you can also pass a regular expression or indices in different ways.
You can do it in such way:
'word'.scan('o').first #=> "o"
'word'.scan('e').first #=> nil
'word'.match(?o)[0] rescue nil
#=> "o"
'word'.match(?q)[0] rescue nil
#=> nil
'word'.match(/(o)/) && $1
#⇒ "o"
'word'.match(/(s)/) && $1
#⇒ nil
In ruby nil.to_f and "".to_f return 0.0, and "1foo2".to_f returns 1.0. In these cases I want to get nil.
What I want are:
nil.my_to_f #=> nil
"".my_to_f #=> nil
"1foo2".my_to_f #=> nil
"3".my_to_f #=> 3.0
"3.5".my_to_f #=> 3.5
'.3'.my_to_f #=> nil
'.'.my_to_f #=> nil
To enable this function I wrote like this:
match(/^([\d\.]+)$/){ $1.to_f if $1 }
It works fine for first five examples, but fails last two.
How can I convert String to Float like the examples?
If there is a Gem for it, I'm willing to use it.
Update
With my code nil raise error, sorry. I'll fix it.
You may use Float class for this. Beware it raises ArgumentError in case of wrong value:
def my_to_f(n)
Float(n)
rescue
0.0 if n == "."
end
my_to_f nil #=> nil
my_to_f "" #=> nil
my_to_f "1foo2" #=> nil
my_to_f "3" #=> 3.0
my_to_f "3.5" #=> 3.5
my_to_f ".3" #=> 0.3
my_to_f "." #=> 0.0
You could do this:
class NilClass
def my_to_f
nil
end
end
class String
def my_to_f
r = /
^ # match beginning of string
-? # optionally match minus sign
\d+ # match > 0 digits
(?:\.\d*)? # optionally match decimal, >= 0 digits in non-capture group
$ # match end of string
/x
s = self[r]
s ? s.to_f : nil
end
end
nil.my_to_f #=> nil
"".my_to_f #=> nil
"1foo2".my_to_f #=> nil
"3".my_to_f #=> 3.0
"3.5".my_to_f #=> 3.5
".3".my_to_f #=> nil
".".my_to_f #=> nil
"3.".my_to_f #=> 3.0
"-2.4". my_to_f #=>-2.4
If you want "3.".my_to_f to return nil, change \d* to \d+.
I have a function like this:
def check_if_correct_type(type, value)
# nil.test!
# eval(type.classify(value)) rescue return false
# true
case type
when "integer"
!!Integer(value) rescue return false
when "float"
!!Float(value) rescue return false
else
return true
end
true
end
A sample would be
check_if_correct_type("integer", "a")
I tried changing the function like this:
check_if_correct_type(type, value)
!!(eval(type.classify(value))) rescue return false
true
end
This is throwing errors. How do I fix this. I am fairly new to meta programming so kind of lost.
Update 1:
"adfadf".kind_of?(String) #=> true
123.kind_of?(String) #=> false
# The "Fixnum" class is actually used for integers
"adfadf".kind_of?(Fixnum) #=> false
123123.kind_of?(Fixnum) #=> true
12.3.kind_of?(Float) #=> true
"sadf".kind_of?(Float) #=> false
12.kind_of?(Float) #=> false
The above will not work for me as the kind_of? function will find the type of the object where as for me the answer requires to be like this:
check_if_correct_type("integer", "1221") #=> true
check_if_correct_type("float", "1.24") #=> true
check_if_correct_type("string", "asds12") #=> true
check_if_correct_type("float", "asdasd1.24") #=> false
where as
"1.24".kind_of?(Float) #=> false
That is why conversion works for me. Hope the question is more clear now.
Update 2:
This is what I get if I use public send.
!!public_send("integer".capitalize("1"))
ArgumentError: wrong number of arguments (1 for 0)
from (pry):4:in capitalize'
[5] pry(main)> !!public_send("integer".classify("1"))
ArgumentError: wrong number of arguments (1 for 0)
from /home/aravind/.rbenv/versions/2.2.0/lib/ruby/gems/2.2.0/gems/activesupport-4.2.0/lib/active_support/core_ext/string/inflections.rb:187:inclassify'
Note: classify is a part of Ruby on Rails and not Ruby.
I suggest you write your method as follows:
def correct_type?(type, str)
case type.downcase
when "integer"
!!to_integer(str)
when "float"
!!to_float(str)
else
raise ArgumentError, "type must be 'integer' or 'float'"
end
end
where to_integer(value) (to_float(value)) is a method that returns value.to_i (value.to_f) if value is the string representation of an integer (a float), else returns nil. The methods to_integer and to_float are useful because they tell you both whether the string can be converted to the given numerical type, and if it can, give you the numerical value.
Before considering how you might implement to_integer and to_float, I would like to call into question the need for correct_type?. Rather than:
str = "33"
if correct_type?("integer", str)
n = str.to_i
puts n
else
...
end
would it not be better to write:
if (n = to_integer("33"))
puts n
else
...
end
There are basically two ways to write the methods to_integer and to_float. The first is the approach you took:
def to_integer(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
Integer(s) rescue nil
end
def to_float(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
return nil if to_integer(s)
Float(s) rescue nil
end
to_integer("3") #=> 3
to_integer("-3") #=> -3
to_integer("+ 3") #=> 3
to_integer("cat") #=> nil
to_integer("3.14") #=> nil
to_integer(:cat) #=> ArgumentError: ArgumentError
to_float("3.14") #=> 3.14
to_float("-3.14") #=> -3.14
to_float("+ 3.14") #=> 3.14
to_float("cat") #=> nil
to_float("3") #=> nil
to_float(:cat) #=> ArgumentError: ArgumentError
The second approach is to use a regular expression:
def to_integer(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
s[/^[+-]?\s*\d+$/] ? s.to_i : nil
end
def to_float(str)
raise ArgumentError unless str.is_a? String
s = str.gsub(/\s/,'')
return nil if to_integer(s)
s[/^[+-]?\s*\d+\.\d+$/] ? s.to_f : nil
end
to_integer("3") #=> 3
to_integer("-3") #=> -3
to_integer("+ 3") #=> 3
to_integer("cat") #=> nil
to_integer("3.14") #=> nil
to_integer(:cat) #=> ArgumentError: ArgumentError
to_float("3.14") #=> 3.14
to_float("-3.14") #=> -3.14
to_float("+ 3.14") #=> 3.14
to_float("cat") #=> nil
to_float("3") #=> nil
to_float(:cat) #=> ArgumentError: ArgumentError
There is no need to use eval to send a message. You can just use send instead:
def check_if_correct_type(type, value)
!!send(type.capitalize, value) rescue return false
true
end
Note: there is no method named classify anywhere in either the Ruby core library or the Ruby standard libraries. Note also that it is a very bad idea, to just blindly catch all exceptions.
I don't see a point of using metaprogramming for this example. You should avoid using it where there's no need for it. Generally speaking, your program's logic should be:
a) Check the type of the value entered.
b) Compare the type with the type entered as argument. Or in code:
def check_if_correct_type(type, value)
actual_type = value.class.name
return actual_type.downcase == type.downcase
end
p check_if_correct_type('string', 'test') #=> true
p check_if_correct_type('integer', 'test2') #=> false
This could could be made even shorter in one line, but did it in two to demonstrate more clearly what's going on.
If you want to check an object's class, the right way is this:
"adfadf".kind_of?(String) #=> true
123.kind_of?(String) #=> false
# The "Fixnum" class is actually used for integers
"adfadf".kind_of?(Fixnum) #=> false
123123.kind_of?(Fixnum) #=> true
12.3.kind_of?(Float) #=> true
"sadf".kind_of?(Float) #=> false
12.kind_of?(Float) #=> false
There is no reason to be using the Integer() or Float() methods to check for a type. Those are type conversion methods, they will convert other types to Float or Fixnum. If you do want to try to convert a type is convertable to Float or numeric, that is one way to do it, but there might be better ways.
In general, you should never plan on raising and rescuing an exception as part of ordinary program flow; one reason is because it is very slow. Exceptions should be used for errors and unusual/exceptional conditions, not routine conditions such that exceptions will be frequently raised.
And definitely don't start bringing eval into it, geez why would you do that?
This is how I have ended up solving my problem
def check_if_correct_type(type, value)
!!eval("#{type.classify}(value)") rescue return false
true
end
Sample output for this function is below incase you are wondering if it words or not
[25] pry(main)> value = "1"
=> "1"
[26] pry(main)> !!eval("#{type.classify}(value)")
=> true
[27] pry(main)> value = "a"
=> "a"
[28] pry(main)> !!eval("#{type.classify}(value)")
ArgumentError: invalid value for Float(): "a"
from (pry):28:in `eval'
[29] pry(main)> value = "1.4"
=> "1.4"
[30] pry(main)> type = "integer"
=> "integer"
[31] pry(main)> !!eval("#{type.classify}(value)")
ArgumentError: invalid value for Integer(): "1.4"
from (pry):31:in `eval'
Given strings like:
Bob
Bob,
Bob
Bob Burns,
How can you return that w/o a comma?
Bob
Bob
Bob
Bob Burns
Also, I would want this method not to break if passed a nil, just to return a nil?
def remove_trailing_comma(str)
!str.nil? ? str.replace(",") :nil
end
My thought would be to use string.chomp:
Returns a new String with the given record separator removed from the end of str (if present).
Does this do what you want?
def remove_trailing_comma(str)
str.nil? ? nil : str.chomp(",")
end
use String#chomp
irb(main):005:0> "Bob".chomp(",")
=> "Bob"
irb(main):006:0> "Bob,".chomp(",")
=> "Bob"
irb(main):007:0> "Bob Burns,".chomp(",")
=> "Bob Burns"
UPDATE:
def awesome_chomp(str)
str.is_a?(String) ? str.chomp(",") : nil
end
p awesome_chomp "asd," #=> "asd"
p awesome_chomp nil #=> nil
p awesome_chomp Object.new #=> nil
You could do something like this:
str && str.sub(/,$/, '')
As everyone said, chomp will do the trick.
Starting from Ruby 2.3 you can use the safe navigation operator like this: str&.chomp(','). It will only execute chomp if str is not nil, otherwise it will return nil.
you could use
str.delete_suffix(',')
Why isnt that working:
>> s = "hi"
=> "hi"
>> s == ("hi"|"ho")
NoMethodError: undefined method `|' for "hi":String
from (irb):2
>>
I don't get it.. Is there a solution for this kind of syntax? Because
s == ("hi"|"ho")
#is shorther than
s == "hi" || s == "ho"
Yes, the bitwise operator | is not defined in the String class: http://ruby-doc.org/core/classes/String.html
Consider this for expressiveness:
["hi", "ho"].include? myStr
irb(main):001:0> s = "hi"
=> "hi"
irb(main):002:0> ["hi", "ho"]
=> ["hi", "ho"]
irb(main):003:0> ["hi", "ho"].include? s
=> true
irb(main):004:0> s = "foo"
=> "foo"
irb(main):005:0> ["hi", "ho"].include? s
=> false
In most high level languages that syntax will not work, you have to stick to the longer syntax of:
s == "hi" || s == "ho"
Note that | is a bitwise or, whereas || is a regular or
You could use the include? method on array if you've got several == tests to do:
["hi", "ho"].include?(s)
Not shorter for two checks admittedly but it will be shorter for three or more.
This syntax doesn't exist in any language as far as I know.
What you are saying
s == ("hi"|"ho")
Literally translates to 'bitwise OR the strings "hi" and "ho" together and then compare them with s'. If you can't see why this is not what you are looking for, try writing down the ASCII codes for "hi" and "ho" and then bitwise ORing them together. You are going to get complete gibberish.
You could make it work that way:
irb> class Pair
def initialize(strA,strB)
#strA,#strB = strA,strB
end
def ==(string)
string == #strA || string == #strB
end
def |(other)
Pair.new(self,other)
end
end
#=> nil
irb> class String
def |(other)
Pair.new(self,other)
end
alias old_equals :==
def ==(other)
if other.kind_of? Pair
other == self
else
old_equals other
end
end
end
#=> nil
irb> ("one"|"two") == "one"
#=> true
irb> ("one"|"two") == "two"
#=> true
irb> ("one"|"two") == "three"
#=> false
irb> "one" == ("one"|"two")
#=> true
irb> "three" == ("one"|"two"|"three")
#=> true
But since this involves some monkey-patching of a fairly lowlevel class, I wouldn't advise relying on it. Other people will hate reading your code.
Ruby supports binary 'or' and other binary operations on values of type Fixnum and Bignum, meaning any integer. Bitwise operations aren't supported on strings or any other type, as far as I know.
As other people have mentioned, you probably want something other than binary operations altogether. However, you can easily get integer representations of characters, so you can compare characters like so:
a = "Cake"
b = "Pie"
puts a[0] | b[0] # Prints "83" - C is 67 and P is 80.
You can get an array of the comparisons easily with some conversions.
a = "Cake"
b = "Pie " # Strings of uneven length is trivial but more cluttered.
a_arr = a.split(//)
b_arr = b.split(//)
c_arr = []
a.each_with_index { |char, i| c.push(a[i].to_i | b[i].to_i) }
# If you *really* want an ASCII string back...
c = c_arr.collect(&:chr).join
You could use a regex:
Like so:
regex = /hi|ho/
s = "hi"
t = "foo"
s =~ regex
#=> 0
t =~ regex
#=> nil