Skip to content

Instantly share code, notes, and snippets.

@davidbalbert
Last active April 17, 2019 13:57
Show Gist options
  • Save davidbalbert/8592135 to your computer and use it in GitHub Desktop.
Save davidbalbert/8592135 to your computer and use it in GitHub Desktop.
A reimplementation of Ruby's Hash in Ruby. It hash all the methods I felt like implementing. It does not preserve insertion order. It is untested.
class Array
def to_my_h
MyHash[self]
end
end
class Hash
def to_my_h
to_a.to_my_h
end
end
class NilClass
def to_my_h
MyHash.new
end
end
class MyHash
class KeyError < StandardError; end
attr_reader :buckets, :size
private :buckets
alias length size
alias count size
RESIZE_THRESHOLD = 0.75
include Enumerable
def self.[](pairs)
if pairs.is_a?(self.class) || pairs.is_a?(Hash)
return pairs.to_my_h
end
h = new
pairs.each do |k, v|
h[k] = v
end
h
end
def initialize
@buckets = Array.new(5) { [] }
@size = 0
end
def fetch(key, *rest)
pair = pair_for_key(key)
if pair
pair[1]
elsif block_given?
yield(key)
elsif rest.size > 0
rest[0]
else
raise KeyError, "key not found #{key.inspect}"
end
end
def [](key)
pair = pair_for_key(key)
if pair
pair[1]
else
nil
end
end
def []=(key, value)
if (size + 1).to_f / buckets.size > RESIZE_THRESHOLD
resize!
end
@size += 1
if key.is_a?(String)
key = key.dup.freeze
end
add(key, value)
value
end
alias store []=
def has_key?(key)
!pair_for_key(key).nil?
end
alias include? has_key?
alias key? has_key?
alias member? has_key?
def key(value)
pair = pair_for_value(value)
if pair
pair[0]
else
nil
end
end
def has_value?(value)
!pair_for_value(value).nil?
end
alias value? has_value?
def delete(key)
if include?(key)
i = index(key)
value = self[key]
buckets[i].delete([key, value])
@size -= 1
value
else
nil
end
end
def each
return to_enum unless block_given?
buckets.each do |b|
b.each do |pair|
yield pair
end
end
self
end
alias each_pair each
def select!
return to_enum(:select!) unless block_given?
changes_made = false
each do |k, v|
unless yield(k, v)
delete(k)
changes_made = true
end
end
if changes_made
self
else
nil
end
end
def keep_if(&block)
return to_enum(:keep_if) unless block_given?
select!(&block)
self
end
def select(&block)
return to_enum(:select) unless block_given?
dup.keep_if(&block)
end
def values_at(key, *keys)
all_keys = [key] + keys
select { |k, _| all_keys.include?(k) }.values
end
def reject!
return to_enum(:reject!) unless block_given?
changes_made = false
each do |k, v|
if yield(k, v)
delete(k)
changes_made = true
end
end
if changes_made
self
else
nil
end
end
def delete_if(&block)
return to_enum(:delete_if) unless block_given?
reject!(&block)
self
end
def reject(&block)
return to_enum(:reject) unless block_given?
dup.delete_if(&block)
end
def dup
duped = self.class.new
each { |k, v| duped[k] = v }
duped
end
def keys
map { |k, _| k }
end
def each_key(&block)
return to_enum(:each_key) unless block_given?
keys.each(&block)
end
def values
map { |_, v| v }
end
def each_value(&block)
return to_enum(:each_value) unless block_given?
values.each(&block)
end
def empty?
size == 0
end
def ==(other)
return false unless size == other.size
each_key do |k|
return false unless self[k] == other[k]
end
true
end
alias eql? ==
def clear
@buckets = Array.new(5) { [] }
@size = 0
self
end
def invert
inverted = self.class.new
each do |k, v|
inverted[v] = k
end
inverted
end
def merge!(other)
other.each do |k, v|
if has_key?(k) && block_given?
self[k] = yield(k, self[k], v)
else
self[k] = v
end
end
self
end
alias update merge!
def merge(other, &block)
dup.merge!(other, &block)
end
def replace(other)
clear
other.each do |k, v|
self[k] = v
end
self
end
def shift
if empty?
nil
else
@size -= 1
b = buckets.find { |b| !b.empty? }
b.shift
end
end
def assoc(key)
each do |k, v|
return [k, v] if k == key
end
nil
end
def rassoc(key)
pair_for_value(key)
end
def flatten(level = 1)
to_a.flatten(level)
end
def to_a
buckets.flat_map { |b| b }
end
def to_h
h = {}
each {|k, v| h[k] = v }
h
end
def to_my_h
if instance_of?(MyHash)
self
else
h = MyHash.new
h.replace(self)
h
end
end
def to_my_hash
self
end
def to_s
s = "{"
s += map { |k, v| "#{k.inspect}=>#{v.inspect}" }.join(", ")
s += "}"
end
def inspect
"#<#{self.class} #{to_s}>"
end
private
def resize!
old_buckets = buckets
@buckets = Array.new(buckets.size * 2) { [] }
old_buckets.each do |b|
b.each do |k, v|
add(k, v)
end
end
end
def add(key, value)
pair = pair_for_key(key)
if pair
pair[1] = value
else
i = index(key)
buckets[i] = buckets[i] + [[key, value]]
end
end
def pair_for_key(key)
buckets[index(key)].find { |k, _| k.eql?(key) }
end
def pair_for_value(value)
buckets.each do |b|
pair = b.find { |_, v| v === value }
return pair if pair
end
nil
end
def index(key)
key.hash % buckets.size
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment