5

Suppose a regex comes from calling code, outside of the current context, and then is passed on to another call implemented outside of the current project:

["1", "2"].grep(/1/)        #=> ["1"]

Is there a simple, Rubyish way to achieve the following behavior when the call is being made?

["1", "2"].grep(/1/.negate) #=> ["2"]

This behavior is similar to switching the =~ operator with the !~ operator. It is possible to use #select or #reject, of course, or to open up or subclass Regexp. But I'm curious whether there is a way already available in Ruby to negate the matches returned by a regular expression in the manner above. Also, I don't care whether false or nil or true or the position of a match are involved in accomplishing this effect.

There is a theoretical question which is relevant but which goes beyond the simple considerations here.


EDIT: I get that iterators are the general way to go in Ruby for filtering a list, but people are overlooking the constraints of the question. Also, I think there is something nicely functional about the way the regex is being inverted. I don't see it as being overwrought or too-clever by half; it's plain-old object-oriented programming and the kind of thing that Ruby excels at doing.

Community
  • 1
  • 1
Eric Walker
  • 7,063
  • 3
  • 35
  • 38
  • 2
    Although not semantically obvious, if you think about it, passing a regular expression to `split` "matches" all the substrings that are non-matches. I'm not familiar enough with Ruby to write up an answer based on this, but I'm sure someone else can. – Andrew Cheong Jul 15 '13 at 17:00
  • Your question is sort of contradictory because it is not Rubyish to try to modify the regex in that way when you can simply alter the iterator. I would say what you are trying to achieve is more Perlish than Rubyish. – sawa Jul 15 '13 at 17:14
  • @sawa - I don't dispute this in principle. But Ruby allows for multiple ways of getting at a problem. In this case, I'd like to continue using a method out of my control (not necessarily `#grep`), which may not accept a block argument, for example. – Eric Walker Jul 15 '13 at 17:16

6 Answers6

7
["1", "2"].reject { |e| /1/ === e }
Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
  • I like the weirdness of this one, but you're using `#reject`, and I'm hoping to continue using `#grep` (or some other method out of my control). – Eric Walker Jul 15 '13 at 17:08
  • @tadman: I'm trying to work within the constraints of passing a regex out of my control to a method out of my control (not necessarily `#grep`), that's all. I'm not trying to be difficult. – Eric Walker Jul 15 '13 at 17:28
  • The argument to `reject` can be specified programmatically, where `/1/` can be a variable, or you can wrap it like: `def grep_reject(regex); reject { |e| regex === e }; end` – tadman Jul 15 '13 at 17:30
  • 6
    If you're chasing a super generic solution, it sounds like you've got an architectural problem with too much information bleeding between separate concerns. You should have a filter object of some kind that given a regexp does what you want. Any object that exposes the correct method would be a suitable replacement. The "Ruby way" is to pass in blocks that define filtering behaviour, so in this case `{ |e| !regex.match(e) }` would encapsulate the logic you're looking for. – tadman Jul 15 '13 at 17:33
  • @tadman - without commenting on the likelihood of leaking concerns in this particular case, Ruby provides another way to manipulate regexes after they've been instantiated, namely, `Regexp.union`. I don't think the hypothetical `#negate` method is all that different in spirit. – Eric Walker Jul 15 '13 at 20:44
3

You can do something like this:

class NegatedRegex < Regexp
  def ===(other)
    !super
  end
end

class Regexp
  def negate
    NegatedRegex.new self
  end
end

There are probably other methods to reimplement, but for grep this is enough:

["1", "2"].grep(/1/.negate) #=> ["2"]
Guilherme Bernal
  • 8,183
  • 25
  • 43
  • Something along these lines is perhaps the only way to do this. I'll accept this answer if it turns out there is no built-in support already in Ruby (I get the impression there isn't). – Eric Walker Jul 15 '13 at 17:20
  • 3
    `String#scan` for example, calls `rb_reg_search` directly on MRI. No way to change this behavior without reimplementing every method that possibly takes a regexp. – Guilherme Bernal Jul 15 '13 at 17:24
  • 5
    Although this is clever, it's probably *too* clever, the kind of clever that years from now you'll want to build a time machine to go back and prevent yourself from ever doing this in the first place. – tadman Jul 15 '13 at 17:28
  • 1
    Maybe it's too clever, but I think it's interesting and worth trying out to see where it goes. – Eric Walker Aug 01 '13 at 06:49
  • I like this approach, but it would be safer to not subclass `Regexp` (because you don't actually negate all of its behavior) and rather have a class that defines `===` only. – akuhn Jul 05 '15 at 19:54
3

You can do them both in one go:

re = /1/
matches, non_matches = ["1", "2", "1", "3"].partition { |el| re =~ el }

p matches       #=> ["1", "1"]
p non_matches   #=> ["2", "3"]
Boris Stitnicky
  • 12,444
  • 5
  • 57
  • 74
steenslag
  • 79,051
  • 16
  • 138
  • 171
2

this could be one way of doing this

["1", "2", 3].select {|i| i !~ /1/ }
 => ["2", 3] 
Sachin Singh
  • 7,107
  • 6
  • 40
  • 80
1
arr=["1","2"]
arr-arr.grep("1")  # ["2"]

:)

Torimus
  • 379
  • 2
  • 10
1

Grep has a dark brother who does everything the same as grep, but vice versa.

["1", "2"].grep(/1/)   #=> ["1"]

["1", "2"].grep_v(/1/) #=> ["2"]
Viktor Ivliiev
  • 1,015
  • 4
  • 14
  • 21