RandomArray Mixin
Метод random_array для Array, Set и Hash
Примеси некоторая альтернатива наследованию.
Это ещё одна техника "реюза" (code reuse).
Примеси позволяют, в частности, определить один и тот же метод в разных классах,
используя один и тот же код.
Примесь предполагает, что класс, к которому её примешивают, имеет некоторые методы,
и предоставляет новые методы, использующие эти методы.
Приведённая ниже примесь предоставляет метод
random_array(n)
.
Она "предполагает" , что класс, к которому её примешивают, имеет instance-методы
size
и
each
.
module RandomArray
def random_array(n)
res = []
return self.dup.to_a if n > size
return [] if n == 0
to_take = n
rest = size
self.each {|x|
if rand(rest) > to_take
# skip
else
#take
res << x
to_take -= 1
break if to_take <= 0
end
rest -= 1
}
res
end
end
require 'set'
class Set
include RandomArray
end
class Array
include RandomArray
end
class Hash
include RandomArray
end
Проверим, что все работает как надо:
require 'test/unit'
class TestRandomArray < Test::Unit::TestCase
def test_me
(10..200).each {|i|
a = Set.new((1..i).to_a)
k = rand i
res = a.random_array(k)
assert_equal( res.size, k)
assert( res.all? {|x| a.include?(x)} )
}
end
end
Обратите внимание, как эта примесь работает в случае с Hash:
[1,2,3,4,5,6,7,8,9,10].random_array(3) # => [1, 3, 6]
require 'set'
Set[1,2,3,4,5,6,7,8,9,10].random_array(3) # => [5, 6, 3]
{1=>2,3=>4,5=>6,7=>8,9=>10}.random_array(2) # => [[7, 8], [3, 4]]
Ниже приведена более продвинутая версия, которая возвращает не массив, а объект того же типа:
module RandomArray
def random
random_part(1).to_a[0]
end
def random_part(n)
res = self.class.new
return self.dup if n > size
return res if n == 0
to_take = n
rest = size
if self.is_a?(Hash)
# singleton-метод метод отдельного выделенного объекта
# его собственный метод, которым не обладают другие объекты
# даже объекты того же класса; его личный метод
def res.<<(x)
self[x[0]] = x[1]
end
end
self.each {|x|
if rand(rest) > to_take
# skip
else
#take
res << x
to_take -= 1
break if to_take <= 0
end
rest -= 1
}
res
end
end
[1,2,3,4,5,6,7,8,9,10].random_part(3) # => #<Array [2, 7, 8]>
require 'set'
Set[1,2,3,4,5,6,7,8,9,10].random_part(3) # => #<Set {2, 3, 7}>
{1=>2,3=>4,5=>6,7=>8,9=>10}.random_part(2) # => #<Hash {3=>4, 5=>6}>
Класс возвращаемого объекта в данном случае тот же, что и у исходного объекта.
Примешивание методов класса
Покажем на примере функции
random
, как можно с помощью примеси создавать
методы класса
module RandomArray
def RandomArray.included(klass)
def klass.random(x,*args)
res = self.new
n.times { res << rand(*args) }
res
end
end
end
Теперь, чтобы сгенерировать случайный Set или Array из 10 элементов с элементами из промежутка (100..200) мы можем писать:
s = Set.random(10, (100..200))
a = Array.random(10, (100..200))
И последний штрих:
module RandomArray
def RandomArray.included(klass)
unless klass == Hash
def klass.random(x, *args, &block)
block ||= lambda { true }
res = self.new
while res.size < n
x = rand(*args)
res << x if block[x]
end
res
end
end
end
end
Что позволяет нам писать:
s2 = Set.random(100, (0..1000) ) {|x| x % 5 != 0}
для генерации слуяайного массива чисел, некратных 5.
Обратите внимание на то, что дописывание методов в модуль после того, как он уже был включён (included, "примешан")
в классы, классы тем не менее "чувствуют", и в этих классах появляются новые методы.
--
ArtemVoroztsov - 20 Sep 2007