Многоиндексный контейнер для рубина - PullRequest
3 голосов
/ 31 августа 2010

Есть ли что-то вроде boost :: multi_index, кроме рубина.В основном, взяв некоторый контейнер объектов и проиндексировав его N различными способами с N различными методами запросов.

Я думаю, вы могли бы использовать DataMapper с SQLite в базе данных памяти, но мне было интересно, есть ли что-нибудь чистое рубин вокруг.1003 *

Ниже представлен пример того, что может делать этот тип класса.Это очень похоже на базу данных.

class Foo
    attr_accessor :a
    attr_accessor :b
    attr_accessor :c
end


class FooIndexer < MultiIndex
    hash_index :a do |o|
        o.a
    end

    ordered_index :b do |x, y|
        x.b <=> y.b
    end
end


index = FooIndexer.new

index.insert( Foo.new ( ... ))
index.insert( Foo.new ( ... ))
index.insert( Foo.new ( ... ))
index.insert( Foo.new ( ... ))
index.insert( Foo.new ( ... ))


index.find ( index.a == 10 )
index.find ( index.b > 10  )

Ответы [ 2 ]

1 голос
/ 01 сентября 2010

Это полностью проработанное решение, включая спецификацию, но только для несколько ключей хеша.

require 'pp'

class MKey
  def initialize &bk
    @block = bk
    @containers = {}
  end

  def <<(val)
    keys = @block.call(val)
    keys.each do |k,v|
      @containers[k] ||= {}
      @containers[k][v] = val
    end
  end

  def [](key)
    k, v = key.first
    @containers[k][v]
  end

  def delete(key)
    val = self[key]
    keys = @block.call(val)
    keys.each do |k,v|
      @containers[k].delete(v)
    end
  end

  include Enumerable

  def each
    k, c = @containers.first 
    c.each do |k, val|
      yield val
    end
  end

end


describe MKey do 

  class Foo
    def initialize(a,b)
      @a = a
      @b = b
    end
    attr_accessor :a
    attr_accessor :b
  end

  it "should insert" do

    index = MKey.new do |o|
      { :a => o.a,
        :b => o.b
      }
    end

    x = Foo.new("hello", "cat")
    y = Foo.new("goodbye", "code")

    index << x
    index << y

    # Test Enumerable interface
    index.find do |val|
      val.a == "hello"
    end.should == x

    # Test multi key interface
    index[:a => "hello"].should == x
    index[:b => "code"].should == y

    index.delete(:a => "hello")

    index[:a => "hello"].should == nil
    index[:b => "code"].should == y

    index.delete(:b => "code")

    index[:a => "hello"].should == nil
    index[:b => "code"].should == nil


  end

  it "hash lookup should be faster than find" do


    index = MKey.new do |o|
      { :a => o.a,
        :b => o.b
      }
    end

    for i in 1..10000
      index << Foo.new(i, i*100)
    end

    t0 = timer do
      index[:a => 1000]
    end

    t1 = timer do
      index.find {|v| v.a == 10000}
    end

    t0.should < t1 * 100 

  end

end
0 голосов
/ 01 сентября 2010

Похоже, вам нужен конкретный способ реализации этой функции. Но с точки зрения интерфейса в стиле ruby ​​, я бы рекомендовал использовать метод Enumerable#find. Таким образом, вы можете сказать

foo_container = [FooIndexer.new, ...]
foo_container.find{|x| x.a == 10}

, что очень похоже на ваш пример, за исключением скобок вместо скобок!

Позже, если вы обнаружите, что производительность очень плохая, возможно, вы захотите кэшировать или оптимизировать ее find. Но, основываясь только на вашем вопросе, если вы посмотрите на это сейчас, вы будете оптимизировать слишком рано.

Enumerable уже предоставляет множество таких вещей, поэтому у вас есть естественные расширения, такие как

foo_container.select{|x| x.a == 10}  # Finds all instances.
foo_container.reject{|x| x.a == 10}  # Finds the complementary set.
...