Ruby——如何优雅地链接多个OR语句?(检查数组越界)

7

我正在做一个编码谜题,其中你是一个矿工在一个数组中挖掘,但不能超出边界。我有如下代码:

if x > minemap.length-1 or x < 0 or y > minemap[0].length-1 or y < 0
  return false
end

有没有更好、更干净、每行只有一个条件的方法来连接大量的OR语句?


[x > minemap.length-1, x < 0, y > minemap[0].length-1, y < 0].all? 这可能不是最好的或最惯用的方式,但值得一试。 - Santhosh
@Santhosh,这是一个不错的方法,但它确实创建了一个临时对象(数组),为其分配内存,然后等待垃圾回收器对其进行收集…这些可能并不重要,但在性能受到影响的情况下(即,在循环内部)保持它们在脑海中是有好处的。 - Myst
1
@Myst 虽然你一直强调这一点很好,但你也不应该忘记唐纳德·克努斯(Donald Knuth)的名言:“我们应该忘记小的效率问题,大约97%的时间:过早优化是万恶之源。” - Michael Kohl
@MichaelKohl - 我认为这样的if语句经常出现在循环中(即检查板的状态)。这可能不像人们想象的那么小的优化。此外,许多开发人员声称Ruby很慢,我认为我们意识到有时我们的“惯用”方法实际上是性能瓶颈非常重要。人们没有像他们应该的那样经常考虑临时对象。 - Myst
当然可以,但正如你自己所说,你只是在假设。此外,Ruby 2.1 中引入并在 2.2 中进行了改进的分代 GC 实际上是基于大多数对象都是短寿命的假设构建的。因此,“次要”GC运行非常快,因为它仅在“年轻”的对象(即尚未经历三次GC运行的对象)上运行。因此,虽然我不否认你的观点有其优点,但我仍然更喜欢首先编写惯用代码。 - Michael Kohl
显示剩余2条评论
6个回答

6

首先,在 Ruby 中,条件语句中使用 orand 而不是 ||&& 是不符合惯用法的,因为它们具有不同的优先级,并且可能并不总是做你想要的事情(风格指南参考)。

至于实际问题,像这样的写法更符合 Ruby 的惯用法:

(0...minemap.length).cover?(x) && (0...minemap[0].length).cover?(y)

这段代码使用Range#cover?来检查xy是否在正确的范围内,并且只有在这些条件为真时才返回false


1
需要使用unless代码块吗?条件返回布尔值。 - Santhosh
应该写成 return false - Stefan
1
@mudasobwa 不,你误解了我的意思:-) OP的代码包含一个 return false - 这可能是一个大方法内的检查。 - Stefan
为避免任何混淆,我将我的代码简化为条件语句,OP可以按照自己的喜好使用它。 - Michael Kohl
@Mirror318,我想你是指的✅,而不是简单的✓。 - Cary Swoveland
显示剩余4条评论

2
您可以使用或等于赋值运算符||=将每个条件放在新行上:
is_true = false # initial condition
is_true ||= x > minemap.length-1
is_true ||= x < 0
is_true ||= y > minemap[0].length-1
is_true ||= y < 0

is_true # will be true or false

除了其他答案之外,您不需要显式返回true或false的值,因为这是通过表达式的求值隐式完成的。
# will return true or false
def out_of_bounds?
  x > minemap.length-1 || x < 0 || y > minemap[0].length-1 || y < 0
end

你也可以创建一个对应的方法,只是取反作用:
def in_bounds?
  !out_of_bounds?
end

肯定可以更容易地看到/理解每个条件... 我总是觉得这样看起来更干净... 整个代码块(以及各个部分)都可以提取到单独的方法中。 - br3nt

2
虽然我支持@MichaelKohl的答案,但是它确实会创建临时对象,因为每个if语句都会为两个范围对象分配内存,并调用它们的#cover实例方法。这些对象存在于内存中等待垃圾回收器发挥其魔力,它们的存在和使用浪费资源和CPU周期。这可能不是一个问题,但它可能会降低性能,在循环中使用或者经常调用if语句时可能会成为一个问题。另一方面,如果您知道数组永远不包含nil(即,如果数组包含有效的true/false或其他数字值),则可以简单地使用:
unless x < 0 || y < 0 || minemap[x].nil? || minemap[x][y].nil?
   # ...
end

如果您知道x和y始终为0或正数,则可以使用数学计算...

if (minemap.length - x) > 0 && (minemap[x].length - y) > 0
   # ...
end

或者使用带有附加条件的数学计算...
if x >=0 && y>= 0 && (minemap.length - x) > 0 && (minemap[x].length - y) > 0
   # ...
end

祝你好运。


它们不是相同的范围。minemap 似乎是一个二维数组,因此 y 检查使用了 minemap[0].length] - Michael Kohl
然后你也可以在第一句中删除(identical)这个词。 :) - Michael Kohl
1
问题在于 minemap[-1] 进行了反向索引,而不是 nil - Mirror318
@Mirror318 - 你发现得好!我已经把它移除了。 - Myst
@Mirror318 - 反向索引可以通过 x < 0 检查轻松避免...但我认为 x < 0 可能是一个非法值,可以更早地检查并用于引发异常。该值应仅在出现错误或对应用程序进行攻击时出现。 - Myst

2

我经常会利用any?all?,使代码更具惯用性:

class Something

  def the_question?
    [ thing_1, thing_2, thing_3 ].any?
  end

protected

  def thing_1
    # …
  end

  def thing_2
    # …
  end

  def thing_3
    # …
  end
end

在您的情况下,问题出在您的个别语句上:它们在本地变量上调用了[]方法,然后在其上调用了一个方法。换句话说,这个上下文/方法可能正在处理应该通过接口(而不是实现细节)与之交互的对象的细节。

更适合于更复杂的条件语句(但对于我正在做的事情来说有些过度了)。 - Mirror318

2
假设地图大小固定,我会在创建地图之前将其宽度和高度存储在变量或常量中:
假设地图大小固定,我会在创建地图之前将其宽度和高度存储在变量或常量中:
HEIGHT = 5
WIDTH = 10
@minemap = Array.new(WIDTH) { Array.new(HEIGHT) }

然后,我会使用between?来检查xy是否在范围内:

return unless x.between?(0, WIDTH-1) && y.between?(0, HEIGHT-1)
# do something with minemap[x][y]

顺便说一下,使用带有坐标键的哈希表通常比使用嵌套数组更容易,这样你就可以使用:minemap[x, y]而不是minemap[x][y]


1

针对N个区间和N个相应点的最通用解决方案:

ranges = [0...minemap.length, 0...minemap[0].length]
values = [x, y]
ranges.zip(values).map { |e| e.reduce(&:cover?) }.all?
#⇒ true

您也可以明确查询第一个不适合的内容:

ranges.zip(values).detect { |e| !e.reduce(&:cover?) }

所有都不适合:

ranges.zip(values).reject { |e| e.reduce(&:cover?) }

etc.


它必须是常量吗? - Sergio Tulentsev
@SergioTulentsev 让我查一下文档...好的,看起来不是,那些可能是局部变量甚至是返回数组的方法。 - Aleksei Matiushkin

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