【问题标题】:Efficient way to select items < value from sorted array从排序数组中选择项目<值的有效方法
【发布时间】:2015-10-28 02:19:31
【问题描述】:

我有一个带有排序数字的数组,例如。

arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]

有没有办法在不遍历整个数组的情况下选择 = 5 的项目时立即中断。

arr.select { |p| p < 5 }

我尝试了 break、next 和 return 的变体,但没有成功。

arr.select { |p| p < 5; break  if p >= 5 } # = nil
arr.select { |p| p < 5; next   if p >= 5 } # = []
arr.select { |p| p < 5; return if p >= 5 } # LocalJumpError

有什么好的方法可以做到这一点?

【问题讨论】:

    标签: arrays ruby sorting select


    【解决方案1】:

    我认为您可以为此使用take_while 方法。

    【讨论】:

    • 选择是一种不好的方法。如果你在它完成之前打破它,它不会返回你想要的结果。同意 baron816 的观点。 take_while 完全符合您的要求。
    • 不加arr.take_while { |e| e &lt; 5 }吗?
    • 谢谢,take_while 是我一直在寻找的,而且效果很好。
    • @BasselSamman [1,2,3,4].select { |e| e.odd?.tap { break if e &gt; 2 } } #⇒ [1].
    • 哈哈哈@mudasobwa 真的。不错的技巧,但是使用.odd?最终给你错误的答案,因为它会跳过偶数。不过很酷的主意,你可以做 [1,2,3,4].select { |e| e.tap { break if e > 2 } } 甚至 [1,2,3,4].select { |e| true.tap { break if e > 2 } } 只要对象不评估为 nil 或 false。在一天结束时,虽然仍然不如 take_while 干净。
    【解决方案2】:

    这是另一种方法:

      > arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
      #=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
      > index = arr.index(5)
      #=> 4
      > arr[0..index]
      #=> [1, 2, 3, 4, 5]
    

    【讨论】:

    • 在这个例子中可以工作,在我的真实数据中该值可能不存在 - 所以按索引查找是行不通的。
    【解决方案3】:

    首先,为什么您的解决方案不起作用? select 接受一个块,它会丢弃该块返回假值的所有元素(nilfalse),并保留所有块返回真值的元素。

    arr.select { |p| p < 5; break  if p >= 5 } # = nil
    

    在这里,您调用p &lt; 5,但您只是忽略了它的返回值。在 Ruby 中,块的返回值是块内最后一个表达式的值。此处评估的最后一个表达式是break if p &gt;= 5。由于没有else的情况,它会为小于5的所有元素返回nil(即false,即表示“不保留此元素”),然后它将返回break(表示“中止并返回nil")。

    IOW:您是在告诉 select 丢弃所有小于 5 的元素,然后中止。

    arr.select { |p| p < 5; next   if p >= 5 } # = []
    

    在这种情况下,您再次为小于 5 的所有元素返回 nil,对于所有大于或等于 5 的元素,您将参数返回给 next……但没有参数,所以,您还将为所有大于 5 的元素返回 nil

    IOW:你告诉select 扔掉所有小于 5 的元素,然后你告诉它扔掉所有大于或等于 5 的元素,或者,换句话说,你告诉它扔掉所有元素… 并且你仍在迭代整个数组。

    无论如何,这没有多大意义。 next 隐含在块中,就像 return 隐含在方法中一样。 (事实上​​,next 是块,return 是方法),所以你所做的只是添加一个多余的关键字。

    arr.select { |p| p < 5; return if p >= 5 } # LocalJumpError
    

    再一次,和以前一样……你告诉select 扔掉所有小于5 的元素,然后你是return 来自封闭方法。但是没有封闭方法,所以Ruby raises 是LocalJumpError

    您可以通过这种方式“修复”您的第二个解决方案:

    arr.select { |p| if p < 5 then true else next end }
    

    即对于小于 5 的元素,您实际上需要返回 true(或一些真实的东西)。但是,就像我说的那样,next 无论如何都是隐含的,所以您可以将其省略,这意味着这相当于

    arr.select { |p| if p < 5 then true else nil end }
    

    其中,由于select 对待falsenil 相同,因此等价于

    arr.select { |p| if p < 5 then true else false end }
    

    当然和

    一样
    arr.select { |p| p < 5 }
    

    其中,由于&lt;&gt; 的对称性,所以与

    arr.select(&5.method(:>))
    

    无论如何,在排序数组中搜索的正确解决方案是使用二分搜索,它根本不需要迭代数组,并且只需要 O(log(n) ) 比较:

    arr.bsearch_index(&5.method(:<=))
    

    这将搜索“小于 5 的最后一个数字的下一个最大索引右侧的元素的索引”。然后你可以使用Array#[] 来提取你想要的元素切片

    arr[0...arr.bsearch_index(&5.method(:<=))]
    

    注意Array#bsearch_index就是new in 2.3.0,相信在那之前,你可以使用Range#bsearchinstead

    arr[0...(0...arr.size).bsearch {|i| arr[i] >= 5 }]
    

    【讨论】:

      【解决方案4】:

      您也可以将Enumerable#lazyEnumerable#slice_while 一起使用:

      arr.lazy.slice_before { |i| i >= 5 }.first
        #=> [1, 2, 3, 4] 
      

      通过使slice_before 变得懒惰,first 在获得第一个切片后终止切片。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-08-03
        相关资源
        最近更新 更多