Last active
April 17, 2019 13:57
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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