Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return true only if one of a set of strings matches?

Tags:

regex

ruby

I want to return true if the user enters only one of a set of possible matches. Similar to an XOR operator, but only one string out of the entire group may exist in the input. Here is my code:

if input.match?(/str|con|dex|wis|int|cha/)

The following inputs should return true:

+2 int
-3con
str
con
wisdom
dexterity

The following inputs should return false:

+1 int +2 cha
-4dex+3con-1cha
int cha
str dex
con wis cha
strength intelligence
strdex
like image 802
Richard Avatar asked Dec 07 '22 11:12

Richard


2 Answers

I'd probably go with String#scan and a simple regex so that you can understand what you've done later:

if input.scan(/str|dex|con|int|wis|cha/).length == 1
  # Found exactly one
else
  # Didn't find it or found too many
end

That also makes it easier to distinguish between the various ways it can fail.

Presumably your strings will be relatively small so scanning the string for all the matches won't have any noticeable overhead.

like image 75
mu is too short Avatar answered Apr 13 '23 00:04

mu is too short


The following are three ways to answer the question without creating an intermediate array. All employ the regular expression:

R = /str|con|dex|wis|int|cha/

and return the following:

one_match? "It wasn't a con, really"              #=> true 
one_match? "That sounds to me like a wild guess." #=> falsy (nil or false) 
one_match? "Both int and dex are present."        #=> falsy (nil or false) 
one_match? "Three is an integer."                 #=> true 
one_match? "Both int and indexes are present."    #=> falsy (nil or false) 

#1 Do first and last match begin at the same index?

def one_match?(s)
  (idx = s.index(R)) && idx == s.rindex(R)
end

See String#index and String#rindex.

#2 Use the form of String#index that takes an argument equal to the index at which the search is to begin.

def one_match?(s)
  s.index(R) && s.index(R, Regexp.last_match.end(0)).nil?
end

See Regexp::last_match and MatchData#end. Regexp.last_match can be replaced by $~.

#3 Use the form of String#gsub that takes one argument and no block to create an enumerator that generates matches

def one_match?(s)
  s.gsub(/str|con|dex|wis|int|cha/).count { true } == 1
end

See Enumerable#count.

Alternatively,

s.gsub(/str|con|dex|wis|int|cha/).to_a.size == 1

though this has the disadvantage of creating a temporary array.

To match whole words only

In the penultimate example 'int' matches 'int' in 'integer' and in the last match 'dex' matches 'dex' in 'indexes'. To enforce full-word matches the regular expression can be changed to:

/\b(?:str|con|dex|wis|int|cha)\b/
like image 41
Cary Swoveland Avatar answered Apr 13 '23 01:04

Cary Swoveland