painterofthewind
painterofthewind

Reputation: 123

Ruby Regular expressions (regex): character appear only once at most

Suppose I want to make sure a string x equals any combination of abcd (each character appearing one or zero times-->each character should not repeat, but the combination may appear in any order)

  1. valid ex: bc .. abcd ... bcad ... b... d .. dc
  2. invalid ex. abcdd, cc, bbbb, abcde (ofcourse)

my effort: I tried various techniques: the closest I came was x =~ ^(((a)?(b)?(c)?(d)?))$

but this wont work if I do not type them in the same order as i have written:

  1. works for: ab, acd, abcd, a, d, c
  2. wont work for: bcda, cb, da (anything that is not in the above order)

you can test your solutions here : http://rubular.com/r/wCpD355bub

PS: the characters may not be in alphabetical order, it could be u c e t

Upvotes: 4

Views: 2107

Answers (4)

pguardiario
pguardiario

Reputation: 54984

You can reverse it (match the condition that would make it fail)

re = /^ # start of line
(?=.*([a-d]).*\1) # match if a letter appears more than once
| # or
(?=.*[^a-d]) # match if a non abcd char appears
/x
puts 'fail' if %w{bc abcd bcad b d dc}.any?{|s| s =~ re}
puts 'fail' unless %w{abcdd cc bbbb abcde}.all?{|s| s =~ re} 

Upvotes: 2

Catnapper
Catnapper

Reputation: 1905

I don't think regexes are well suited to this problem, so here is another non-regex solution. It's recursive:

def match_chars_no_more_than_once(characters, string)
  return true if string.empty?
  if characters.index(string[0]) 
    match_chars_no_more_than_once(characters.sub(string[0],''), string[1..-1])
  else
    false
  end
end

%w{bc bdac hello acbbd cdda}.each do |string|
  p [string, match_chars_no_more_than_once('abcd', string)]
end

Output:

["bc", true]
["bdac", true]
["hello", false]
["acbbd", false]
["cdda", false]

Upvotes: 1

greedybuddha
greedybuddha

Reputation: 7507

This is really not what regular expressions are meant to do, but if you really really want to.

Here is a regex that satisfies the conditions.

^([a-d])(?!(\1))([a-d])?(?!(\1|\3))([a-d])?(?!(\1|\3|\5))([a-d])?(?!(\1|\3|\5|\7))$

basically it goes through each character, making the group, then makes sure that that group isn't matched. Then checks the next character, and makes sure that group and the previous groups don't match.

Upvotes: 4

Chris Heald
Chris Heald

Reputation: 62648

If you can use things besides regexes, you can try:

str.chars.uniq.length == str.length && str.match(/^[a-d]+$/)

The general idea here is that you just strip any duplicated characters from the string, and if the length of the uniq array is not equal to the length of the source string, you have a duplicated character in the string. The regex then enforces the character set.

This can probably be improved, but it's pretty straightforward. It does create a couple of extra arrays, so you might want a different approach if this needs to be used in a performance-critical location.

If you want to stick to regexes, you could use:

str.match(/^[a-d]+$/) && !str.match(/([a-d]).*\1/)

That'll basically check that the string only contains the allowed characters, and that those characters are never repeated.

Upvotes: 5

Related Questions