Module: Predictor

Defined in:
lib/predictor/version.rb,
lib/predictor/distance.rb,
lib/predictor/predictor.rb,
lib/predictor/input_matrix.rb

Defined Under Namespace

Modules: Base, Distance Classes: InputMatrix

Constant Summary collapse

VERSION =
"2.3.1"
PROCESS_ITEMS_LUA_SCRIPT =
"  local redis_prefix = ARGV[1]\n  local input_matrices = cjson.decode(ARGV[2])\n  local similarity_limit = tonumber(ARGV[3])\n  local item = ARGV[4]\n  local keys = {}\n\n  for name, options in pairs(input_matrices) do\n    local key = table.concat({redis_prefix, name, 'sets', item}, ':')\n    local sets = redis.call('SMEMBERS', key)\n    for _, set in ipairs(sets) do\n      table.insert(keys, table.concat({redis_prefix, name, 'items', set}, ':'))\n    end\n  end\n\n  -- Account for empty tables.\n  if next(keys) == nil then\n    return nil\n  end\n\n  local related_items = redis.call('SUNION', unpack(keys))\n\n  local function add_similarity_if_necessary(item, similarity, score)\n    local store = true\n    local key = table.concat({redis_prefix, 'similarities', item}, ':')\n\n    if similarity_limit ~= nil then\n      local zrank = redis.call('ZRANK', key, similarity)\n\n      if zrank ~= nil then\n        local zcard = redis.call('ZCARD', key)\n\n        if zcard >= similarity_limit then\n          -- Similarity is not already stored and we are at limit of similarities.\n\n          local lowest_scored_item = redis.call('ZRANGEBYSCORE', key, '0', '+inf', 'withscores', 'limit', 0, 1)\n\n          if #lowest_scored_item > 0 then\n            -- If score is less than or equal to the lowest score, don't store it. Otherwise, make room by removing the lowest scored similarity\n            if score <= tonumber(lowest_scored_item[2]) then\n              store = false\n            else\n              redis.call('ZREM', key, lowest_scored_item[1])\n            end\n          end\n        end\n      end\n    end\n\n    if store then\n      redis.call('ZADD', key, score, similarity)\n    end\n  end\n\n  for i, related_item in ipairs(related_items) do\n    -- Disregard the current item.\n    if related_item ~= item then\n      local score = 0.0\n\n      for name, matrix in pairs(input_matrices) do\n        local s = 0.0\n\n        local key_1 = table.concat({redis_prefix, name, 'sets', item}, ':')\n        local key_2 = table.concat({redis_prefix, name, 'sets', related_item}, ':')\n\n        if matrix.measure == 'jaccard_index' then\n          local x = tonumber(redis.call('SINTERSTORE', 'temp', key_1, key_2))\n          local y = tonumber(redis.call('SUNIONSTORE', 'temp', key_1, key_2))\n          redis.call('DEL', 'temp')\n\n          if y > 0 then\n            s = s + (x / y)\n          end\n        elseif matrix.measure == 'sorensen_coefficient' then\n          local x = redis.call('SINTERSTORE', 'temp', key_1, key_2)\n          local y = redis.call('SCARD', key_1)\n          local z = redis.call('SCARD', key_2)\n\n          redis.call('DEL', 'temp')\n\n          local denom = y + z\n          if denom > 0 then\n            s = s + (2 * x / denom)\n          end\n        else\n          error(\"Bad matrix.measure: \" .. matrix.measure)\n        end\n\n        score = score + (s * matrix.weight)\n      end\n\n      if score > 0 then\n        add_similarity_if_necessary(item, related_item, score)\n        add_similarity_if_necessary(related_item, item, score)\n      else\n        redis.call('ZREM', table.concat({redis_prefix, 'similarities', item}, ':'), related_item)\n        redis.call('ZREM', table.concat({redis_prefix, 'similarities', related_item}, ':'), item)\n      end\n    end\n  end\n"
@@redis =
nil
@@redis_prefix =
nil

Class Method Summary collapse

Class Method Details

.capitalize(str_or_sym) ⇒ Object



30
31
32
33
# File 'lib/predictor/predictor.rb', line 30

def self.capitalize(str_or_sym)
  str = str_or_sym.to_s.each_char.to_a
  str.first.upcase + str[1..-1].join("").downcase
end

.constantize(klass) ⇒ Object



35
36
37
# File 'lib/predictor/predictor.rb', line 35

def self.constantize(klass)
  Object.module_eval("Predictor::#{klass}", __FILE__, __LINE__)
end

.get_processing_techniqueObject



43
44
45
# File 'lib/predictor/predictor.rb', line 43

def self.get_processing_technique
  @technique || :ruby
end

.get_redis_prefixObject



18
19
20
21
22
23
24
25
26
27
28
# File 'lib/predictor/predictor.rb', line 18

def self.get_redis_prefix
  if @@redis_prefix
    if @@redis_prefix.respond_to?(:call)
      @@redis_prefix.call
    else
      @@redis_prefix
    end
  else
    'predictor'
  end
end

.process_lua_script(*args) ⇒ Object



47
48
49
50
# File 'lib/predictor/predictor.rb', line 47

def self.process_lua_script(*args)
  @process_sha ||= redis.script(:load, PROCESS_ITEMS_LUA_SCRIPT)
  redis.evalsha(@process_sha, argv: args)
end

.processing_technique(algorithm) ⇒ Object



39
40
41
# File 'lib/predictor/predictor.rb', line 39

def self.processing_technique(algorithm)
  @technique = algorithm
end

.redisObject



9
10
11
12
# File 'lib/predictor/predictor.rb', line 9

def self.redis
  return @@redis unless @@redis.nil?
  raise "redis not configured! - Predictor.redis = Redis.new"
end

.redis=(redis) ⇒ Object



5
6
7
# File 'lib/predictor/predictor.rb', line 5

def self.redis=(redis)
  @@redis = redis
end

.redis_prefix(prefix = nil, &block) ⇒ Object



14
15
16
# File 'lib/predictor/predictor.rb', line 14

def self.redis_prefix(prefix = nil, &block)
  @@redis_prefix = block_given? ? block : prefix
end