[ruby] Ruby에서 배열에 값이 있는지 확인하는 방법

'Dog'과 배열이 ['Cat', 'Dog', 'Bird']있습니다.

배열을 반복하지 않고 배열에 존재하는지 어떻게 확인합니까? 값이 존재하는지 확인하는 간단한 방법이 있습니까?



답변

당신이 찾고있는 것 include?:

>> ['Cat', 'Dog', 'Bird'].include? 'Dog'
=> true


답변

in?방법ActiveSupport@campaterson가 가리키는 아웃과 같은 버전 3.1 이후 (레일의 일부). 따라서 Rails 내에서 또는 다음 require 'active_support'과 같이 쓸 수 있습니다.

'Unicorn'.in?(['Cat', 'Dog', 'Bird']) # => false

OTOH, Ruby 자체 에는 in연산자 나 #in?메소드 가 없습니다. 비록 이전에, 특히 Yusuke Endoh에 의해 제안되었지만 루비 코어의 최고 수준의 멤버 인 .

다른 사람에 의해 지적, 반대 방법이 include?존재하며, 모든 Enumerable포함의 Array, Hash, Set, Range:

['Cat', 'Dog', 'Bird'].include?('Unicorn') # => false

배열에 많은 값이 있으면 O(n)해시에 대한 조회가 일정한 시간 (예 :)으로 바뀌면서 값이 하나씩 차례로 확인 됩니다 (예 🙂 O(1). 예를 들어 배열이 일정하면 대신 Set 을 사용하는 것이 좋습니다 . 예 :

require 'set'
ALLOWED_METHODS = Set[:to_s, :to_i, :upcase, :downcase
                       # etc
                     ]

def foo(what)
  raise "Not allowed" unless ALLOWED_METHODS.include?(what.to_sym)
  bar.send(what)
end

빠른 테스트는 호출하는 것을 알 수 include?10 요소를하는 Set동등에 전화보다 빠른 3.5 배에 관한 것입니다Array (요소가 발견되지 않는 경우).

마지막 닫는주의 사항 : 사용시주의해야 include?켜짐 Range, 그래서 참조, 미묘한가 다큐먼트 와 비교 cover?


답변

시험

['Cat', 'Dog', 'Bird'].include?('Dog')


답변

사용 Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

또는 여러 테스트가 수행되면 1 루프를 제거 include?하고 다음을 사용하여 O (n) 에서 O (1) 로 이동할 수 있습니다 .

h = Hash[[a, a].transpose]
h['Dog']


1. 나는 이것이 명백하지만 반대 의견을 피하기를 희망한다. 그렇다. 단지 몇 번의 룩업을 위해서 Hash []와 transpose ops가 프로파일을 지배하고 각각 O (n) 그 자체이다.


답변

블록별로 확인하려면 any?또는을 시도하십시오 all?.

%w{ant bear cat}.any? {|word| word.length >= 3}   #=> true  
%w{ant bear cat}.any? {|word| word.length >= 4}   #=> true  
[ nil, true, 99 ].any?                            #=> true  

열거 가능 참조 을 참조하십시오.

내 영감 “에서 온 배열 루비에서 모든 항목이있는 경우 평가


답변

루비에는 배열에서 요소를 찾는 11 가지 방법이 있습니다.

바람직한 하나는 include?반복 된 액세스에 대해 호출 후 creat에 세트하고, 또는 include?member?.

다음은 모두입니다.

array.include?(element) # preferred method
array.member?(element)
array.to_set.include?(element)
array.to_set.member?(element)
array.index(element) > 0
array.find_index(element) > 0
array.index { |each| each == element } > 0
array.find_index { |each| each == element } > 0
array.any? { |each| each == element }
array.find { |each| each == element } != nil
array.detect { |each| each == element } != nil

true요소가 존재하면 모두 ish 값을 반환합니다 .

include?선호되는 방법입니다. for내부적 으로 C 언어 루프를 사용 하여 요소가 내부 rb_equal_opt/rb_equal함수 와 일치 할 때 중단됩니다 . 반복 멤버쉽 확인을위한 세트를 작성하지 않으면 훨씬 효율적으로 얻을 수 없습니다.

VALUE
rb_ary_includes(VALUE ary, VALUE item)
{
  long i;
  VALUE e;

  for (i=0; i<RARRAY_LEN(ary); i++) {
    e = RARRAY_AREF(ary, i);
    switch (rb_equal_opt(e, item)) {
      case Qundef:
        if (rb_equal(e, item)) return Qtrue;
        break;
      case Qtrue:
        return Qtrue;
    }
  }
  return Qfalse;
}

member?Array클래스 에서 재정의되지 않고 Enumerable모든 요소를 ​​문자 그대로 열거 하는 모듈 에서 최적화되지 않은 구현을 사용합니다 .

static VALUE
member_i(RB_BLOCK_CALL_FUNC_ARGLIST(iter, args))
{
  struct MEMO *memo = MEMO_CAST(args);

  if (rb_equal(rb_enum_values_pack(argc, argv), memo->v1)) {
    MEMO_V2_SET(memo, Qtrue);
    rb_iter_break();
  }
  return Qnil;
}

static VALUE
enum_member(VALUE obj, VALUE val)
{
  struct MEMO *memo = MEMO_NEW(val, Qfalse, 0);

  rb_block_call(obj, id_each, 0, 0, member_i, (VALUE)memo);
  return memo->v2;
}

Ruby 코드로 변환하면 다음과 같은 작업이 수행됩니다.

def member?(value)
  memo = [value, false, 0]
  each_with_object(memo) do |each, memo|
    if each == memo[0]
      memo[1] = true
      break
    end
  memo[1]
end

모두 include?member?때문에 O (n)은 시간 복잡도가 모두 기대 값의 첫번째 발생의 배열을 검색.

Set을 사용하여 배열의 해시 표현을 먼저 생성해야하는 비용으로 O (1) 액세스 시간을 얻을 수 있습니다. 동일한 어레이에서 멤버십을 반복해서 확인하면이 초기 투자가 빠르게 보상받을 수 있습니다. SetC에서는 구현되지 않지만 일반 Ruby 클래스로 구현되지만 여전히 기본의 O (1) 액세스 시간 @hash이 가치가 있습니다.

Set 클래스의 구현은 다음과 같습니다.

module Enumerable
  def to_set(klass = Set, *args, &block)
    klass.new(self, *args, &block)
  end
end

class Set
  def initialize(enum = nil, &block) # :yields: o
    @hash ||= Hash.new
    enum.nil? and return
    if block
      do_with_enum(enum) { |o| add(block[o]) }
    else
      merge(enum)
    end
  end

  def merge(enum)
    if enum.instance_of?(self.class)
      @hash.update(enum.instance_variable_get(:@hash))
    else
      do_with_enum(enum) { |o| add(o) }
    end
    self
  end

  def add(o)
    @hash[o] = true
    self
  end

  def include?(o)
    @hash.include?(o)
  end
  alias member? include?

  ...
end

보시다시피 Set 클래스는 내부 @hash인스턴스를 만들고 모든 객체를 매핑 true한 다음 다음을 사용하여 멤버십을 확인합니다.Hash#include? 해시 클래스에서 O (1) 액세스 시간으로 구현 된 .

다른 7 가지 방법은 모두 덜 효율적이므로 다루지 않습니다.

실제로 위에 나열된 11 이상의 O (n) 복잡도를 가진 훨씬 더 많은 메소드가 있지만 첫 번째 일치를 깨지 않고 전체 배열을 스캔하기 때문에 나열하지 않기로 결정했습니다.

이것을 사용하지 마십시오 :

# bad examples
array.grep(element).any?
array.select { |each| each == element }.size > 0
...


답변

몇 가지 답변이 제안 Array#include?하지만 중요한 한 가지주의 사항이 있습니다. 소스를보고 Array#include?루핑을 수행합니다.

rb_ary_includes(VALUE ary, VALUE item)
{
    long i;

    for (i=0; i<RARRAY_LEN(ary); i++) {
        if (rb_equal(RARRAY_AREF(ary, i), item)) {
            return Qtrue;
        }
    }
    return Qfalse;
}

루핑없이 단어 존재를 테스트하는 방법은 배열에 대한 trie 를 구성하는 것 입니다. 거기에는 많은 트리 구현 (Google “ruby trie”)이 있습니다. rambling-trie이 예에서 사용하겠습니다 .

a = %w/cat dog bird/

require 'rambling-trie' # if necessary, gem install rambling-trie
trie = Rambling::Trie.create { |trie| a.each do |e| trie << e end }

이제 우리는 sublinear를 사용하여 O(log n)구문상의 단순성으로 다음과 같은 시간에 반복하지 않고 배열에 다양한 단어의 존재를 테스트 할 준비가되었습니다 .Array#include?Trie#include?

trie.include? 'bird' #=> true
trie.include? 'duck' #=> false