【问题标题】:Ruby: Find index of next match in array, or find with offsetRuby:在数组中查找下一个匹配项的索引,或使用偏移量查找
【发布时间】:2015-11-30 12:08:41
【问题描述】:

我想在Array#find_index { |item| block } 首次匹配后查找更多匹配项。如何搜索第二个匹配、第三个匹配等的索引?

换句话说,对于Array#find_index,我希望Regexp#match(str, pos)pos 参数等效。然后我可以维护一个当前位置索引来继续搜索。

我不能使用Enumerable#find_all,因为我可能会在调用之间修改数组(在这种情况下,我还将调整我的当前位置索引以反映修改)。我不想复制数组的一部分,因为这会增加我算法的computational complexity。我想在不复制数组的情况下这样做:

new_pos = pos + array[pos..-1].find_index do |elem|
    elem.matches_condition?
end

以下是不同的问题。他们只询问数组中的第一个匹配项,加上一个:

下面的问题更接近,但仍然对我没有帮助,因为我需要在继续下一个之前处理第一个匹配(并且这种方式也与修改冲突):

【问题讨论】:

    标签: arrays ruby


    【解决方案1】:

    一个更简单的方法是:

    new_pos = pos
    while new_pos < array.size and not array[new_pos].matches_condition?
        new_pos += 1
    end
    new_pos = nil if new_pos == array.size
    

    事实上,我认为这可能比我的其他答案更好,因为它更难出错,并且未来不会从周围的代码中引入阴影问题。但是,它仍然很笨拙。

    如果条件更复杂,那么你最终需要做这样的事情:

    new_pos = pos
    # this check is only necessary if pos may be == array.size
    if new_pos < array.size
        prepare_for_condition
    end
    while new_pos < array.size and not array[new_pos].matches_condition?
        new_pos += 1
        if new_pos < array.size
            prepare_for_condition
        end
    end
    new_pos = nil if new_pos == array.size
    

    或者,上帝保佑,begin ... end while 循环(尽管你会遇到new_pos 的初始值的麻烦):

    new_pos = pos - 1
    begin
        new_pos += 1
        if new_pos < array.size
            prepare_for_condition
        end
    end while new_pos < array.size and not array[new_pos].matches_condition?
    new_pos = nil if new_pos == array.size
    

    这可能看起来很可怕。然而,假设prepare_for_condition 是不断被微调的东西。这些调整最终会被重构;然而,到那时,重构代码的输出最终也会以不属于旧重构代码的小方式进行调整,但似乎还不能证明他们自己的重构是合理的——等等。有时,有人会忘记改变这两个地方。这似乎是病态的;然而,在编程中,众所周知,病态的情况往往发生得太频繁。

    【讨论】:

      【解决方案2】:

      这是可以做到这一点的一种方法。我们可以在Array 类中定义一个新方法,它允许我们找到与给定条件匹配的索引。条件可以指定为返回布尔值的块。

      新方法返回一个Enumerator,这样我们就可以从许多Enumerator 方法中受益,例如nextto_a 等。

      ary = [1,2,3,4,5,6]
      
      class Array
        def find_index_r(&block)
          Enumerator.new do |yielder| 
            self.each_with_index{|i, j| yielder.yield j if block.call(i)}
          end
        end
      end
      
      e = ary.find_index_r { |r| r % 2 == 0 }
      
      p e.to_a  #=> [1, 3, 5]
      p e.next  
      #=> 1
      p e.next  
      #=> 3
      ary[2]=10 
      p ary
      #=> [1, 2, 10, 4, 5, 6]
      p e.next  
      #=> 5
      e.rewind
      p e.next  
      #=> 1
      p e.next  
      #=> 2
      

      注意:我在Array 类中添加了一个新方法用于演示目的。解决方案可以很容易地适应工作而无需猴子补丁

      【讨论】:

      • 我喜欢你的方法,但你做得太多了。 Enumerator.new 块中的所有内容都可以替换为 each_with_index {|item, idx| yielder.yield(idx) if block.call(item) }。看一看:ideone.com/7jOyqQ
      • 谢谢@Jordan - 我已经根据你的建议更新了我的答案。
      • 但是,如果我在调用 e.next 之间修改数组,这仍然可以保证工作吗?
      • @martinjs 不确定您在谈论哪种修改。如果更新一个已经遍历过的元素,直到执行Enumerator#rewind才能看到效果
      【解决方案3】:

      当然,一种方法是:

      new_pos = pos + (pos...array.size).find_index do |index|
          elem = array[index]
          elem.matches_condition?
      end
      

      但是,这很笨拙且容易出错。例如,您可能忘记添加pos。此外,您必须确保elem 没有遮蔽某些东西。这两种情况都可能导致难以追踪的错误。

      我很难相信 Array#find_indexArray#index 的索引参数仍然没有进入语言。但是,我注意到 Regexp#match(str,pos) 直到 1.9 版才出现,这同样令人惊讶。

      【讨论】:

      • 你为什么发布一个“笨拙且容易出错”的答案
      • 因为在某些情况下,这将是您唯一(或最佳)的选择,除非猴子修补。我想说服 Ruby 团队,他们应该向标准库方法#find_index(可能还有其他方法)添加一个位置(或偏移量)参数。
      【解决方案4】:

      假设

      arr = [9,1,4,1,9,36,25]
      findees = [1,6,3,6,3,7]
      proc = ->(n) { n**2 }
      

      对于findees 中的每个元素n,我们希望arr 中第一个不匹配元素m 的索引为which proc[n] == m。例如,如果n=3,那么proc[3] #==&gt; 9,那么arr 中的第一个匹配索引将是0。对于findees 中的下一个n=3arr 中的第一个不匹配匹配位于索引4

      我们可以这样做:

      arr = [9,1,4,1,9,36,25]
      findees = [1,6,3,6,3,7]
      proc = ->(n) { n**2 }
      
      h = arr.each_with_index.with_object(Hash.new { |h,k| h[k] = [] }) { |(n,i),h| h[n] << i }
        #=> {9=>[0, 4], 1=>[1, 3], 4=>[2], 36=>[5], 25=>[6]}
      findees.each_with_object([]) { |n,a| v=h[proc[n]]; a << v.shift if v }
        #=> [1, 5, 0, nil, 4, nil]
      

      我们可以将其概括为一个方便的Array 方法,如下所示:

      class Array
        def find_indices(*args)
          h = each_with_index.with_object(Hash.new {|h,k| h[k] = []}) { |(n,i),h| h[n] << i }
          args.each_with_object([]) { |n,a| v=h[yield n]; a << v.shift if v }
        end
      end
      
      arr.find_indices(*findees) { |n| n**2 }
        #=> [1, 5, 0, nil, 4, nil]
      
      arr = [3,1,2,1,3,6,5]
      findees = [1,6,3,6,3,7]
      arr.find_indices(*findees, &:itself)
        #=> [1, 5, 0, nil, 4, nil]
      

      【讨论】:

        【解决方案5】:

        我的方法与其他方法没有太大区别,但可能打包更清洁器在语法上类似于 Array#find_index 。这是紧凑的形式。

        def find_next_index(a,prior=nil)
            (((prior||-1)+1)...a.length).find{|i| yield a[i]}
        end
        

        这是一个简单的测试用例。

        test_arr = %w(aa ab ac ad)
        puts find_next_index(test_arr){|v| v.include?('a')}
        puts find_next_index(test_arr,1){|v| v.include?('a')}
        puts find_next_index(test_arr,3){|v| v.include?('a')}
        
        # evaluates to:
        #     0
        #     2
        #     nil
        

        当然,只要稍微重写一下,您就可以将其修补到 Array 类中

        【讨论】:

          猜你喜欢
          • 2018-12-29
          • 2016-02-14
          • 1970-01-01
          • 2018-11-01
          • 2015-08-02
          • 2017-07-06
          • 2021-01-20
          • 2011-06-17
          • 2012-04-13
          相关资源
          最近更新 更多