Pertanyaan Periksa apakah ada nilai dalam array di Ruby


Saya punya nilai 'Dog' dan sebuah array ['Cat', 'Dog', 'Bird'].

Bagaimana cara memeriksa apakah ada dalam larik tanpa melewatinya? Apakah ada cara sederhana untuk memeriksa apakah nilai itu ada, tidak lebih?


1106
2017-12-31 17:49


asal


Jawaban:


Kamu sedang mencari include?:

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

1659
2017-12-31 17:51



Ada sebuah in? metode di ActiveSupport (bagian dari Rails) sejak v3.1, seperti yang ditunjukkan oleh @campaterson. Jadi dalam Rails, atau jika Anda require 'active_support', kamu bisa menulis:

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

OTOH, tidak ada in operator atau #in? di Ruby itu sendiri, meskipun telah diusulkan sebelumnya, khususnya oleh Yusuke Endoh anggota kelas atas ruby-core.

Seperti yang ditunjukkan oleh orang lain, metode sebaliknya include? ada, untuk semua Enumerables termasuk Array, Hash, Set, Range:

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

Perhatikan bahwa jika Anda memiliki banyak nilai dalam larik Anda, mereka akan diperiksa satu per satu setelah yang lain (mis. O(n)), sementara pencarian untuk hash akan menjadi waktu yang konstan (yaitu O(1)). Jadi, jika Anda array konstan, misalnya, itu adalah ide yang baik untuk menggunakan Set sebagai gantinya. Misalnya:

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

SEBUAH tes cepat mengungkapkan panggilan itu include? pada 10 elemen Set adalah sekitar 3,5x lebih cepat daripada memanggilnya pada yang setara Array (jika elemen tidak ditemukan).

Catatan penutup terakhir: berhati-hatilah saat menggunakan include? pada suatu Range, ada kehalusan, jadi lihatlah dokter dan bandingkan dengan cover?...


205
2018-05-15 12:50



Mencoba

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

157
2017-12-31 17:52



Menggunakan Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

Atau, jika sejumlah tes selesai,1 Anda dapat menyingkirkan loop (bahkan include? memiliki) dan pergi dari Di) untuk O (1) dengan:

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


1. Saya harap ini jelas tetapi untuk menghentikan keberatan: ya, hanya untuk beberapa pencarian, Hash [] dan transpose ops mendominasi profil dan masing-masing Di) diri.


44
2017-12-31 17:52



Jika Anda ingin mengecek dengan satu blok, Anda dapat mencoba? atau semua?

%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  

Detail ada di sini: http://ruby-doc.org/core-1.9.3/Enumerable.html
Inspirasi saya berasal dari sini: https://stackoverflow.com/a/10342734/576497


41
2018-05-20 09:08



Beberapa jawaban menyarankan Array#include?, tetapi ada satu peringatan penting: Melihat sumbernya, bahkan Array#include? melakukan perulangan:

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;
}

Cara untuk menguji kehadiran kata tanpa perulangan adalah dengan membangun a trie untuk array Anda. Ada banyak implementasi trie di luar sana (google "ruby trie"). saya akan gunakan rambling-trie dalam contoh ini:

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 }

Dan sekarang kami siap untuk menguji keberadaan berbagai kata dalam array Anda tanpa mengulanginya, di O(log n) waktu, dengan kesederhanaan sintaksis yang sama seperti Array#include?, menggunakan sublinear Trie#include?:

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

28
2018-06-10 16:23



Ruby memiliki 11 metode untuk menemukan elemen dalam sebuah array.

Yang lebih disukai adalah include?

Atau untuk akses berulang, membuat satu set dan kemudian menelepon include? atau member?

Ini semuanya,

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

Semuanya kembali a truenilai ish jika elemen tersebut ada.

include? adalah metode yang disukai. Ia menggunakan bahasa C for loop internal yang rusak ketika sebuah elemen cocok dengan internal rb_equal_opt/rb_equalfungsi. Ini tidak bisa jauh lebih efisien kecuali Anda membuat satu set untuk pemeriksaan keanggotaan berulang.

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? tidak didefinisikan ulang Array kelas dan menggunakan implementasi yang tidak dioptimalkan dari Enumerable modul yang secara harfiah menyebutkan melalui semua elemen.

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;
}

Diterjemahkan ke kode Ruby ini tentang hal berikut

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

Kedua include? dan member? memiliki O(n) kompleksitas waktu karena keduanya mencari array untuk kemunculan pertama dari nilai yang diharapkan.

Kita bisa menggunakan satu set untuk mendapatkannya O(1) waktu akses dengan biaya harus membuat representasi hash dari array pertama. Jika Anda berulang kali memeriksa keanggotaan pada larik yang sama, investasi awal ini dapat melunasi dengan cepat. Set tidak diimplementasikan dalam C tetapi sebagai kelas Ruby biasa, masih O(1) waktu akses yang mendasarinya @hash membuat ini berharga.

Berikut ini adalah implementasi dari Set kelas,

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

Seperti yang Anda lihat Set kelas hanya menciptakan internal @hash Misalnya, petakan semua objek ke true dan kemudian cek keanggotaan menggunakan Hash#include? yang diimplementasikan dengan O(1) waktu akses dalam Hash kelas.

Saya tidak akan membahas 7 metode lainnya karena semuanya kurang efisien.

Sebenarnya ada lebih banyak metode dengan O(n) kerumitan di luar 11 yang tercantum di atas, tetapi saya memutuskan untuk tidak mencantumkannya karena memindai seluruh array daripada melanggar pada pertandingan pertama.

Jangan gunakan ini,

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

23
2017-12-25 23:40



Jika Anda tidak ingin mengulang, tidak ada cara untuk melakukannya dengan Array. Anda sebaiknya menggunakan Kumpulan saja.

require 'set'
s = Set.new
100.times{|i| s << "foo#{i}"}
s.include?("foo99")
 => true
[1,2,3,4,5,6,7,8].to_set.include?(4) 
  => true

Set bekerja secara internal seperti hash, sehingga Ruby tidak perlu mengulang melalui koleksi untuk menemukan item, karena seperti namanya, itu menghasilkan hash kunci dan membuat peta memori sehingga setiap titik hash ke titik tertentu dalam memori. Contoh sebelumnya dilakukan dengan Hash:

fake_array = {}
100.times{|i| fake_array["foo#{i}"] = 1}
fake_array.has_key?("foo99")
  => true

Kelemahannya adalah bahwa Set dan kunci hash hanya dapat menyertakan barang-barang unik dan jika Anda menambahkan banyak item, Ruby harus mengulang semuanya setelah sejumlah item untuk membangun peta baru yang sesuai dengan ruang tombol yang lebih besar. Untuk lebih lanjut tentang ini, saya sarankan Anda menonton MountainWest RubyConf 2014 - Big O dalam Hash Buatan Sendiri oleh Nathan Long 

Berikut ini tolok ukurnya:

require 'benchmark'
require 'set'

array = []
set   = Set.new

10_000.times do |i|
  array << "foo#{i}"
  set   << "foo#{i}"
end

Benchmark.bm do |x|
  x.report("array") { 10_000.times { array.include?("foo9999") } }
  x.report("set  ") { 10_000.times { set.include?("foo9999")   } }
end

Dan hasilnya:

      user     system      total        real
array  7.020000   0.000000   7.020000 (  7.031525)
set    0.010000   0.000000   0.010000 (  0.004816)

16
2018-05-29 19:58



Ini adalah cara lain untuk melakukan ini: gunakan metode indeks Array #.

Ini mengembalikan indeks dari kemunculan pertama dari elemen dalam array.

contoh:

a = ['cat','dog','horse']
if a.index('dog')
    puts "dog exists in the array"
end

indeks () juga dapat mengambil blok

sebagai contoh

a = ['cat','dog','horse']
puts a.index {|x| x.match /o/}

di sini, kembalikan indeks kata pertama dalam larik yang berisi huruf 'o'.


15
2017-10-02 17:22



Ada banyak cara untuk mencapai ini. Beberapa di antaranya adalah sebagai berikut:

a = [1,2,3,4,5]

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false

8
2018-04-29 10:12



Fakta yang menyenangkan,

Kamu dapat memakai * untuk memeriksa keanggotaan array dalam suatu case ekspresi.

case element
when *array 
  ...
else
  ...
end

Perhatikan si kecil * pada saat klausa, ini memeriksa keanggotaan dalam larik.

Semua perilaku sulap yang biasa dilakukan operator splat berlaku, jadi misalnya jika array sebenarnya bukan sebuah array tetapi elemen tunggal akan cocok dengan elemen itu.


6
2017-12-25 23:48