查找符合给定条件的元素的索引


70

给定一个数组,如何找到与给定条件匹配的元素的所有索引?

例如,如果我有:

arr = ['x', 'o', 'x', '.', '.', 'o', 'x']

要找到项目所在的所有索引x,我可以这样做:

arr.each_with_index.map { |a, i| a == 'x' ? i : nil }.compact   # => [0, 2, 6]

要么

(0..arr.size-1).select { |i| arr[i] == 'x' }   # => [0, 2, 6]

有没有更好的方法来实现这一目标?


您是在寻找逻辑还是语法,可以在逻辑上帮助您,但在语法上却不是:)不太熟悉ruby ...您可以使用regexp
bonCodigo 2012年

我猜逻辑和语法:)不确定正则表达式,因为项目不是必需的字符串。
Misha Moroshko

这是我认为Python的列表理解确实能更好地阅读的少数情况之一:[i for i,a in enumerate(arr) if a == 'x']
马克·里德

令我感到Enumerable#find_indices(elem)困扰的是,Ruby Stdlib中没有a 。以某种方式这将提出合理的功能请求。
费利克斯

Answers:


101

Ruby 1.9:

arr = ['x', 'o', 'x', '.', '.', 'o', 'x']
p arr.each_index.select{|i| arr[i] == 'x'} # =>[0, 2, 6]


1
谢谢你 我之前才发现arr.enum_for(:each_with_index).collect {|item,index| item=='x' ? index : nil }.delete_if {|i| i.nil? }
2014

3
@ 6ftDan delete_if {|i| i.nil? }== compact
steenslag 2014年

出于好奇,p在这种情况下会做什么?
stevec

@ user5783745p以“对开发人员友好”的方式将表达式的结果打印到屏幕上。在这种情况下:[0, 2, 6]
steenslag

1
@ user5783745my_var = p arr.each_index.select{|i| arr[i] == 'x'}可以使用或不使用p
steenslag,

27

另一种方式:

arr.size.times.select {|i| arr[i] == 'x'} # => [0, 2, 6]

编辑:

不确定是否甚至需要这样做,但是确实如此。

基准测试:

arr = 10000000.times.map{rand(1000)};

Benchmark.measure{arr.each_with_index.map { |a, i| a == 50 ? i : nil }.compact}
2.090000   0.120000   2.210000 (  2.205431)

Benchmark.measure{(0..arr.size-1).select { |i| arr[i] == 50 }}
1.600000   0.000000   1.600000 (  1.604543)

Benchmark.measure{arr.map.with_index {|a, i| a == 50 ? i : nil}.compact}
1.810000   0.020000   1.830000 (  1.829151)

Benchmark.measure{arr.each_index.select{|i| arr[i] == 50}}
1.590000   0.000000   1.590000 (  1.584074)

Benchmark.measure{arr.size.times.select {|i| arr[i] == 50}}
1.570000   0.000000   1.570000 (  1.574474)

支持最合理的实施。其他任何事情都更加复杂。
akostadinov

我会说被接受的答案更简单;arr.size.times == arr.each_index。只需一个方法调用即可,而不是两个。但是根据此处添加的基准,显然慢一些吗?
马克·里德


9

这种方法有点长,但速度却快一倍

class Array
  def find_each_index find
    found, index, q = -1, -1, []
    while found
      found = self[index+1..-1].index(find)
      if found
        index = index + found + 1
        q << index
      end
    end
    q
  end
end

arr = ['x', 'o', 'x', '.', '.', 'o', 'x']
p arr.find_each_index 'x'
# [0, 2, 6]

在这里,AGS的基准与该解决方案紧密相关

arr = 10000000.times.map{rand(1000)};

puts Benchmark.measure{arr.each_with_index.map { |a, i| a == 50 ? i : nil }.compact}
puts Benchmark.measure{(0..arr.size-1).select { |i| arr[i] == 50 }}
puts Benchmark.measure{arr.map.with_index {|a, i| a == 50 ? i : nil}.compact}
puts Benchmark.measure{arr.each_index.select{|i| arr[i] == 50}}
puts Benchmark.measure{arr.size.times.select {|i| arr[i] == 50}}
puts Benchmark.measure{arr.find_each_index 50}

  # 1.263000   0.031000   1.294000 (  1.267073)
  # 0.843000   0.000000   0.843000 (  0.846048)
  # 0.936000   0.015000   0.951000 (  0.962055)
  # 0.842000   0.000000   0.842000 (  0.839048)
  # 0.843000   0.000000   0.843000 (  0.843048)
  # 0.405000   0.000000   0.405000 (  0.410024)

请注意,这仅在对参数使用索引而不对块使用时有效。对于一个区块,公认的解决方案仍然是唯一的方法。
Sprachprofi 2013年

5

不知道您是否认为这是一种改进,但是使用(map+ compact)作为过滤器对我来说感觉很笨拙。我会使用select,因为这就是它的用途,然后仅获取我关心的结果的一部分:

arr.each_with_index.select { |a,i| a == 'x' }.map &:last

3

我定义Array#index_all了行为,Array#index但返回所有匹配的索引。此方法可以接受参数并阻止。

class Array
  def index_all(obj = nil)
    if obj || block_given?
      proc = obj ? ->(i) { self[i] == obj } : ->(i) { yield self[i] }
      self.each_index.select(&proc)
    else
      self.each
    end
  end
end

require 'test/unit'

class TestArray < Test::Unit::TestCase
  def test_index_all
    arr = ['x', 'o', 'x', '.', '.', 'o', 'x']
    result = arr.index_all('x')
    assert_equal [0, 2, 6], result

    arr = [100, 200, 100, 300, 100, 400]
    result = arr.index_all {|n| n <= 200 }
    assert_equal [0, 1, 2, 4], result
  end
end
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.