Раздел «Язык Ruby».RandomArrayMixin:

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

Attachment sort Action down Size Date Who Comment
random.rb manage 5.7 K 08 Oct 2007 - 17:12 ArtemVoroztsov