Ruby tiene once métodos para encontrar elementos en una matriz.
El preferido es include?
o, para acceso repetido, crear un Set y luego llamar include?
o member?
.
Aquí están todos ellos:
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
Todos devuelven un true
valor ish si el elemento está presente.
include?
Es el método preferido. Utiliza un for
bucle de lenguaje C internamente que se rompe cuando un elemento coincide con las rb_equal_opt/rb_equal
funciones internas . No puede ser mucho más eficiente a menos que cree un Conjunto para verificaciones de membresía repetidas.
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?
no se redefine en la Array
clase y utiliza una implementación no optimizada del Enumerable
módulo que literalmente enumera a través de todos los elementos:
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;
}
Traducido al código Ruby, esto hace lo siguiente:
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
Ambos include?
ymember?
tienen complejidad O (n) tiempo desde que el tanto buscar la matriz para la primera aparición del valor esperado.
Podemos usar un Set para obtener el tiempo de acceso O (1) a costa de tener que crear primero una representación Hash de la matriz. Si verifica repetidamente la membresía en la misma matriz, esta inversión inicial puede pagar rápidamente. Set
no está implementado en C pero como clase simple de Ruby, sigue siendo el tiempo de acceso O (1) del subyacente@hash
hace que valga la pena.
Aquí está la implementación de la clase 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
Como puede ver, la clase Set solo crea una @hash
instancia interna , asigna todos los objetos true
y luego verifica la membresía usandoHash#include?
que se implementa con el tiempo de acceso O (1) en la clase Hash.
No discutiré los otros siete métodos, ya que todos son menos eficientes.
En realidad, hay incluso más métodos con complejidad O (n) más allá de los 11 enumerados anteriormente, pero decidí no enumerarlos ya que escanean toda la matriz en lugar de romper en la primera coincidencia.
No uses estos:
# bad examples
array.grep(element).any?
array.select { |each| each == element }.size > 0
...