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ề true
giá trị ish nếu phần tử có mặt.
include?
là phương pháp ưa thích. Nó sử dụng for
vò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_equal
chứ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 Array
lớp và sử dụng một triển khai không được tối ưu hóa từ Enumerable
mô đ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?
và 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. Set
khô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ở @hash
là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 true
và 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
...