Ruby的bsearch find-minimum和find-any行为是什么?

时间:2019-08-29 08:24:12

标签: ruby bsearch

我正在阅读the docs for Ruby's bsearch

似乎当该块返回true或false时,然后bsearch使用“ find-minimum”模式进行工作。还有最大查找模式吗?

对于以下第3至5种情况,我不太了解Ruby的bsearch最小查找行为:

[10, 20, 50, 80, 110].bsearch{|a| a >= 25}
=> 50

[10, 20, 50, 80, 110].bsearch{|a| a >= 20}
=> 20

[10, 20, 50, 80, 110].bsearch{|a| a == 20}
=> nil

[10, 20, 50, 80, 110].bsearch{|a| a < 50}
=> nil

[10, 20, 50, 80, 110].bsearch{|a| a <= 50}
=> 10
  • 对于第三种情况,为什么找不到20
  • 对于第四种情况,为什么也找不到20? (第一个小于50)。
  • 对于第五种情况,为什么找到10而不是20

此外,当块不返回true或false而是返回数字时,bsearch似乎将使用find-any模式。但是我真的无法理解它在文档中的作用:

ary = [0, 4, 7, 10, 12]
# try to find v such that 4 <= v < 8
ary.bsearch {|x| 1 - x / 4 } #=> 4 or 7

例如1 - x / 4是什么,它在做什么?

3 个答案:

答案 0 :(得分:6)

  
      
  • 对于第三种情况,为什么找不到20
  •   
  • 对于第四种情况,为什么也找不到20? (第一个小于50)。
  •   
  • 对于第五种情况,为什么找到10而不是20
  •   

您的示例3、4和5违反了该方法的前提条件,因此该方法可以执行所需的任何操作,包括返回nil,返回10或格式化硬盘。 (尽管最后一个可能性很小。)

The documentation指出:

  

该块必须返回true或false,并且必须有一个索引i(0 <= i <= ary.size),以便:

     
      
  • 对于索引小于i的任何元素,该块返回false,并且
  •   
  • 对于索引大于或等于i的任何元素,该块返回true。
  •   

您的代码块违反了该条件,因此方法不可能返回有意义的结果。

对于第三种情况,让我们实际完成Array#bsearch的示例运行:

[10, 20, 50, 80, 110].bsearch{|a| a == 20 }

在第一次迭代中,算法将选择数组的中间(50)并针对您的块进行测试:

50 == 20 #=> false

false表示我们正在搜索的元素位于当前元素的 right 处。如此,我们要搜索的元素必须在子数组[80, 110]中。因此,我们递归:

[80, 110].bsearch{|a| a == 20 }

同样,我们选择“中间”元素(110)并针对您的代码块进行测试:

110 == 20 #=> false

由于该块的返回值为false,因此我们知道该元素必须在当前元素的右边,,但是右边没有元素,因此我们知道我们要搜索的元素不存在。

现在是您的第四种情况:

[10, 20, 50, 80, 110].bsearch{|a| a < 50 }

在第一次迭代中,算法将选择数组的中间(50)并针对您的块进行测试:

50 < 50 #=> false

false表示我们正在搜索的元素位于当前元素的 right 处。如此,我们要搜索的元素必须在子数组[80, 110]中。因此,我们递归:

[80, 110].bsearch{|a| a < 50 }

同样,我们选择“中间”元素(110)并针对您的代码块进行测试:

110 < 50 #=> false

由于该块的返回值为false,因此我们知道该元素必须在当前元素的右边,,但是右边没有元素,因此我们知道我们要搜索的元素不存在。

第五种情况:

[10, 20, 50, 80, 110].bsearch{|a| a <= 50 }

在第一次迭代中,算法将选择数组的中间(50)并针对您的块进行测试:

50 <= 50 #=> true

值为true表示我们正在搜索的元素位于当前元素的 left 处。如此,我们要搜索的元素必须在子数组[10, 20]中。因此,我们递归:

[10, 20].bsearch{|a| a <= 50 }

同样,我们选择“中间”元素(20)并针对您的代码块进行测试:

20 <= 50 #=> true

因此,该元素必须仍位于左侧子数组中:

[10].bsearch{|a| a <= 50 }

将测试

10 <= 50 #=> true

由于该块的返回值为true,因此我们知道该元素必须位于当前元素或此元素的左侧,但是没有元素可以是正确的,因此,我们知道我们要搜索的元素就是这个元素。

注意:我假定Array#bsearch总是会选择一个尽可能靠近中间的元素,并且我假设对于偶数个元素,它总是会选择中间的一个。但是您知道他们对假设的看法:它使您和我陷入困境。实际上,文档明确指出:

  

每次迭代中实际获取哪个值是不确定的。

因此,根据哪些实际在每次迭代中实际拾取的元素,结果实际上可能有所不同。而且这也不足为奇,因为该块违反了该方法的前提,所以没有办法知道将要发生什么。

  

例如1 - x / 4是什么,它在做什么?

这只是您的搜索条件。 0x == 4x < 4是正,x > 4是负。这正是 find-any 模式所需要的:正数告诉算法它必须向左看,负数告诉算法必须向右看,零表示“您找到了范围”:

  

该块必须始终返回一个数字,并且必须有两个索引i和j(0 <= i <= j <= ary.size),以便:

     
      
  • 如果0 <= k 该块为ary返回一个正数   
  • 如果i <= k   
  • 如果j <= k   

答案 1 :(得分:2)

在您的第三个示例中,您没有满足“查找最小”模式的要求之一,即该块返回true的任何值大于或等于您的搜索值。

在第三种情况下,您已经切换了值,因此对于小值返回true,对于大值返回false,这再次导致未定义的行为。

类似地,在第一种情况下,您已切换了相等性检查的顺序。该方法的要求是,如果搜索值等于或大于搜索值,则该块返回true;如果小于或等于false,则该块返回。

通常,如果您可以满足bsearch算法的所有要求,即对搜索到的数组进行排序并指定合适的块,则bsearch算法非常有效。在这种情况下,使用基本的find方法可能会更有效率。

但是,如果您缺少其任何要求,您将获得不确定的行为并有效地获得随机结果。在这种情况下,仅使用Enumerable#find可能也会有更好的结果,该方法同样适用于数组,哈希,...

答案 2 :(得分:1)

尽管有文档,Array#bsearch 没有

  

从此数组中找到一个满足给定条件的值

该方法的确切作用是要求您构造这样的数组ary和这样的块blk

查找最小模式

...代码ary.map(&blk)应该返回一个类似的数组

[false, false, ..., false, false, true, true, ..., true, true]
#                                 ^^^^

然后代码ary.bsearch(&blk)将返回给您最左侧数组元素,该元素将为true返回bkl.call(element)

查找任何模式

...代码ary.map(&blk)应该返回一个类似的数组

[positive, ..., positive, 0, ..., 0, negative, ..., negative]
#                         ^^^^^^^^^

然后代码ary.bsearch(&blk)将返回给您 first-met-by-bisect-jump 数组元素,该数组元素为0返回bkl.call(element)


情况3-5:

  
      
  1. [10, 20, 50, 80, 110].bsearch{|a| a == 20}
  2.   

毫无意义

[10, 20, 50, 80, 110].map{|a| a == 20}
=> [false, true, false, false, false]
  
      
  1. [10, 20, 50, 80, 110].bsearch{|a| a < 50}
  2.   
可以更改

以满足最小查找模式的要求:

[10, 20, 50, 80, 110].reverse.map{|a| a < 50}
=> [false, false, false, true, true]
[10, 20, 50, 80, 110].reverse.bsearch{|a| a < 50}
=> 20
  
      
  1. [10, 20, 50, 80, 110].bsearch{|a| a <= 50}
  2.   

也可以使用reverse

[10, 20, 50, 80, 110].reverse.bsearch{|a| a <= 50}
=> 50