dumb_numb_set 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/dumb_numb_set.rb +127 -0
- metadata +54 -0
@@ -0,0 +1,127 @@
|
|
1
|
+
# ### DumbNumbSet
|
2
|
+
#
|
3
|
+
# A DumbNumbSet is a data structure for a very specific purpose: compactly
|
4
|
+
# storing a set of mostly consecutive positive integers that may begin at
|
5
|
+
# any number.
|
6
|
+
#
|
7
|
+
# In Ruby, a Set is actually stored as a Hash with `true` as values. So the
|
8
|
+
# fairest comparison for DumbNumbSet is the same Hash a Set would use.
|
9
|
+
#
|
10
|
+
# #### Usage
|
11
|
+
#
|
12
|
+
# The API for DumbNumbSet is very simple. Numbers can be added, removed, and
|
13
|
+
# their presence in the set can be queried.
|
14
|
+
#
|
15
|
+
# dns = DumbNumbSet.new
|
16
|
+
# dns.add 1
|
17
|
+
# dns.include? 1 #=> true
|
18
|
+
# dns.remove 1
|
19
|
+
# dns.include? 1 #=> false
|
20
|
+
#
|
21
|
+
# #### Implementation
|
22
|
+
#
|
23
|
+
# DumbNumbSet is backed by a Hash of integers. Each key represents a multiple
|
24
|
+
# of the native integer length, and each value is a bit field. Each bit in the
|
25
|
+
# value represents the presence or absence of an integer.
|
26
|
+
#
|
27
|
+
# #### Performance
|
28
|
+
#
|
29
|
+
# Performance is nearly the same as a Hash, except when inserting many
|
30
|
+
# non-consecutive values.
|
31
|
+
#
|
32
|
+
# #### Size
|
33
|
+
#
|
34
|
+
# For consecutive values, DumbNumbSet is typically ~95% smaller than a Hash when
|
35
|
+
# comparing serialized size with `Marshal.dump`.
|
36
|
+
#
|
37
|
+
class DumbNumbSet
|
38
|
+
def initialize
|
39
|
+
@bitsets = {}
|
40
|
+
|
41
|
+
# Set divisor so that bit-wise operations are always performed
|
42
|
+
# with Fixnums.
|
43
|
+
if 1.size == 4
|
44
|
+
@div = 29
|
45
|
+
else
|
46
|
+
@div = 61
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add a non-negative integer to the set.
|
51
|
+
# Raises an ArgumentError if the number given is not a non-negative integer.
|
52
|
+
def add num
|
53
|
+
check_num num
|
54
|
+
|
55
|
+
index = bitset_index num
|
56
|
+
|
57
|
+
bitset = @bitsets[index]
|
58
|
+
|
59
|
+
if bitset
|
60
|
+
@bitsets[index] = (bitset | bin_index(num))
|
61
|
+
else
|
62
|
+
@bitsets[index] = bin_index(num)
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
alias << add
|
69
|
+
|
70
|
+
# Remove number from set.
|
71
|
+
# Raises an ArgumentError if the number given is not a non-negative integer.
|
72
|
+
def remove num
|
73
|
+
check_num num
|
74
|
+
|
75
|
+
index = bitset_index num
|
76
|
+
|
77
|
+
bitset = @bitsets[index]
|
78
|
+
|
79
|
+
return false unless bitset
|
80
|
+
|
81
|
+
@bitsets[index] = (bitset ^ bin_index(num))
|
82
|
+
|
83
|
+
if @bitsets[index] == 0
|
84
|
+
@bitsets.delete index
|
85
|
+
end
|
86
|
+
|
87
|
+
num
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns true if the given number is in the set.
|
91
|
+
# Raises an ArgumentError if the number given is not a non-negative integer.
|
92
|
+
def include? num
|
93
|
+
check_num num
|
94
|
+
|
95
|
+
index = bitset_index num
|
96
|
+
|
97
|
+
bitset = @bitsets[index]
|
98
|
+
|
99
|
+
return false unless bitset
|
100
|
+
|
101
|
+
bitset & bin_index(num) != 0
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns number of keys in set (not number of values).
|
105
|
+
def size
|
106
|
+
@bitsets.length
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
# Calculate the bitmask to index the given number in the bitset.
|
112
|
+
def bin_index num
|
113
|
+
1 << (num % @div)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Calculate the key of the bitset for a given number.
|
117
|
+
def bitset_index num
|
118
|
+
(num / @div)
|
119
|
+
end
|
120
|
+
|
121
|
+
# Check that the argument is a valid number.
|
122
|
+
def check_num num
|
123
|
+
unless num.is_a? Fixnum and num.integer? and num >= 0
|
124
|
+
raise ArgumentError, "Argument must be positive integer"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
metadata
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dumb_numb_set
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Justin Collins
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-06-13 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! 'Ever needed to compactly store and query a set of mostly consecutive,
|
15
|
+
|
16
|
+
non-negative integers? Probably not, but if you do this library may work for
|
17
|
+
|
18
|
+
you. It''s just about as fast as a Set and a lot smaller for numbers that stay
|
19
|
+
|
20
|
+
close together.
|
21
|
+
|
22
|
+
'
|
23
|
+
email:
|
24
|
+
executables: []
|
25
|
+
extensions: []
|
26
|
+
extra_rdoc_files: []
|
27
|
+
files:
|
28
|
+
- lib/dumb_numb_set.rb
|
29
|
+
homepage: https://github.com/presidentbeef/dumb-numb-set
|
30
|
+
licenses:
|
31
|
+
- MIT
|
32
|
+
post_install_message:
|
33
|
+
rdoc_options: []
|
34
|
+
require_paths:
|
35
|
+
- lib
|
36
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
requirements: []
|
49
|
+
rubyforge_project:
|
50
|
+
rubygems_version: 1.8.25
|
51
|
+
signing_key:
|
52
|
+
specification_version: 3
|
53
|
+
summary: A compact data structure for mostly consecutive, non-negative integers.
|
54
|
+
test_files: []
|