Ruby中类似于grep -v的命令是什么?

9
这是我一直在做的事情:
my_array.reject { |elem| elem =~ /regex/ }.each { ... }

我感觉这有点笨重,但是我没有找到任何内置的函数可以让我将其更改为my_array.grepv /regex/ { ... }

是否有这样的函数?


1
我不这么认为。不过你可以自己创建一个! - rogerdpack
1
grep_v是自Ruby 2.3以来可枚举方法。 - steenslag
10个回答

17

显而易见的问题是为什么在创建grep时没有创建grepv - 我发现Ruby是一种难以置信的混乱语言。 - android.weasel
@android.weasel 我并不是在“为它之前的缺失辩护”,但直到2012年才有人提出了这个问题; 查一下YAGNI原则。至于为什么要等那么久才添加它:当向广泛使用的语言添加新的核心功能时,需要谨慎和讨论。如果API笨拙/不直观,更改后会破坏现有代码的用户采用;另一种选择是添加新的API并弃用旧的API。阅读:混乱。进化在事后看来常常是混乱的。 - Kelvin
@kelvin 直到那时,有人才“请求”它:人们很早就“需要”它了。YAGNI 对于一般编程是很好的,那是设计和发现的混合体,但语言开发人员和主要库设计人员不仅仅是为了自己的项目而做事:为了正确地设计,他们需要考虑其他人的“需求”。特别是鉴于 select/reject 和 if/unless 作为明显的先前已经存在的内容,从一开始就没有实现 grepv 只是证明了这种失败。Ruby 吸收了许多之前的语言已经共同“演进”过的错误。愚我一次,怎么会呢? - android.weasel

7
你知道 Symbol#to_proc 怎样帮助链式操作吗?正则表达式也可以实现相同的功能:
class Regexp
  def to_proc
    Proc.new {|string| string =~ self}
  end
end

["Ruby", "perl", "Perl", "PERL"].reject(&/perl/i)
=> ["Ruby"]

但你可能不应该这样做。Grep 不仅可以使用正则表达式 - 你可以像下面这样使用它

[1,2, "three", 4].grep(Fixnum)

如果你想要用grep -v来处理它,你需要实现Class#to_proc,这听起来不太对。


这个答案捕捉了 Ruby 的极简主义精神——可惜没有一种简单的方法可以使它适用于 grep 的所有行为。 - jdsumsion

6

这个怎么样?

arr = ["abc", "def", "aaa", "def"]
arr - arr.grep(/a/)  #=> ["def", "def"]

我故意包含了一个dup,以确保所有的值都被返回。


4

那么反转正则表达式呢?

["ab", "ac", "bd"].grep(/^[^a]/) # => ["bd"]

3

我不相信有内置的东西可以做到这一点,但是很容易添加:

class Array
  def grepv(regex, &block)
    self.reject { |elem| elem =~ regex }.each(&block)
  end
end

请注意,在调用此函数时,您需要在正则表达式周围使用括号,否则会出现语法错误:
myarray.grepv(/regex/) { ... }

1

你可以做:

my_array.reject{|e| e[/regex/]}.each { ... }

但实际上更简洁和自我记录是很困难的。它可以使用grep(/.../)编写一些负向先行模式,但我认为这样会更难理解整体操作,因为模式本身更难理解。


0

尝试使用{{link1:Array#collect!}}

my_array.collect! do |elem|
  if elem =~ /regex/
    # do stuff
    elem
  end
end

编辑:抱歉,此时您需要在之后调用Array#compact。至少这样可以消除第二个块。但是它会产生更多物理代码。这取决于你做了多少“东西”。


0

你只需要对正则表达式匹配的结果取反即可。

Enumerable.module_eval do
  def grepv regexp
    if block_given?
      self.each do |item|
        yield item if item !~ regexp
      end
    else
      self.find_all do |item|
        item !~ regexp
      end
    end
  end
end

0

非常感谢大家的评论。最终,我是这样做的:

module Enumerable
    def grepv(condition)

        non_matches = []

        self.each do |item|
            unless condition === item or condition === item.to_s
                non_matches.push(item)
                yield item if block_given?
            end
        end

        return non_matches
    end
end

因为我刚开始学习 Ruby,所以不确定这是否是最好的方法。与其他人的解决方案相比,它有一点长,但我喜欢它,因为它类似于 Enumerable 的 grep 选项——它可以处理任何能够处理 === 的东西,就像 grep 一样,如果给出一个块,则返回找到的项目,并且无论如何都返回那些不匹配的项目的数组。

我添加了 or to_s 部分,以便可以使用相同的正则表达式匹配数组中插入的任何整数,尽管我可以想象有时这可能会使事情变得很麻烦。


0

这里再试一次,加入bltxdHsiu的答案,并尽可能保留原始grep的精神(即使有点啰嗦):

module Enumerable
  def grepv(condition)
    if block_given?
      each do |item|
        yield item if not condition === item
      end
    else
      inject([]) do |memo, item|
        memo << item if not condition === item
        memo
      end
    end
  end
end

如果您提供了一个块,那么一切都像您期望的那样是惰性的。如果您没有提供一个块,就会有一些重复的代码。我真的希望Andrew Grimm的答案适用于一般情况。
>> (%w(1 2 3) + [4]).cycle(3).grepv(Fixnum)
=> ["1", "2", "3", "1", "2", "3", "1", "2", "3"]

>> (%w(1 2 3) + [4]).cycle(3).grepv(/[12]/)
=> ["3", 4, "3", 4, "3", 4]

在这两种情况下,您都不需要像使用数组减法时的最坏情况一样支付高达O(n^2)的项目比较费用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接