Cách kiểm tra nếu một giá trị tồn tại trong một mảng trong Ruby


1315

Tôi có một giá trị 'Dog'và một mảng ['Cat', 'Dog', 'Bird'].

Làm thế nào để tôi kiểm tra nếu nó tồn tại trong mảng mà không lặp qua nó? Có một cách đơn giản để kiểm tra nếu giá trị tồn tại, không có gì hơn?


5
sử dụng .incolee? phương pháp . Nó trả về một boolean đó là những gì bạn muốn. Trong trường hợp của bạn, chỉ cần gõ: ['Cat', 'Dog', 'Bird']. Bao gồm ('Dog') và nó sẽ trả về giá trị boolean đúng.
Jwan622

không sử dụng bao gồm? phương pháp nếu bạn muốn kiểm tra bội số lần cho giá trị khác nhau có mặt trong mảng hay không vì bao gồm? mỗi lần sẽ lặp lại trên mảng lấy thao tác O (n) để tìm kiếm mỗi lần, thay vào đó hãy tạo một hàm băm hash = arr.map {|x| [x,true]}.to_h, bây giờ hãy kiểm tra xem có hash.has_key? 'Dog' trả về đúng hay không
aqfaridi

3
Bạn thực sự không thể làm điều đó hoàn toàn "mà không lặp qua nó". Điều này là không thể, máy tính không thể biết chắc chắn liệu mảng có chứa phần tử mà không lặp qua các phần tử để kiểm tra xem có bất kỳ phần nào trong số chúng là phần tử mà nó đang tìm kiếm hay không. Trừ khi nó trống rỗng, tất nhiên. Sau đó, tôi đoán bạn không cần một vòng lặp.
Tim M.

Xem các điểm chuẩn bên dưới để biết các kiểm tra về sự khác biệt của các cách khác nhau để tìm một phần tử trong Mảng và Tập hợp. stackoverflow.com/a/60404934/128421
Người đàn ông Tin

Câu trả lời:


1946

Bạn đang tìm kiếm include?:

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

73
Cú pháp thay thế:%w(Cat Dog Bird).include? 'Dog'
Scarver2

184
Đôi khi tôi ước nó "chứa" không bao gồm. Tôi luôn luôn nhận được nó trộn lẫn với bao gồm.
Henley Chiu

19
Hãy để tôi lưu ý rằng trong nội bộ, #include?vẫn không thực hiện lặp. Mặc dù vậy, bộ mã hóa được lưu từ việc viết vòng lặp một cách rõ ràng. Tôi đã thêm một câu trả lời thực hiện nhiệm vụ thực sự mà không cần lặp.
Boris Stitnicky

8
@HenleyChiu Tôi được gọi là[ 'Dog', 'Bird', 'Cat' ].has? 'Dog'

4
@AlfonsoVergara Có, bất kỳ giải pháp nào cho một mảng phải thực hiện một số kiểu lặp trong nội bộ; không có cách nào để kiểm tra tư cách thành viên của một mảng mà không lặp. Nếu bạn không muốn thực hiện bất kỳ vòng lặp nào ngay cả trong nội bộ, bạn cần sử dụng một cấu trúc dữ liệu khác, chẳng hạn như một bảng băm hoàn hảo với các khóa có kích thước cố định. Cho rằng không có cách nào để kiểm tra tư cách thành viên trong một mảng mà không lặp trong nội bộ, tôi đã giải thích câu hỏi có nghĩa là "không cần phải viết vòng lặp một cách rõ ràng"
Brian Campbell

250

Có một in?phương thức trong ActiveSupport(một phần của Rails) kể từ phiên bản 3.1, như được chỉ ra bởi @campaterson. Vì vậy, trong Rails, hoặc nếu bạn require 'active_support', bạn có thể viết:

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

OTOH, không có intoán tử hay #in?phương thức nào trong Ruby, mặc dù nó đã được đề xuất trước đó, đặc biệt là Yusuke Endoh, một thành viên xuất sắc hàng đầu của ruby-core.

Như đã chỉ ra bởi những người khác, phương pháp ngược lại include?tồn tại, cho tất cả các Enumerables bao gồm Array, Hash, Set, Range:

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

Lưu ý rằng nếu bạn có nhiều giá trị trong mảng của mình, tất cả chúng sẽ được kiểm tra lần lượt từng cái (nghĩa là O(n)), trong khi việc tìm kiếm băm sẽ là thời gian không đổi (nghĩa là O(1)). Vì vậy, nếu mảng của bạn là hằng số, chẳng hạn, nên sử dụng Set thay thế. Ví dụ:

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

Một thử nghiệm nhanh cho thấy rằng việc gọi include?một phần tử 10 Setnhanh hơn khoảng 3,5 lần so với việc gọi nó trên phần tử tương đương Array(nếu không tìm thấy phần tử này).

Lưu ý cuối cùng: hãy cảnh giác khi sử dụng include?trên a Range, có sự tinh tế, vì vậy hãy tham khảo tài liệu và so sánh với cover?...


2
Mặc dù Ruby không bao gồm #in?trong lõi của nó, nhưng nếu bạn đang sử dụng Rails, thì nó có sẵn. api.rubyonrails.org/groupes/Object.html#method-i-in-3F (Tôi biết đây là Ruby, không phải là câu hỏi Rails, nhưng nó có thể giúp bất cứ ai muốn sử dụng #in?trong Rails. Có vẻ như nó đã được thêm vào Rails 3,1 apidock.com/rails/Object/in%3F
campeterson

166

Thử

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

đây là cú pháp cũ hơn, hãy xem
^^ ^

62
@jahrichie chính xác thì bạn nghĩ gì về "cú pháp cũ" trong câu trả lời này, các dấu ngoặc đơn tùy chọn?
Dennis

3
Tôi đồng ý @Dennis, đây không phải là cũ, dấu ngoặc đơn là tùy chọn và trong hầu hết các trường hợp THỰC HÀNH TỐT .... hãy thử sử dụng bao gồm không có dấu ngoặc đơn trong một dòng nếu ví dụ, ý tôi là tùy thuộc vào trường hợp của bạn nên hoặc phải sử dụng dấu ngoặc hoặc không (hoàn toàn không liên quan đến cú pháp ruby ​​"cũ")
d1jhoni1b

Đây là cú pháp duy nhất tôi có thể làm việc trong một hoạt động tạm thời.
Michael Cameron

49

Sử dụng Enumerable#include:

a = %w/Cat Dog Bird/

a.include? 'Dog'

Hoặc, nếu một số bài kiểm tra được thực hiện, 1 bạn có thể thoát khỏi vòng lặp (thậm chí include?có) và đi từ O (n) đến O (1) với:

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


1. Tôi hy vọng điều này là hiển nhiên nhưng để chống lại sự phản đối: có, chỉ với một vài lần tra cứu, Hash [] và ops chuyển đổi thống trị hồ sơ và mỗi O (n) .


49

Nếu bạn muốn kiểm tra theo một khối, bạn có thể thử any?hoặc 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  

Xem Vô số để biết thêm thông tin.

Cảm hứng của tôi đến từ " đánh giá xem mảng có bất kỳ vật phẩm nào trong ruby ​​không "


1
Rất hữu ích nếu bạn muốn kiểm tra bất kỳ / tất cả các chuỗi đó được bao gồm trong một chuỗi / hằng số khác
thanikkal

43

Ruby có mười một phương thức để tìm các phần tử trong một mảng.

Cái ưa thích là include?hoặc, để truy cập nhiều lần, hãy tạo Set và sau đó gọi include?hoặc member?.

Đây là tất cả chúng:

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

Tất cả đều trả về truegiá trị ish nếu phần tử có mặt.

include?là phương pháp ưa thích. Nó sử dụng forvòng lặp ngôn ngữ C bên trong, ngắt khi một phần tử khớp với các rb_equal_opt/rb_equalchức năng bên trong . Nó không thể hiệu quả hơn nhiều trừ khi bạn tạo Set để kiểm tra thành viên nhiều lần.

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?không được định nghĩa lại trong Arraylớp và sử dụng một triển khai không được tối ưu hóa từ Enumerablemô đun mà liệt kê theo nghĩa đen thông qua tất cả các yếu tố:

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

Được dịch sang mã Ruby, điều này thực hiện như sau:

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

Cả hai include?member?có độ phức tạp thời gian O (n) kể từ khi cả hai tìm kiếm mảng cho lần xuất hiện đầu tiên của giá trị mong đợi.

Chúng ta có thể sử dụng Set để lấy thời gian truy cập O (1) với chi phí phải tạo đại diện Hash của mảng trước. Nếu bạn liên tục kiểm tra tư cách thành viên trên cùng một mảng, khoản đầu tư ban đầu này có thể được thanh toán nhanh chóng. Setkhông được triển khai trong C mà là lớp Ruby đơn giản, vẫn là thời gian truy cập O (1) của cơ sở @hashlàm cho điều này đáng giá.

Dưới đây là việc triển khai lớp 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

Như bạn có thể thấy lớp Set chỉ tạo một thể hiện bên trong @hash, ánh xạ tất cả các đối tượng truevà sau đó kiểm tra tư cách thành viên bằng cách sử dụng Hash#include?thời gian truy cập O (1) trong lớp Hash.

Tôi sẽ không thảo luận về bảy phương pháp khác vì tất cả chúng đều kém hiệu quả.

Thực tế thậm chí còn có nhiều phương thức với độ phức tạp O (n) ngoài 11 được liệt kê ở trên, nhưng tôi quyết định không liệt kê chúng vì chúng quét toàn bộ mảng thay vì phá vỡ ở trận đấu đầu tiên.

Đừng sử dụng những thứ này:

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

Thật trơ trẽn khi nói rằng Ruby có chính xác 11 cách để làm bất cứ điều gì! Không sớm hơn bạn nói rằng ai đó sẽ chỉ ra rằng bạn đã bỏ lỡ # 12, sau đó # 13, v.v. Để đưa ra quan điểm của tôi, tôi sẽ đề xuất những cách khác, nhưng trước tiên hãy để tôi đặt câu hỏi về 11những cách bạn đã liệt kê. Đầu tiên, bạn khó có thể tính indexfind_index(hoặc finddetect) là các phương thức riêng biệt, vì chúng chỉ là các tên khác nhau cho cùng một phương thức. Thứ hai, tất cả các biểu thức kết thúc bằng > 0không chính xác, mà tôi chắc chắn là một sự giám sát. (tt)
Cary Swoveland

... arr.index(e), ví dụ, trả về 0nếu arr[0] == e. Bạn sẽ nhớ lại arr.index(e)lợi nhuận nilnếu ekhông có mặt. indexkhông thể được sử dụng, tuy nhiên, nếu ai tìm kiếm niltrong arr. (Cùng một vấn đề với rindex, không được liệt kê.). Chuyển đổi mảng thành một tập hợp và sau đó sử dụng các phương thức tập hợp là một chút kéo dài. Tại sao không chuyển đổi thành hàm băm (với các khóa từ mảng và giá trị tùy ý), sau đó sử dụng phương thức băm? Ngay cả khi chuyển đổi thành một bộ là OK, vẫn có các phương thức thiết lập khác có thể được sử dụng, chẳng hạn như !arr.to_set.add?(e). (tt)
Cary Swoveland 28/07/18

... Như đã hứa, đây là một vài phương pháp hơn có thể được sử dụng: arr.count(e) > 0, arr != arr.dup.delete(e) , arr != arr - [e]arr & [e] == [e]. Ai cũng có thể sử dụng selectreject.
Cary Swoveland

Tôi thực sự khuyên bạn nên sử dụng Bộ nếu tiêu chí là không cho phép trùng lặp và biết liệu một thành phần cụ thể có tồn tại trong danh sách hay không. Đặt là SOooo nhanh hơn nhiều. Mảng thực sự không nên được sử dụng khi cần tìm kiếm; Chúng được sử dụng tốt hơn như một hàng đợi trong đó mọi thứ được lưu trữ tạm thời để được xử lý theo thứ tự. Hash và Set là tốt hơn để tìm kiếm nếu một cái gì đó tồn tại.
Tin Man

30

Một số câu trả lời gợi ý Array#include?, nhưng có một cảnh báo quan trọng: Nhìn vào nguồn, thậm chí Array#include?không thực hiện lặp:

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

Cách để kiểm tra sự hiện diện từ mà không lặp là bằng cách xây dựng một Trie cho mảng của bạn. Có rất nhiều triển khai trie ngoài kia (google "ruby trie"). Tôi sẽ sử dụng rambling-trietrong ví dụ này:

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 }

Và bây giờ chúng tôi đã sẵn sàng để kiểm tra sự hiện diện của các từ khác nhau trong mảng của bạn mà không lặp lại theo O(log n)thời gian, với sự đơn giản cú pháp giống như Array#include?, sử dụng dòng con Trie#include?:

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

8
a.each do ... endUmm ... không chắc làm thế nào mà không phải là một vòng lặp
Doorknob

27
Lưu ý rằng điều này thực sự bao gồm một vòng lặp; bất cứ điều gì không phải là O (1) bao gồm một số loại vòng lặp. Nó chỉ là một vòng lặp trên các ký tự của chuỗi đầu vào. Cũng lưu ý hơn một câu trả lời đã được đề cập Set#include?cho những người quan tâm đến hiệu quả; kết hợp với việc sử dụng các ký hiệu thay vì các chuỗi, nó có thể là trường hợp trung bình O (1) (nếu bạn sử dụng các chuỗi, thì chỉ cần tính toán hàm băm là O (n) trong đó n là độ dài của chuỗi). Hoặc nếu bạn muốn sử dụng các thư viện của bên thứ ba, bạn có thể sử dụng hàm băm hoàn hảo là trường hợp xấu nhất O (1).
Brian Campbell

4
AFAIK, Setsử dụng băm để lập chỉ mục các thành viên của nó, do đó, thực sự Set#include? phải có độ phức tạp O (1) để phân phối tốt Set(cụ thể hơn là O (kích thước đầu vào) cho băm và O (log (n / số-xô)) cho tìm kiếm)
Uri Agassi

11
Chi phí tạo và duy trì bộ ba cũng nhiều như vậy. Nếu bạn đang thực hiện nhiều thao tác tìm kiếm trên mảng, thì bộ nhớ và chi phí thời gian để tạo ra một bộ ba và duy trì nó là đáng giá, nhưng đối với một, hoặc thậm chí hàng trăm hoặc hàng ngàn kiểm tra, O (n) là hoàn toàn phù hợp. Một tùy chọn khác không yêu cầu thêm phụ thuộc sẽ là sắp xếp mảng hoặc duy trì nó theo thứ tự được sắp xếp, trong trường hợp đó có thể sử dụng thao tác tìm kiếm nhị phân O (lg n) để kiểm tra sự bao gồm.
nói

1
@speakingcode, bạn có thể đúng từ quan điểm thực dụng. Nhưng OP yêu cầu "kiểm tra xem giá trị có tồn tại không, không có gì nữa, không lặp lại". Khi tôi viết câu trả lời này, có nhiều giải pháp thực dụng ở đây, nhưng không có giải pháp nào thực sự đáp ứng yêu cầu theo nghĩa đen của người hỏi. Quan sát của bạn rằng các BST có liên quan đến các lần thử là chính xác, nhưng đối với các chuỗi, trie là công cụ phù hợp cho công việc, ngay cả Wikipedia cũng biết điều đó rất nhiều . Sự phức tạp của việc xây dựng và duy trì một bộ ba được thực hiện tốt là rất thuận lợi.
Boris Stitnicky

18

Nếu bạn không muốn lặp, không có cách nào để làm điều đó với Mảng. Bạn nên sử dụng Set thay thế.

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

Các bộ hoạt động bên trong như Băm, vì vậy Ruby không cần phải lặp qua bộ sưu tập để tìm các mục, vì như tên của nó, nó tạo ra các giá trị băm của các phím và tạo một bản đồ bộ nhớ để mỗi điểm băm chỉ đến một điểm nhất định trong bộ nhớ. Ví dụ trước được thực hiện với Hash:

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

Nhược điểm là các khóa Bộ và Hash chỉ có thể bao gồm các vật phẩm duy nhất và nếu bạn thêm nhiều vật phẩm, Ruby sẽ phải thử lại toàn bộ vật phẩm sau một số vật phẩm nhất định để xây dựng bản đồ mới phù hợp với không gian phím lớn hơn. Để biết thêm về điều này, tôi khuyên bạn nên xem " MountainWest RubyConf 2014 - Big O in a Homemade Hash của Nathan Long ".

Đây là một điểm chuẩn:

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

Và kết quả:

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

Nếu bạn sử dụng phát hiện, thì ít nhất bạn có thể giảm vòng lặp. phát hiện sẽ dừng ở mục đầu tiên 'được phát hiện' (khối được truyền cho mục được đánh giá là đúng). Ngoài ra, bạn có thể cho biết phát hiện phải làm gì nếu không phát hiện ra điều gì (bạn có thể vượt qua trong lambda).
aenw

1
@aenw không include?dừng lại ở lần đánh đầu tiên?
Kimmo Lehto

bạn hoàn toàn đúng. Tôi đã quá quen với việc sử dụng phát hiện mà tôi đã quên rằng bao gồm. cảm ơn vì nhận xét của bạn - nó đảm bảo rằng tôi đã làm mới kiến ​​thức của mình.
aenw

include?dừng ở lần truy cập đầu tiên nhưng nếu lần truy cập đó ở cuối danh sách .... Bất kỳ giải pháp nào dựa vào Mảng để lưu trữ sẽ có hiệu suất giảm khi danh sách phát triển, đặc biệt là khi phải tìm một phần tử ở cuối danh sách danh sách. Hash và Set không có vấn đề đó, cũng không có danh sách theo thứ tự và tìm kiếm nhị phân.
Tin Man

Đó là khá nhiều những gì câu trả lời này đã có ở vị trí đầu tiên :)
Kimmo Lehto

17

Đây là một cách khác để làm điều này: sử dụng Array#indexphương pháp.

Nó trả về chỉ mục của lần xuất hiện đầu tiên của phần tử trong mảng.

Ví dụ:

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

index() cũng có thể mất một khối:

Ví dụ:

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

Điều này trả về chỉ mục của từ đầu tiên trong mảng có chứa chữ 'o'.


indexvẫn lặp trên mảng, nó chỉ trả về giá trị của phần tử.
Tin Man

13

Sự thật thú vị,

Bạn có thể sử dụng *để kiểm tra thành viên mảng trong một casebiểu thức.

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

Lưu ý một chút *trong mệnh đề khi, điều này kiểm tra tư cách thành viên trong mảng.

Tất cả các hành vi ma thuật thông thường của toán tử splat đều được áp dụng, vì vậy, ví dụ nếu arraykhông thực sự là một mảng mà là một phần tử duy nhất thì nó sẽ khớp với phần tử đó.


Thật tốt khi biết ..!
Cary Swoveland

Đây cũng là một kiểm tra đầu tiên chậm trong một tuyên bố trường hợp, vì vậy tôi sẽ sử dụng nó trong lần cuối cùng whencó thể để các kiểm tra khác nhanh hơn, nhanh chóng bị loại bỏ.
Tin Man

9

Có nhiều cách để thực hiện điều này. Một vài trong số đó là như sau:

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

2.in? a  #=> true

8.in? a #=> false

a.member? 1 #=> true

a.member? 8 #=> false

6
Lưu ý rằng Object#in?chỉ được thêm vào Rails (tức là ActiveSupport) v3.1 +. Nó không có sẵn trong lõi Ruby.
Tom Lord

5

Nếu bạn cần kiểm tra bội số lần cho bất kỳ khóa nào, hãy chuyển đổi arrthành hashvà bây giờ kiểm tra O (1)

arr = ['Cat', 'Dog', 'Bird']
hash = arr.map {|x| [x,true]}.to_h
 => {"Cat"=>true, "Dog"=>true, "Bird"=>true}
hash["Dog"]
 => true
hash["Insect"]
 => false

Hiệu suất của Hash # has_key? so với mảng # bao gồm?

Tham số Hash # has_key? Mảng # bao gồm

Thời gian phức tạp O (1) hoạt động O (n) hoạt động 

Kiểu truy cập Truy cập Hash [key] nếu nó lặp qua từng phần tử
                        trả về bất kỳ giá trị nào sau đó của mảng cho đến khi
                        true được trả về giá trị tìm thấy trong Array
                        Băm # has_key? gọi
                        gọi    

Đối với kiểm tra một lần sử dụng include?là tốt


Bạn vẫn không lặp qua mảng để chuyển đổi nó thành hàm băm?
chrisjacob

2
vâng, tôi đã làm nhưng bây giờ tôi có câu trả lời được tính toán trước để kiểm tra xem có phần tử nào tồn tại trong mảng hay không. Bây giờ, lần sau khi tôi tìm kiếm từ khác, nó sẽ lấy O (1) cho truy vấn, mặc dù tiền tính toán sẽ mất O (n). Trên thực tế tôi đã sử dụng bao gồm? trong ứng dụng của tôi bên trong vòng lặp để kiểm tra xem phần tử cụ thể có tồn tại trong mảng hay không, nó phá hỏng hiệu năng, nó đã kiểm tra trong ruby-prof, đó là nút cổ chai
aqfaridi

1
.... nhưng không gian O (n) và cũng không, không phải là thời gian O (n), vì như @ chris72205 chỉ ra một cách chính xác, trước tiên bạn phải lặp qua mảng của mình. O (1) + O (n) = O (n). Vì vậy, trên thực tế, đây là cách tồi tệ hơn bao gồm?
Rambatino

Anh bạn tôi chỉ nói không sử dụng bao gồm vòng lặp bên trong, để kiểm tra một lần bằng cách sử dụng bao gồm là tốt. Vì vậy, vui lòng đọc trường hợp sử dụng trước, tốt hơn nếu bạn cần kiểm tra nhiều lần bên trong vòng lặp.
aqfaridi

Chuyển đổi một mảng thành một hàm băm rất tốn kém, nhưng sử dụng một mảng để tìm kiếm là cấu trúc sai. Mảng tốt hơn khi hàng đợi trong đó thứ tự có thể quan trọng; Băm và Bộ sẽ tốt hơn khi bao gồm / tồn tại / duy nhất là quan trọng. Bắt đầu với đúng container và vấn đề là moot.
Tin Man

4

Điều này sẽ cho bạn biết không chỉ nó tồn tại mà còn bao nhiêu lần nó xuất hiện:

 a = ['Cat', 'Dog', 'Bird']
 a.count("Dog")
 #=> 1

12
Tuy nhiên, không có ý nghĩa gì trong việc sử dụng điều này trừ khi bạn muốn biết nó xuất hiện bao nhiêu lần, mặc dù vậy, .any?nó sẽ quay trở lại ngay khi tìm thấy phần tử khớp đầu tiên, .countsẽ luôn xử lý toàn bộ mảng.
Zaz

Mặc dù về mặt kỹ thuật này sẽ cho biết liệu có thứ gì đó tồn tại hay không, đó cũng KHÔNG phải là cách làm đúng nếu bạn quan tâm đến tốc độ.
Tin Man

4

Đối với những gì nó có giá trị, tài liệu Ruby là một nguồn tài nguyên tuyệt vời cho những loại câu hỏi này.

Tôi cũng sẽ lưu ý về độ dài của mảng bạn đang tìm kiếm. Các include?phương pháp sẽ chạy một tìm kiếm tuyến tính với O (n) sự phức tạp mà có thể nhận được khá xấu xí tùy thuộc vào kích thước của mảng.

Nếu bạn đang làm việc với một mảng lớn (được sắp xếp), tôi sẽ xem xét việc viết một thuật toán tìm kiếm nhị phân không quá khó và có trường hợp xấu nhất là O (log n).

Hoặc nếu bạn đang sử dụng Ruby 2.0, bạn có thể tận dụng bsearch.


3
Một tìm kiếm nhị phân giả định mảng được sắp xếp (hoặc được sắp xếp theo một số hình thức) có thể tốn kém cho các mảng lớn, thường phủ nhận lợi thế.
Tin Man

Một tìm kiếm nhị phân cũng yêu cầu tất cả các cặp yếu tố phải tương đương với nhau <=>, điều này không phải lúc nào cũng đúng. Giả sử, ví dụ, các phần tử của mảng là băm.
Cary Swoveland

1
@CarySwoveland nên ít nhiều ngụ ý rằng các phần tử trong một mảng được sắp xếp là tương đương nhau.
davissp14

4

Bạn co thể thử:

Ví dụ: nếu Cat và Dog tồn tại trong mảng:

(['Cat','Dog','Bird'] & ['Cat','Dog'] ).size == 2   #or replace 2 with ['Cat','Dog].size

Thay vì:

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

Lưu ý: member?include?giống nhau.

Điều này có thể làm công việc trong một dòng!


3

Nếu chúng ta không muốn sử dụng include?thì nó cũng hoạt động:

['cat','dog','horse'].select{ |x| x == 'dog' }.any?

3
bất kì? cũng chấp nhận các khối: ['mèo', 'chó', 'ngựa']. bất kỳ? {| x | x == 'chó'}
maikonas

3
['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}
=> "Dog"
!['Cat', 'Dog', 'Bird'].detect { |x| x == 'Dog'}.nil?
=> true

['Cat', nil, 'Dog'].detect { |x| x == nil } #=> nil. Đã nilđược tìm thấy?
Cary Swoveland

2

Làm thế nào về cách này?

['Cat', 'Dog', 'Bird'].index('Dog')

Nó vẫn sẽ lặp đi lặp lại trên mảng chỉ để tìm phần tử. Sau đó, nó phải trả về chỉ mục của phần tử đó.
Tin Man

2

Nếu bạn đang cố gắng thực hiện việc này trong bài kiểm tra đơn vị MiniTest , bạn có thể sử dụng assert_includes. Thí dụ:

pets = ['Cat', 'Dog', 'Bird']
assert_includes(pets, 'Dog')      # -> passes
assert_includes(pets, 'Zebra')    # -> fails 

2

Có cách khác để giải quyết vấn đề này.

Giả sử mảng đó [ :edit, :update, :create, :show ], có lẽ là toàn bộ bảy tội lỗi chết người / yên tĩnh .

Và hơn nữa đồ chơi với ý tưởng kéo một hành động hợp lệ từ một số chuỗi:

"my brother would like me to update his profile"

Sau đó:

[ :edit, :update, :create, :show ].select{|v| v if "my brother would like me to update his profile".downcase =~ /[,|.| |]#{v.to_s}[,|.| |]/}

1
Regex của bạn /[,|.| |]#{v.to_s}[,|.| |]/, khiến tôi nghĩ rằng bạn muốn tìm 'tên của hành động được bao quanh bởi một trong số: dấu phẩy, dấu chấm, dấu cách hoặc không có gì cả', nhưng có một số lỗi tinh vi. "|update|"sẽ trở lại [:update]"update"sẽ trở lại []. Các lớp ký tự ( [...]) không sử dụng ống dẫn ( |) để phân tách các ký tự. Ngay cả khi chúng tôi thay đổi chúng thành các nhóm ( (...)), bạn không thể khớp một ký tự trống. Vì vậy, regex bạn có thể muốn là/(,|\.| |^)#{v.to_s}(,|\.| |$)/
bkDJ

regex vẫn ổn (đã kiểm tra rubular.com) - và @Rambatino: tại sao lại giống như Amazon Echo;) Bạn có thể nói: "vui lòng thêm gà vào danh sách mua sắm của tôi" (và - tốt - sau đó bạn sẽ phải thêm vào: thêm vào mảng, nhưng tôi nghĩ bạn sẽ nắm được ý chính của nó;)
walt_die

1
"regex là ok (đã kiểm tra rubular.com)". Nó không ổn Regex của bạn sẽ không khớp với từ khóa ở đầu hoặc cuối chuỗi (ví dụ: "cập nhật hồ sơ của anh tôi"). Nếu bạn không muốn khớp từ đầu hoặc cuối, thì regex của bạn vẫn không ổn vì lớp nhân vật ở hai bên của từ khóa phải là/[,. ]/
bkDJ

Như @bkDJ nói, regex là sai. rubular.com/r/4EG04rANz6KET6
Người đàn ông Tin

1

Nếu bạn muốn trả về giá trị không chỉ đúng hay sai, hãy sử dụng

array.find{|x| x == 'Dog'}

Điều này sẽ trả về 'Dog' nếu nó tồn tại trong danh sách, nếu không thì không.


Hoặc sử dụng array.any?{|x| x == 'Dog'}nếu bạn làm muốn đúng / sai (không phải là giá trị), nhưng cũng muốn so sánh với một khối như thế này.
mahemoff

0

Đây là một cách khác để làm điều này:

arr = ['Cat', 'Dog', 'Bird']
e = 'Dog'

present = arr.size != (arr - [e]).size

1
Đây là một cách không hiệu quả khủng khiếp để làm điều này! Tôi không hạ thấp vì nó không đúng về mặt kỹ thuật và ai đó có thể học được điều gì đó về Ruby khi đọc nó, nhưng có nhiều câu trả lời tốt hơn ở trên.
jibberia

Hình nón, bạn có thể đơn giản hóa arr != arr - [e]. arr & [e] == [e]là một cách khác dọc theo cùng một dòng.
Cary Swoveland

@CarySwoveland Đừng vui vẻ với chiếc mũ của phù thủy ;-)
Wand Maker

Tôi đã đề cập đến đầu của phù thủy, không phải mũ, với sự tôn trọng lớn nhất.
Cary Swoveland


0

nó có nhiều cách để tìm một phần tử trong bất kỳ mảng nào nhưng cách đơn giản nhất là 'in?' phương pháp.

example:
arr = [1,2,3,4]
number = 1
puts "yes #{number} is present in arr" if number.in? arr

2
Lưu ý: Như được mô tả trong câu trả lời này , phương thức in?yêu cầu ActiveSupportđược nhập : require active_support.
Patrick

Nó không yêu cầu tất cả ActiveSupport nếu bạn sử dụng các tiện ích mở rộng cốt lõi .
Tin Man

0

nếu bạn không muốn sử dụng, include?trước tiên bạn có thể bọc phần tử trong một mảng và sau đó kiểm tra xem phần tử được bao bọc có bằng giao điểm của mảng và phần tử được bao bọc không. Điều này sẽ trả về một giá trị boolean dựa trên sự bình đẳng.

def in_array?(array, item)
    item = [item] unless item.is_a?(Array)
    item == array & item
end

-1

Tôi luôn thấy thú vị khi chạy một số điểm chuẩn để thấy tốc độ tương đối của các cách khác nhau để làm một cái gì đó.

Tìm một phần tử mảng ở đầu, giữa hoặc cuối sẽ ảnh hưởng đến bất kỳ tìm kiếm tuyến tính nào nhưng hầu như không ảnh hưởng đến tìm kiếm đối với Tập hợp.

Chuyển đổi một mảng thành một tập hợp sẽ gây ra một cú hích trong thời gian xử lý, vì vậy hãy tạo một tập hợp từ một mảng một lần hoặc bắt đầu với một tập hợp ngay từ đầu.

Đây là mã điểm chuẩn:

# frozen_string_literal: true

require 'fruity'
require 'set'

ARRAY = (1..20_000).to_a
SET = ARRAY.to_set

DIVIDER = '-' * 20

def array_include?(elem)
  ARRAY.include?(elem)
end

def array_member?(elem)
  ARRAY.member?(elem)
end

def array_index(elem)
  ARRAY.index(elem) >= 0
end

def array_find_index(elem)
  ARRAY.find_index(elem) >= 0
end

def array_index_each(elem)
  ARRAY.index { |each| each == elem } >= 0
end

def array_find_index_each(elem)
  ARRAY.find_index { |each| each == elem } >= 0
end

def array_any_each(elem)
  ARRAY.any? { |each| each == elem }
end

def array_find_each(elem)
  ARRAY.find { |each| each == elem } != nil
end

def array_detect_each(elem)
  ARRAY.detect { |each| each == elem } != nil
end

def set_include?(elem)
  SET.include?(elem)
end

def set_member?(elem)
  SET.member?(elem)
end

puts format('Ruby v.%s', RUBY_VERSION)

{
  'First' => ARRAY.first,
  'Middle' => (ARRAY.size / 2).to_i,
  'Last' => ARRAY.last
}.each do |k, element|
  puts DIVIDER, k, DIVIDER

  compare do
    _array_include?        { array_include?(element)        }
    _array_member?         { array_member?(element)         }
    _array_index           { array_index(element)           }
    _array_find_index      { array_find_index(element)      }
    _array_index_each      { array_index_each(element)      }
    _array_find_index_each { array_find_index_each(element) }
    _array_any_each        { array_any_each(element)        }
    _array_find_each       { array_find_each(element)       }
    _array_detect_each     { array_detect_each(element)     }
  end
end

puts '', DIVIDER, 'Sets vs. Array.include?', DIVIDER
{
  'First' => ARRAY.first,
  'Middle' => (ARRAY.size / 2).to_i,
  'Last' => ARRAY.last
}.each do |k, element|
  puts DIVIDER, k, DIVIDER

  compare do
    _array_include? { array_include?(element) }
    _set_include?   { set_include?(element)   }
    _set_member?    { set_member?(element)    }
  end
end

Mà, khi chạy trên máy tính xách tay Mac OS của tôi, kết quả là:

Ruby v.2.7.0
--------------------
First
--------------------
Running each test 65536 times. Test will take about 5 seconds.
_array_include? is similar to _array_index
_array_index is similar to _array_find_index
_array_find_index is faster than _array_any_each by 2x ± 1.0
_array_any_each is similar to _array_index_each
_array_index_each is similar to _array_find_index_each
_array_find_index_each is faster than _array_member? by 4x ± 1.0
_array_member? is faster than _array_detect_each by 2x ± 1.0
_array_detect_each is similar to _array_find_each
--------------------
Middle
--------------------
Running each test 32 times. Test will take about 2 seconds.
_array_include? is similar to _array_find_index
_array_find_index is similar to _array_index
_array_index is faster than _array_member? by 2x ± 0.1
_array_member? is faster than _array_index_each by 2x ± 0.1
_array_index_each is similar to _array_find_index_each
_array_find_index_each is similar to _array_any_each
_array_any_each is faster than _array_detect_each by 30.000000000000004% ± 10.0%
_array_detect_each is similar to _array_find_each
--------------------
Last
--------------------
Running each test 16 times. Test will take about 2 seconds.
_array_include? is faster than _array_find_index by 10.000000000000009% ± 10.0%
_array_find_index is similar to _array_index
_array_index is faster than _array_member? by 3x ± 0.1
_array_member? is faster than _array_find_index_each by 2x ± 0.1
_array_find_index_each is similar to _array_index_each
_array_index_each is similar to _array_any_each
_array_any_each is faster than _array_detect_each by 30.000000000000004% ± 10.0%
_array_detect_each is similar to _array_find_each

--------------------
Sets vs. Array.include?
--------------------
--------------------
First
--------------------
Running each test 65536 times. Test will take about 1 second.
_array_include? is similar to _set_include?
_set_include? is similar to _set_member?
--------------------
Middle
--------------------
Running each test 65536 times. Test will take about 2 minutes.
_set_member? is similar to _set_include?
_set_include? is faster than _array_include? by 1400x ± 1000.0
--------------------
Last
--------------------
Running each test 65536 times. Test will take about 4 minutes.
_set_member? is similar to _set_include?
_set_include? is faster than _array_include? by 3000x ± 1000.0

Về cơ bản, kết quả bảo tôi sử dụng Set cho mọi thứ nếu tôi sẽ tìm kiếm để đưa vào trừ khi tôi có thể đảm bảo rằng yếu tố đầu tiên là thứ tôi muốn, điều đó không có khả năng. Có một số chi phí khi chèn các phần tử vào hàm băm, nhưng thời gian tìm kiếm nhanh hơn rất nhiều, tôi không nghĩ đó nên là một sự cân nhắc. Một lần nữa, nếu bạn cần tìm kiếm nó, đừng sử dụng Mảng, hãy sử dụng Set. (Hoặc một Hash.)

Mảng càng nhỏ, các phương thức Array sẽ chạy càng nhanh, nhưng chúng vẫn không theo kịp, mặc dù trong các mảng nhỏ, sự khác biệt có thể rất nhỏ.

"Đầu tiên", "Trung" và "Last" phản ánh việc sử dụng first, size / 2lastcho ARRAYcho các phần tử con người tìm kiếm. Phần tử đó sẽ được sử dụng khi tìm kiếm ARRAYSETcác biến.

Những thay đổi nhỏ đã được thực hiện đối với các phương pháp được so sánh với > 0vì thử nghiệm nên >= 0dành cho indexthử nghiệm loại.

Thông tin thêm về Fruity và phương pháp luận của nó có sẵn trong README của nó .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.