class ZipCode::FR

TODO: factor index system out rubocop:disable Metrics/ClassLength

Public Class Methods

new() click to toggle source
# File lib/zipcode-fr.rb, line 8
def initialize
  @indexes = {}
end

Public Instance Methods

complete(name, str, key = nil) click to toggle source
# File lib/zipcode-fr.rb, line 180
def complete(name, str, key = nil)
  key ||= name
  search(name, str).map { |e| e[key] }
end
index!(name, data, modes = nil, key: nil) click to toggle source
# File lib/zipcode-fr.rb, line 70
def index!(name, data, modes = nil, key: nil)
  key ||= name
  index = Hash.new { |h, k| h[k] = [] unless h.frozen? }

  modes = [modes] unless modes.is_a?(Enumerable)
  modes.each do |mode|
    data.each(&appender(index, key, mode))
  end

  index.each_value(&:uniq!)
  index.freeze

  @indexes[name] = index
end
load() click to toggle source
# File lib/zipcode-fr.rb, line 12
def load
  # TODO: non-optimal, but not overly long either
  index!(:name, reader, %i[word_prefix match])
  index!(:zip, reader, :prefix)
  @loaded = true
end
memsize_of_index(name) click to toggle source
# File lib/zipcode-fr.rb, line 157
def memsize_of_index(name)
  require 'objspace'
  ObjectSpace.memsize_of(@indexes[name]) +
    @indexes[name].reduce(0) { |a, (_, v)| a + ObjectSpace.memsize_of(v) }
end
ready?() click to toggle source
# File lib/zipcode-fr.rb, line 19
def ready?
  @loaded
end

Private Instance Methods

append_infixes(idx, pos, val, min_size: 1) click to toggle source
# File lib/zipcode-fr.rb, line 141
def append_infixes(idx, pos, val, min_size: 1)
  each_prefix(val, min_size: min_size) do |prefix|
    each_suffix(prefix, min_size: min_size) do |infix|
      idx[infix.hash] << pos
    end
  end
end
append_match(idx, pos, val) click to toggle source
# File lib/zipcode-fr.rb, line 104
def append_match(idx, pos, val)
  idx[val.hash] << pos
end
append_prefixes(idx, pos, val, min_size: 1) click to toggle source
# File lib/zipcode-fr.rb, line 121
def append_prefixes(idx, pos, val, min_size: 1)
  each_prefix(val, min_size: min_size) { |prefix| idx[prefix.hash] << pos }
end
append_word_prefixes(idx, pos, val) click to toggle source
# File lib/zipcode-fr.rb, line 114
def append_word_prefixes(idx, pos, val)
  each_word(val) do |word|
    each_prefix(word) { |prefix| idx[prefix.hash] << pos }
  end
end
append_words(idx, pos, val) click to toggle source
# File lib/zipcode-fr.rb, line 109
def append_words(idx, pos, val)
  each_word(val) { |w| idx[w.hash] << pos }
end
appender(idx, key, mode) click to toggle source

TODO: create an appender registry rubocop:disable Metrics/AbcSize rubocop:disable Metrics/MethodLength

# File lib/zipcode-fr.rb, line 88
def appender(idx, key, mode)
  case mode
  when :prefix
    ->(pos, record) { append_prefixes(idx, pos, record[key]) }
  when :infix
    ->(pos, record) { append_infixes(idx, pos, record[key]) }
  when :word
    ->(pos, record) { append_words(idx, pos, record[key]) }
  when :word_prefix
    ->(pos, record) { append_word_prefixes(idx, pos, record[key]) }
  else
    ->(pos, record) { append_match(idx, pos, record[key]) }
  end
end
clean(row) click to toggle source
# File lib/zipcode-fr.rb, line 55
def clean(row)
  row_to_h(row_clean(row))
end
data_source() click to toggle source
# File lib/zipcode-fr.rb, line 23
def data_source
  path = 'vendor/data/code_postaux_v201703.csv'
  File.expand_path(File.join(File.dirname(__FILE__), '..', path))
end
each_prefix(val, min_size: 1) { |val| ... } click to toggle source
# File lib/zipcode-fr.rb, line 131
def each_prefix(val, min_size: 1)
  min_size.upto(val.length) { |i| yield val[0...i] }
end
each_suffix(val, min_size: 1) { |val| ... } click to toggle source
# File lib/zipcode-fr.rb, line 136
def each_suffix(val, min_size: 1)
  min_size.upto(val.length) { |i| yield val[-i..-1] }
end
each_word(val, &block) click to toggle source
# File lib/zipcode-fr.rb, line 126
def each_word(val, &block)
  val.split.each(&block)
end
index(name) click to toggle source
# File lib/zipcode-fr.rb, line 150
def index(name)
  raise "no index named #{name.inspect}" unless @indexes.key?(name)

  @indexes[name]
end
open() { |csv| ... } click to toggle source
# File lib/zipcode-fr.rb, line 37
def open
  CSV.open(data_source, 'rb', reader_options) do |csv|
    csv.take(1) # skip header manually to preserve tell()
    yield csv
  end
end
read_at(*positions, count: 1) { |clean(row)| ... } click to toggle source
# File lib/zipcode-fr.rb, line 163
def read_at(*positions, count: 1)
  return enum_for(:read_at, *positions, count: count) unless block_given?

  open do |io|
    positions.each do |pos|
      io.seek(pos)
      io.take(count).each { |row| yield clean(row) }
    end
  end
end
reader() { |pos, clean(row)| ... } click to toggle source
# File lib/zipcode-fr.rb, line 45
def reader
  return enum_for(:reader) unless block_given?

  open do |io|
    pos = io.tell
    io.each { |row| yield(pos, clean(row)); pos = io.tell }
  end
end
reader_options() click to toggle source
# File lib/zipcode-fr.rb, line 29
def reader_options
  {
    col_sep: ';',
    encoding: 'ISO-8859-1',
  }
end
row_clean(row) click to toggle source
# File lib/zipcode-fr.rb, line 60
def row_clean(row)
  row.map { |e| e.strip.encode('UTF-8') }
end
row_to_h(row) click to toggle source
# File lib/zipcode-fr.rb, line 65
def row_to_h(row)
  %i[insee name zip alt_name].zip(row).to_h
end