hashtable 0.1.3 → 0.1.4
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.
- checksums.yaml +4 -4
- data/README.md +39 -126
- data/lib/hashtable/hash_table.rb +130 -76
- data/lib/hashtable/sparse_bit_array.rb +46 -26
- data/lib/hashtable/traits.rb +37 -56
- data/lib/hashtable/version.rb +1 -1
- data/lib/hashtable.rb +2 -2
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 9a2d080d4eda5fb41e97c4661131fe7b3124530d4cb3604e907ca1294b38fe20
         | 
| 4 | 
            +
              data.tar.gz: 456c01089c6b9764016386137ef4be6ac247fbef83921861d803659c798d9dec
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 89feb259b63fc1795ff796a0cc434dfe78108aad338a756be4e331034103c2ca3096f617130e4d1d85a24ec7921a7f8d118a08e512b3eae9cd7b4cef8728e690
         | 
| 7 | 
            +
              data.tar.gz: a442d8711dcfa1db95600bd48166e35b12f5612f3b94fbe80d08b10a3ea00aaff49002e0f238eaa1273b456a170f9ee8722b9eb79b03934d686271c71ff32274
         | 
    
        data/README.md
    CHANGED
    
    | @@ -52,116 +52,57 @@ end | |
| 52 52 | 
             
            Store numbers:
         | 
| 53 53 |  | 
| 54 54 | 
             
            ```ruby
         | 
| 55 | 
            -
            class Traits
         | 
| 56 | 
            -
              def hash_lookup_key(key)
         | 
| 57 | 
            -
                key
         | 
| 58 | 
            -
              end
         | 
| 59 | 
            -
             | 
| 60 | 
            -
              def lookup_key_to_storage_key(key)
         | 
| 61 | 
            -
                key
         | 
| 62 | 
            -
              end
         | 
| 63 | 
            -
             | 
| 64 | 
            -
              def storage_key_to_lookup_key(key)
         | 
| 65 | 
            -
                key
         | 
| 66 | 
            -
              end
         | 
| 67 | 
            -
            end
         | 
| 68 | 
            -
             | 
| 69 55 | 
             
            table = HashTable::HashTable.new(2)
         | 
| 70 | 
            -
             | 
| 71 | 
            -
            table.set( | 
| 72 | 
            -
            table.set( | 
| 73 | 
            -
            table.set( | 
| 74 | 
            -
            table.set( | 
| 75 | 
            -
            table.set( | 
| 76 | 
            -
            table. | 
| 56 | 
            +
            table.set(3, 7, HashTable::IdentityHashTraits.new)
         | 
| 57 | 
            +
            table.set(4, 5, HashTable::IdentityHashTraits.new)
         | 
| 58 | 
            +
            table.set(5, 6, HashTable::IdentityHashTraits.new)
         | 
| 59 | 
            +
            table.set(6, 7, HashTable::IdentityHashTraits.new)
         | 
| 60 | 
            +
            table.set(8, 9, HashTable::IdentityHashTraits.new)
         | 
| 61 | 
            +
            table.set(9, 19, HashTable::IdentityHashTraits.new)
         | 
| 62 | 
            +
            expect(table.size).to eq(6)
         | 
| 63 | 
            +
            expect(table.get(3, HashTable::IdentityHashTraits.new)).to eq(7)
         | 
| 64 | 
            +
            expect(table.get(4, HashTable::IdentityHashTraits.new)).to eq(5)
         | 
| 65 | 
            +
            expect(table.get(5, HashTable::IdentityHashTraits.new)).to eq(6)
         | 
| 66 | 
            +
            expect(table.get(6, HashTable::IdentityHashTraits.new)).to eq(7)
         | 
| 67 | 
            +
            expect(table.get(8, HashTable::IdentityHashTraits.new)).to eq(9)
         | 
| 68 | 
            +
            expect(table.get(9, HashTable::IdentityHashTraits.new)).to eq(19)
         | 
| 69 | 
            +
            expect(table.capacity).to eq(16)
         | 
| 77 70 | 
             
            ```
         | 
| 78 71 |  | 
| 79 72 | 
             
            Store strings:
         | 
| 80 73 |  | 
| 81 74 | 
             
            ```ruby
         | 
| 82 | 
            -
            class StringTraits
         | 
| 83 | 
            -
              attr_reader :string_table, :string_index
         | 
| 84 | 
            -
             | 
| 85 | 
            -
              def initialize
         | 
| 86 | 
            -
                @string_table = "\0"
         | 
| 87 | 
            -
                @string_index = 1
         | 
| 88 | 
            -
              end
         | 
| 89 | 
            -
             | 
| 90 | 
            -
              def hash_lookup_key(key)
         | 
| 91 | 
            -
                result = 0
         | 
| 92 | 
            -
                key.each_byte { |byte| result += byte * 13 }
         | 
| 93 | 
            -
                result
         | 
| 94 | 
            -
              end
         | 
| 95 | 
            -
             | 
| 96 | 
            -
              def lookup_key_to_storage_key(key)
         | 
| 97 | 
            -
                @string_table += "#{key}\0"
         | 
| 98 | 
            -
                old_si = @string_index
         | 
| 99 | 
            -
                @string_index += key.length + 1
         | 
| 100 | 
            -
                old_si
         | 
| 101 | 
            -
              end
         | 
| 102 | 
            -
             | 
| 103 | 
            -
              def storage_key_to_lookup_key(offset)
         | 
| 104 | 
            -
                @string_table[offset..-1][/[^\0]+/]
         | 
| 105 | 
            -
              end
         | 
| 106 | 
            -
            end
         | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 109 75 | 
             
            table = HashTable::HashTable.new(2)
         | 
| 110 | 
            -
            traits =  | 
| 76 | 
            +
            traits = HashTable::StringIdentityHashTraits.new
         | 
| 111 77 | 
             
            table.set('ViewController64.h', 'ViewController64.h', traits)
         | 
| 112 78 | 
             
            table.set('ViewController65.h', 'ViewController65.h', traits)
         | 
| 113 79 | 
             
            table.set('ViewController66.h', 'ViewController66.h', traits)
         | 
| 114 80 | 
             
            table.set('ViewController67.h', 'ViewController67.h', traits)
         | 
| 115 81 | 
             
            table.set('ViewController68.h', 'ViewController68.h', traits)
         | 
| 116 82 | 
             
            table.set('ViewController69.h', 'ViewController69.h', traits)
         | 
| 117 | 
            -
             | 
| 118 | 
            -
            table.get('ViewController64.h', traits)
         | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 83 | 
            +
            expect(table.size).to eq(6)
         | 
| 84 | 
            +
            expect(table.get('ViewController64.h', traits)).to eq('ViewController64.h')
         | 
| 85 | 
            +
            expect(table.get('ViewController65.h', traits)).to eq('ViewController65.h')
         | 
| 86 | 
            +
            expect(table.get('ViewController66.h', traits)).to eq('ViewController66.h')
         | 
| 87 | 
            +
            expect(table.get('ViewController67.h', traits)).to eq('ViewController67.h')
         | 
| 88 | 
            +
            expect(table.get('ViewController68.h', traits)).to eq('ViewController68.h')
         | 
| 89 | 
            +
            expect(table.get('ViewController69.h', traits)).to eq('ViewController69.h')
         | 
| 90 | 
            +
            expect(table.capacity).to eq(16)
         | 
| 91 | 
            +
            expect(traits.string_table).to eq("\u0000ViewController64.h\u0000ViewController65.h\u0000ViewController66.h\u0000ViewController67.h\u0000ViewController68.h\u0000ViewController69.h\u0000")
         | 
| 121 92 | 
             
            ```
         | 
| 122 93 |  | 
| 123 94 | 
             
            Store just key string:
         | 
| 124 95 |  | 
| 125 96 | 
             
            ```ruby
         | 
| 126 | 
            -
             | 
| 127 | 
            -
             | 
| 128 | 
            -
             | 
| 129 | 
            -
              def initialize
         | 
| 130 | 
            -
                @string_table = "\0"
         | 
| 131 | 
            -
                @string_index = 1
         | 
| 132 | 
            -
              end
         | 
| 133 | 
            -
             | 
| 134 | 
            -
              def hash_lookup_key(key)
         | 
| 135 | 
            -
                result = 0
         | 
| 136 | 
            -
                key.each_byte { |byte| result += byte * 13 }
         | 
| 137 | 
            -
                result
         | 
| 138 | 
            -
              end
         | 
| 139 | 
            -
             | 
| 140 | 
            -
              def lookup_key_to_storage_key(key)
         | 
| 141 | 
            -
                @string_table += "#{key}\0"
         | 
| 142 | 
            -
                old_si = @string_index
         | 
| 143 | 
            -
                @string_index += key.length + 1
         | 
| 144 | 
            -
                old_si
         | 
| 145 | 
            -
              end
         | 
| 146 | 
            -
             | 
| 147 | 
            -
              def storage_key_to_lookup_key(offset)
         | 
| 148 | 
            -
                @string_table[offset..-1][/[^\0]+/]
         | 
| 149 | 
            -
              end
         | 
| 150 | 
            -
            end
         | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
            table = HashTable::HashTable.new(2)
         | 
| 154 | 
            -
            traits = StringTraits.new
         | 
| 155 | 
            -
             | 
| 97 | 
            +
            table = HashTable::HashTable.new
         | 
| 98 | 
            +
            traits = HashTable::StringIdentityHashTraits.new
         | 
| 156 99 | 
             
            (0..31).each do |i|
         | 
| 157 | 
            -
             | 
| 100 | 
            +
              table.add("ViewController#{i}.h", traits)
         | 
| 158 101 | 
             
            end
         | 
| 159 | 
            -
            # | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
            # 32
         | 
| 164 | 
            -
            p table.num_entries
         | 
| 102 | 
            +
            p "#{table.size}---#{table.capacity}----#{table.num_entries}"
         | 
| 103 | 
            +
            expect(table.size).to eq(32)
         | 
| 104 | 
            +
            expect(table.capacity).to eq(64)
         | 
| 105 | 
            +
            expect(table.num_entries).to eq(32)
         | 
| 165 106 | 
             
            ```
         | 
| 166 107 |  | 
| 167 108 | 
             
            Store strings and expand capacity:
         | 
| @@ -171,44 +112,16 @@ Store strings and expand capacity: | |
| 171 112 | 
             
            - capacity is power_of_two
         | 
| 172 113 |  | 
| 173 114 | 
             
            ```ruby
         | 
| 174 | 
            -
             | 
| 175 | 
            -
             | 
| 176 | 
            -
             | 
| 177 | 
            -
              def initialize
         | 
| 178 | 
            -
                @string_table = "\0"
         | 
| 179 | 
            -
                @string_index = 1
         | 
| 180 | 
            -
              end
         | 
| 181 | 
            -
             | 
| 182 | 
            -
              def hash_lookup_key(key)
         | 
| 183 | 
            -
                result = 0
         | 
| 184 | 
            -
                key.each_byte { |byte| result += byte * 13 }
         | 
| 185 | 
            -
                result
         | 
| 186 | 
            -
              end
         | 
| 187 | 
            -
             | 
| 188 | 
            -
              def lookup_key_to_storage_key(key)
         | 
| 189 | 
            -
                @string_table += "#{key}\0"
         | 
| 190 | 
            -
                old_si = @string_index
         | 
| 191 | 
            -
                @string_index += key.length + 1
         | 
| 192 | 
            -
                old_si
         | 
| 193 | 
            -
              end
         | 
| 194 | 
            -
             | 
| 195 | 
            -
              def storage_key_to_lookup_key(offset)
         | 
| 196 | 
            -
                @string_table[offset..-1][/[^\0]+/]
         | 
| 197 | 
            -
              end
         | 
| 198 | 
            -
            end
         | 
| 199 | 
            -
             | 
| 200 | 
            -
            table = HashTable::HashTable.new(8192, expand: true)
         | 
| 201 | 
            -
            traits = StringTraits.new
         | 
| 115 | 
            +
            table = HashTable::HashTable.new(1364, expand: true)
         | 
| 116 | 
            +
            traits = HashTable::StringHashTraits.new
         | 
| 202 117 | 
             
            buckets = (0..1363).map do |i|
         | 
| 203 | 
            -
             | 
| 204 | 
            -
             | 
| 118 | 
            +
            table.set("ViewController#{i}.h",
         | 
| 119 | 
            +
                        ["/Users/ws/Desktop/llvm/TestAndTestApp/TestAndTestApp/Group/h2/#{i}", "ViewController#{i}.h"], traits)
         | 
| 205 120 | 
             
            end
         | 
| 206 | 
            -
            # | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
            # 5461
         | 
| 211 | 
            -
            p table.num_entries
         | 
| 121 | 
            +
            p "#{table.size}---#{table.capacity}----#{table.num_entries}"
         | 
| 122 | 
            +
            expect(table.size).to eq(1364)
         | 
| 123 | 
            +
            expect(table.capacity).to eq(8192)
         | 
| 124 | 
            +
            expect(table.num_entries).to eq(4097)
         | 
| 212 125 | 
             
            ```
         | 
| 213 126 |  | 
| 214 127 | 
             
            ## Contributing
         | 
    
        data/lib/hashtable/hash_table.rb
    CHANGED
    
    | @@ -1,24 +1,91 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 |  | 
| 3 | 
            +
            require 'benchmark'
         | 
| 4 | 
            +
             | 
| 3 5 | 
             
            # module HashTable
         | 
| 4 6 | 
             
            module HashTable
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              EXPAND_KEY_COUNT_CONST = {
         | 
| 9 | 
            +
                8 => [0, 0],
         | 
| 10 | 
            +
                16 => [6, 9],
         | 
| 11 | 
            +
                32 => [8, 17],
         | 
| 12 | 
            +
                64 => [13, 33],
         | 
| 13 | 
            +
                128 => [23, 65],
         | 
| 14 | 
            +
                256 => [44, 129],
         | 
| 15 | 
            +
                512 => [86, 257],
         | 
| 16 | 
            +
                1024 => [171, 513],
         | 
| 17 | 
            +
                2048 => [341, 1025],
         | 
| 18 | 
            +
                4096 => [682, 2049],
         | 
| 19 | 
            +
                8192 => [1364, 4097],
         | 
| 20 | 
            +
                16_384 => [2729, 8193],
         | 
| 21 | 
            +
                32_768 => [5459, 16_385],
         | 
| 22 | 
            +
                65_536 => [10_920, 32_769],
         | 
| 23 | 
            +
                131_072 => [21_842, 65_537],
         | 
| 24 | 
            +
                262_144 => [43_687, 131_073],
         | 
| 25 | 
            +
                524_288 => [87_377, 262_145],
         | 
| 26 | 
            +
                1_048_576 => [174_758, 524_289],
         | 
| 27 | 
            +
                2_097_152 => [349_520, 1_048_577],
         | 
| 28 | 
            +
                4_194_304 => [699_045, 2_097_153],
         | 
| 29 | 
            +
                8_388_608 => [1_398_095, 4_194_305],
         | 
| 30 | 
            +
                16_777_216 => [2_796_196, 8_388_609],
         | 
| 31 | 
            +
                33_554_432 => [5_592_398, 16_777_217],
         | 
| 32 | 
            +
                67_108_864 => [11_184_803, 33_554_433],
         | 
| 33 | 
            +
                134_217_728 => [22_369_613, 67_108_865],
         | 
| 34 | 
            +
                268_435_456 => [44_739_234, 134_217_729],
         | 
| 35 | 
            +
                536_870_912 => [89_478_476, 268_435_457],
         | 
| 36 | 
            +
                1_073_741_824 => [178_956_961, 536_870_913],
         | 
| 37 | 
            +
                2_147_483_648 => [357_913_931, 1_073_741_825],
         | 
| 38 | 
            +
                4_294_967_296 => [715_827_872, 2_147_483_649],
         | 
| 39 | 
            +
                8_589_934_592 => [1_431_655_754, 4_294_967_297],
         | 
| 40 | 
            +
                17_179_869_184 => [2_863_311_519, 8_589_934_593],
         | 
| 41 | 
            +
                34_359_738_368 => [5_726_623_049, 17_179_869_185],
         | 
| 42 | 
            +
                68_719_476_736 => [11_453_246_110, 34_359_738_369],
         | 
| 43 | 
            +
                137_438_953_472 => [22_906_492_232, 68_719_476_737],
         | 
| 44 | 
            +
                274_877_906_944 => [45_812_984_477, 137_438_953_473],
         | 
| 45 | 
            +
                549_755_813_888 => [91_625_968_967, 274_877_906_945],
         | 
| 46 | 
            +
                1_099_511_627_776 => [183_251_937_948, 549_755_813_889]
         | 
| 47 | 
            +
              }.freeze
         | 
| 5 48 | 
             
              # HashTable
         | 
| 6 49 | 
             
              # @abstract
         | 
| 7 50 | 
             
              class HashTable
         | 
| 8 | 
            -
                 | 
| 9 | 
            -
             | 
| 51 | 
            +
                attr_reader :size
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                # @return [HashTable]
         | 
| 54 | 
            +
                # @param  [object] p_value placeholder value with @buckets_v
         | 
| 55 | 
            +
                # @param  [Bool] Does it need to be expanded
         | 
| 56 | 
            +
                def self.new_from_vlaue_placeholder(count = 0, p_value = nil, expand: false)
         | 
| 57 | 
            +
                  new(count, p_value: p_value, expand: expand)
         | 
| 58 | 
            +
                end
         | 
| 10 59 |  | 
| 11 | 
            -
                 | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 60 | 
            +
                # @return [HashTable]
         | 
| 61 | 
            +
                # @param  [Integer] count of entries
         | 
| 62 | 
            +
                # @param  [object] p_key placeholder key with @buckets_k
         | 
| 63 | 
            +
                # @param  [object] p_value placeholder value with @buckets_v
         | 
| 64 | 
            +
                # @param  [Bool] Does it need to be expanded
         | 
| 65 | 
            +
                def initialize(count = 0, p_key: nil, p_value: nil, expand: false)
         | 
| 66 | 
            +
                  capacity = calculate_count(count, expand: expand)
         | 
| 67 | 
            +
                  @buckets_k = Array.new(capacity, p_key)
         | 
| 68 | 
            +
                  @buckets_v = Array.new(capacity, p_value)
         | 
| 14 69 | 
             
                  @present = SparseBitArray.new
         | 
| 15 70 | 
             
                  @deleted = SparseBitArray.new
         | 
| 16 71 | 
             
                  @expand = expand
         | 
| 17 | 
            -
                  @ | 
| 72 | 
            +
                  @size = 0
         | 
| 18 73 | 
             
                end
         | 
| 19 74 |  | 
| 20 | 
            -
                def  | 
| 21 | 
            -
                   | 
| 75 | 
            +
                def num_entries
         | 
| 76 | 
            +
                  raise 'count must less than 2_863_311_519' if size > 2_863_311_519
         | 
| 77 | 
            +
                  return size unless @expand
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                  es = EXPAND_KEY_COUNT_CONST[capacity]
         | 
| 80 | 
            +
                  size - es[0] + es[1]
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                def keys
         | 
| 84 | 
            +
                  @buckets_k
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                def values
         | 
| 88 | 
            +
                  @buckets_v
         | 
| 22 89 | 
             
                end
         | 
| 23 90 |  | 
| 24 91 | 
             
                def empty?
         | 
| @@ -26,36 +93,27 @@ module HashTable | |
| 26 93 | 
             
                end
         | 
| 27 94 |  | 
| 28 95 | 
             
                def capacity
         | 
| 29 | 
            -
                  @ | 
| 96 | 
            +
                  @buckets_k.length
         | 
| 30 97 | 
             
                end
         | 
| 31 98 |  | 
| 32 99 | 
             
                # @return [Integer] Find the entry whose key has the specified hash value,
         | 
| 33 100 | 
             
                # using the specified traits defining hash function and equality.
         | 
| 34 101 | 
             
                # @param  [object] key
         | 
| 35 102 | 
             
                # @param  [object] traits
         | 
| 36 | 
            -
                def find(key, traits)
         | 
| 37 | 
            -
                   | 
| 38 | 
            -
             | 
| 39 | 
            -
                  unless traits.respond_to?(:storage_key_to_lookup_key)
         | 
| 40 | 
            -
                    raise ArgumentError,
         | 
| 41 | 
            -
                          'traits must respond to storage_key_to_lookup_key method'
         | 
| 42 | 
            -
                  end
         | 
| 43 | 
            -
                  h = traits.hash_lookup_key(key) % capacity
         | 
| 103 | 
            +
                def find(key, traits = IdentityHashTraits.new)
         | 
| 104 | 
            +
                  nums = capacity - 1
         | 
| 105 | 
            +
                  h = traits.hash_lookup_key(key) & nums
         | 
| 44 106 | 
             
                  i = h
         | 
| 45 | 
            -
                  fisrt_unsed = nil
         | 
| 46 107 | 
             
                  loop do
         | 
| 47 | 
            -
                     | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
                     | 
| 53 | 
            -
                    i = (i + 1) % capacity
         | 
| 108 | 
            +
                    bucket = @buckets_k[i]
         | 
| 109 | 
            +
                    return i if !bucket.nil? && present?(i) && traits.storage_key_to_lookup_key(bucket) == key
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    break unless deleted?(i)
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                    i = (i + 1) & nums
         | 
| 54 114 | 
             
                    break if i == h
         | 
| 55 115 | 
             
                  end
         | 
| 56 | 
            -
                   | 
| 57 | 
            -
             | 
| 58 | 
            -
                  fisrt_unsed
         | 
| 116 | 
            +
                  i
         | 
| 59 117 | 
             
                end
         | 
| 60 118 |  | 
| 61 119 | 
             
                # @return [Integer] internal key
         | 
| @@ -63,23 +121,20 @@ module HashTable | |
| 63 121 | 
             
                # @param  [object] key
         | 
| 64 122 | 
             
                # @param  [object] value
         | 
| 65 123 | 
             
                # @param  [object] traits
         | 
| 66 | 
            -
                def set(key, value, traits)
         | 
| 124 | 
            +
                def set(key, value, traits = IdentityHashTraits.new)
         | 
| 67 125 | 
             
                  set_as_interal(key, traits, value)
         | 
| 68 126 | 
             
                end
         | 
| 69 127 |  | 
| 70 | 
            -
                def get(key, traits)
         | 
| 128 | 
            +
                def get(key, traits = IdentityHashTraits.new)
         | 
| 71 129 | 
             
                  i = find(key, traits)
         | 
| 72 | 
            -
                   | 
| 73 | 
            -
                  raise ArgumentError if bucket.nil? || bucket.empty?
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                  bucket.last
         | 
| 130 | 
            +
                  @buckets_v[i]
         | 
| 76 131 | 
             
                end
         | 
| 77 132 |  | 
| 78 133 | 
             
                # @return [Integer] internal key
         | 
| 79 134 | 
             
                # Set the entry using a key type that the specified Traits can convert from a real key to an internal key.
         | 
| 80 135 | 
             
                # @param  [object] key
         | 
| 81 136 | 
             
                # @param  [object] traits
         | 
| 82 | 
            -
                def add(key, traits)
         | 
| 137 | 
            +
                def add(key, traits = IdentityHashTraits.new)
         | 
| 83 138 | 
             
                  set_as_interal(key, traits)
         | 
| 84 139 | 
             
                end
         | 
| 85 140 |  | 
| @@ -87,13 +142,13 @@ module HashTable | |
| 87 142 | 
             
                # Set the entry using a key type that the specified Traits can convert from a real key to an internal key.
         | 
| 88 143 | 
             
                # @param  [object] key
         | 
| 89 144 | 
             
                # @param  [object] traits
         | 
| 90 | 
            -
                def adds(keys, traits)
         | 
| 145 | 
            +
                def adds(keys, traits = IdentityHashTraits.new)
         | 
| 91 146 | 
             
                  keys.map { |key| set_as_interal(key, traits) }
         | 
| 92 147 | 
             
                end
         | 
| 93 148 |  | 
| 94 149 | 
             
                protected
         | 
| 95 150 |  | 
| 96 | 
            -
                attr_reader :present, :deleted, : | 
| 151 | 
            +
                attr_reader :present, :deleted, :buckets_k, :buckets_v
         | 
| 97 152 |  | 
| 98 153 | 
             
                def present?(key)
         | 
| 99 154 | 
             
                  @present.test?(key)
         | 
| @@ -109,62 +164,61 @@ module HashTable | |
| 109 164 | 
             
                # @param  [object] traits
         | 
| 110 165 | 
             
                # @param  [object] value
         | 
| 111 166 | 
             
                # @param  [object] internal_key
         | 
| 112 | 
            -
                def set_as_interal(key, traits, value = nil, internal_key = nil)
         | 
| 113 | 
            -
                  unless traits.respond_to?(:lookup_key_to_storage_key)
         | 
| 114 | 
            -
                    raise ArgumentError,
         | 
| 115 | 
            -
                          'traits must respond to lookup_key_to_storage_key method'
         | 
| 116 | 
            -
                  end
         | 
| 117 | 
            -
                  unless traits.respond_to?(:storage_key_to_lookup_key)
         | 
| 118 | 
            -
                    raise ArgumentError,
         | 
| 119 | 
            -
                          'traits must respond to storage_key_to_lookup_key method'
         | 
| 120 | 
            -
                  end
         | 
| 121 | 
            -
             | 
| 167 | 
            +
                def set_as_interal(key, traits = IdentityHashTraits.new, value = nil, internal_key = nil)
         | 
| 122 168 | 
             
                  index = find(key, traits)
         | 
| 123 | 
            -
                   | 
| 124 | 
            -
                  if  | 
| 169 | 
            +
                  bucket_k = @buckets_k[index]
         | 
| 170 | 
            +
                  if bucket_k.nil?
         | 
| 125 171 | 
             
                    raise ArgumentError if present?(index)
         | 
| 126 172 |  | 
| 127 | 
            -
                     | 
| 128 | 
            -
                     | 
| 173 | 
            +
                    @buckets_k[index] = bucket_k = internal_key.nil? ? traits.lookup_key_to_storage_key(key) : internal_key
         | 
| 174 | 
            +
                    @buckets_v[index] = traits.lookup_key_to_storage_value(bucket_k, value)
         | 
| 129 175 | 
             
                    @present.set(index)
         | 
| 130 176 | 
             
                    @deleted.reset(index)
         | 
| 177 | 
            +
                    @size += 1
         | 
| 131 178 | 
             
                    grow(traits)
         | 
| 132 179 | 
             
                  else
         | 
| 133 | 
            -
                    raise ArgumentError unless present?(index)
         | 
| 134 | 
            -
                    raise ArgumentError unless traits.storage_key_to_lookup_key(@buckets[index].first) == key
         | 
| 180 | 
            +
                    raise ArgumentError unless present?(index) || traits.storage_key_to_lookup_key(bucket_k) == key
         | 
| 135 181 |  | 
| 136 | 
            -
                     | 
| 182 | 
            +
                    @buckets_v[index] = traits.lookup_key_to_storage_value(bucket_k, value)
         | 
| 137 183 | 
             
                  end
         | 
| 138 | 
            -
                   | 
| 139 | 
            -
             | 
| 140 | 
            -
                  bucket[0]
         | 
| 184 | 
            +
                  bucket_k
         | 
| 141 185 | 
             
                end
         | 
| 142 186 |  | 
| 143 187 | 
             
                private
         | 
| 144 188 |  | 
| 145 | 
            -
                def  | 
| 146 | 
            -
                   | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 189 | 
            +
                def need_grow(size, count, expand: false)
         | 
| 190 | 
            +
                  raise 'count must less than 2_863_311_519' if count > 2_863_311_519
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                  count *= 2
         | 
| 193 | 
            +
                  return size >= count / 3 + 1 unless expand
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                  size >= EXPAND_KEY_COUNT_CONST[count][0]
         | 
| 196 | 
            +
                end
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                def calculate_count(size, expand: false)
         | 
| 199 | 
            +
                  return 8 if size < 6
         | 
| 200 | 
            +
                  raise 'count must less than 2_863_311_519' if size > 2_863_311_519
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                  if expand
         | 
| 203 | 
            +
                    EXPAND_KEY_COUNT_CONST.each_pair { |key, e| return key / 2 if size < e[0] }
         | 
| 204 | 
            +
                  else
         | 
| 205 | 
            +
                    2**(size * 3 / 2 + 1 - 1).bit_length
         | 
| 149 206 | 
             
                  end
         | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
                   | 
| 154 | 
            -
             | 
| 155 | 
            -
                   | 
| 156 | 
            -
             | 
| 157 | 
            -
                  @num_entries = m + 1 if @expand
         | 
| 158 | 
            -
                  new_capacity = m * 2
         | 
| 159 | 
            -
                  return if new_capacity <= capacity
         | 
| 160 | 
            -
             | 
| 161 | 
            -
                  new_map = HashTable.new(new_capacity, expand: @expand)
         | 
| 207 | 
            +
                end
         | 
| 208 | 
            +
             | 
| 209 | 
            +
                def grow(traits = IdentityHashTraits.new)
         | 
| 210 | 
            +
                  return unless need_grow(size, capacity, expand: @expand)
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  new_map = HashTable.new(size, expand: @expand)
         | 
| 162 213 | 
             
                  @present.each do |i|
         | 
| 163 | 
            -
                     | 
| 214 | 
            +
                    key = @buckets_k[i]
         | 
| 215 | 
            +
                    lookup_key = traits.storage_key_to_lookup_key(key)
         | 
| 164 216 | 
             
                    # Private methods cannot be called with an explicit receiver and protected ones can.
         | 
| 165 | 
            -
                    new_map.set_as_interal(lookup_key, traits, @ | 
| 217 | 
            +
                    new_map.set_as_interal(lookup_key, traits, @buckets_v[i], key)
         | 
| 166 218 | 
             
                  end
         | 
| 167 | 
            -
             | 
| 219 | 
            +
             | 
| 220 | 
            +
                  @buckets_k = new_map.buckets_k
         | 
| 221 | 
            +
                  @buckets_v = new_map.buckets_v
         | 
| 168 222 | 
             
                  @present = new_map.present
         | 
| 169 223 | 
             
                  @deleted = new_map.deleted
         | 
| 170 224 | 
             
                end
         | 
| @@ -30,11 +30,18 @@ module HashTable | |
| 30 30 | 
             
                  @bits[index]
         | 
| 31 31 | 
             
                end
         | 
| 32 32 |  | 
| 33 | 
            +
                def ==(other)
         | 
| 34 | 
            +
                  return false unless index == other.index
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  (0...BITWORDS_PER_ELEMENT).each do |i|
         | 
| 37 | 
            +
                    return false unless @bits[i] == other.bits[i]
         | 
| 38 | 
            +
                  end || true
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
             | 
| 33 41 | 
             
                def empty?
         | 
| 34 42 | 
             
                  (0...BITWORDS_PER_ELEMENT).each do |i|
         | 
| 35 43 | 
             
                    return false unless @bits[i].zero?
         | 
| 36 | 
            -
                  end
         | 
| 37 | 
            -
                  true
         | 
| 44 | 
            +
                  end || true
         | 
| 38 45 | 
             
                end
         | 
| 39 46 |  | 
| 40 47 | 
             
                def set(index)
         | 
| @@ -48,9 +55,8 @@ module HashTable | |
| 48 55 | 
             
                def test_and_set?(index)
         | 
| 49 56 | 
             
                  unless test(index)
         | 
| 50 57 | 
             
                    set(Idx)
         | 
| 51 | 
            -
                     | 
| 52 | 
            -
                  end
         | 
| 53 | 
            -
                  false
         | 
| 58 | 
            +
                    true
         | 
| 59 | 
            +
                  end || false
         | 
| 54 60 | 
             
                end
         | 
| 55 61 |  | 
| 56 62 | 
             
                def reset(index)
         | 
| @@ -64,8 +70,13 @@ module HashTable | |
| 64 70 | 
             
                # v = (v * 0x0101010101010101) & UINT_64_MAX
         | 
| 65 71 | 
             
                # v >>= 56
         | 
| 66 72 | 
             
                def count
         | 
| 67 | 
            -
                   | 
| 68 | 
            -
                    v =  | 
| 73 | 
            +
                  @bits.inject(0) do |nums, b|
         | 
| 74 | 
            +
                    v = b
         | 
| 75 | 
            +
                    v -= ((v >> 1) & 0x5555555555555555)
         | 
| 76 | 
            +
                    v = (v & 0x3333333333333333) + ((v >> 2) & 0x3333333333333333)
         | 
| 77 | 
            +
                    v = (v + (v >> 4) & 0x0F0F0F0F0F0F0F0F)
         | 
| 78 | 
            +
                    v = (v * 0x0101010101010101) & UINT_64_MAX
         | 
| 79 | 
            +
                    v >>= 56
         | 
| 69 80 | 
             
                    nums + v
         | 
| 70 81 | 
             
                  end
         | 
| 71 82 | 
             
                end
         | 
| @@ -74,11 +85,12 @@ module HashTable | |
| 74 85 | 
             
                def first
         | 
| 75 86 | 
             
                  (0...BITWORDS_PER_ELEMENT).each do |i|
         | 
| 76 87 | 
             
                    v = @bits[i]
         | 
| 77 | 
            -
                     | 
| 78 | 
            -
             | 
| 79 | 
            -
             | 
| 80 | 
            -
                     | 
| 88 | 
            +
                    unless v.zero?
         | 
| 89 | 
            +
                      count = v.digits(2).index(1)
         | 
| 90 | 
            +
                      return i * BITWORD_SIZE + count
         | 
| 91 | 
            +
                    end
         | 
| 81 92 | 
             
                  end
         | 
| 93 | 
            +
                  raise ArgumentError, 'Illegal empty element'
         | 
| 82 94 | 
             
                end
         | 
| 83 95 |  | 
| 84 96 | 
             
                # @return [Integer] the index of the last set bit
         | 
| @@ -86,11 +98,12 @@ module HashTable | |
| 86 98 | 
             
                  (0...BITWORDS_PER_ELEMENT).each do |i|
         | 
| 87 99 | 
             
                    index = BITWORDS_PER_ELEMENT - i - 1
         | 
| 88 100 | 
             
                    v = @bits[index]
         | 
| 89 | 
            -
                     | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
                     | 
| 101 | 
            +
                    unless v.zero?
         | 
| 102 | 
            +
                      count = BIT_WORD - v.bit_length
         | 
| 103 | 
            +
                      return index * BITWORD_SIZE + BITWORD_SIZE - count - 1
         | 
| 104 | 
            +
                    end
         | 
| 93 105 | 
             
                  end
         | 
| 106 | 
            +
                  raise ArgumentError, 'Illegal empty element'
         | 
| 94 107 | 
             
                end
         | 
| 95 108 |  | 
| 96 109 | 
             
                # @return [Integer] the index of the next set bit starting from the "index" bit.
         | 
| @@ -153,6 +166,10 @@ module HashTable | |
| 153 166 | 
             
                    bits >>= 1
         | 
| 154 167 | 
             
                  end
         | 
| 155 168 | 
             
                end
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                protected
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                attr_reader :bits
         | 
| 156 173 | 
             
              end
         | 
| 157 174 |  | 
| 158 175 | 
             
              # SparseBitArray is an implementation of a bitmap that is sparse by only storing the elements that have non-zero bits set.
         | 
| @@ -163,6 +180,7 @@ module HashTable | |
| 163 180 | 
             
                # @return [Integer] Pointer to our current Element.
         | 
| 164 181 | 
             
                # This has no visible effect on the external state of a SparseBitArray
         | 
| 165 182 | 
             
                # It's just used to improve performance in the common case of testing/modifying bits with similar indices.
         | 
| 183 | 
            +
                # attr_reader :current_index
         | 
| 166 184 | 
             
                attr_reader :current_index
         | 
| 167 185 |  | 
| 168 186 | 
             
                def initialize
         | 
| @@ -177,8 +195,7 @@ module HashTable | |
| 177 195 |  | 
| 178 196 | 
             
                  e_index = index / ELEMENT_SIZE
         | 
| 179 197 | 
             
                  element_i = lower_bound(e_index)
         | 
| 180 | 
            -
                   | 
| 181 | 
            -
                  return false if element_i == last || elements[element_i].index != e_index
         | 
| 198 | 
            +
                  return false if element_i == elements.length || elements[element_i].index != e_index
         | 
| 182 199 |  | 
| 183 200 | 
             
                  elements[element_i].test?(index % ELEMENT_SIZE)
         | 
| 184 201 | 
             
                end
         | 
| @@ -205,13 +222,14 @@ module HashTable | |
| 205 222 | 
             
                  e_index = index / ELEMENT_SIZE
         | 
| 206 223 | 
             
                  element_i = lower_bound(e_index)
         | 
| 207 224 | 
             
                  new_e = elements[element_i]
         | 
| 208 | 
            -
                  unless new_e.nil?
         | 
| 209 | 
            -
             | 
| 210 | 
            -
                     | 
| 225 | 
            +
                  eql = new_e.index <=> e_index unless new_e.nil?
         | 
| 226 | 
            +
                  if eql.nil? || eql != 0
         | 
| 227 | 
            +
                    element_i += 1 if eql == -1
         | 
| 228 | 
            +
                    new_e = SparseBitArrayElement.new(e_index)
         | 
| 229 | 
            +
                    @elements.insert(element_i, new_e)
         | 
| 211 230 | 
             
                  end
         | 
| 212 231 | 
             
                  @current_index = element_i
         | 
| 213 | 
            -
                   | 
| 214 | 
            -
                  @elements[@current_index].set(index % ELEMENT_SIZE)
         | 
| 232 | 
            +
                  new_e.set(index % ELEMENT_SIZE)
         | 
| 215 233 | 
             
                end
         | 
| 216 234 |  | 
| 217 235 | 
             
                # @return [Void] Test, Set a bit in the bitmap
         | 
| @@ -269,12 +287,14 @@ module HashTable | |
| 269 287 | 
             
                  @current_index -= 1 if @current_index == elements.length
         | 
| 270 288 | 
             
                  element_i = @current_index
         | 
| 271 289 | 
             
                  element = @elements[element_i]
         | 
| 272 | 
            -
                  return element_i if element.index == index
         | 
| 273 290 |  | 
| 274 | 
            -
                  @current_index =  | 
| 291 | 
            +
                  @current_index = case element.index <=> index
         | 
| 292 | 
            +
                                   when 0
         | 
| 293 | 
            +
                                     element_i
         | 
| 294 | 
            +
                                   when 1
         | 
| 275 295 | 
             
                                     @elements[0..element_i].rindex { |e| e.index <= index } || 0
         | 
| 276 | 
            -
                                    | 
| 277 | 
            -
                                     @elements[element_i..]. | 
| 296 | 
            +
                                   when -1
         | 
| 297 | 
            +
                                     @elements[element_i..].count { |e| e.index < index } + element_i
         | 
| 278 298 | 
             
                                   end
         | 
| 279 299 | 
             
                end
         | 
| 280 300 | 
             
              end
         | 
    
        data/lib/hashtable/traits.rb
    CHANGED
    
    | @@ -1,84 +1,65 @@ | |
| 1 | 
            -
            # frozen_string_literal:  | 
| 1 | 
            +
            # frozen_string_literal: false
         | 
| 2 2 |  | 
| 3 3 | 
             
            # module HashTable
         | 
| 4 4 | 
             
            module HashTable
         | 
| 5 | 
            -
               | 
| 6 | 
            -
             | 
| 7 | 
            -
             | 
| 8 | 
            -
                 | 
| 9 | 
            -
             | 
| 10 | 
            -
                 | 
| 11 | 
            -
                  key
         | 
| 12 | 
            -
                end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                def storage_key_to_lookup_key(key)
         | 
| 15 | 
            -
                  key
         | 
| 16 | 
            -
                end
         | 
| 5 | 
            +
              # IdentityHashTraits
         | 
| 6 | 
            +
              class IdentityHashTraits
         | 
| 7 | 
            +
                define_method(:hash_lookup_key) { |key| key }
         | 
| 8 | 
            +
                define_method(:lookup_key_to_storage_key) { |key| key }
         | 
| 9 | 
            +
                define_method(:storage_key_to_lookup_key) { |key| key }
         | 
| 10 | 
            +
                define_method(:lookup_key_to_storage_value) { |_key, value| value }
         | 
| 17 11 | 
             
              end
         | 
| 18 12 |  | 
| 19 | 
            -
               | 
| 20 | 
            -
             | 
| 13 | 
            +
              # StringIdentityHashTraits
         | 
| 14 | 
            +
              class StringIdentityHashTraits < IdentityHashTraits
         | 
| 15 | 
            +
                attr_reader :string_table, :buckets
         | 
| 21 16 |  | 
| 22 | 
            -
                def initialize
         | 
| 17 | 
            +
                def initialize(&block)
         | 
| 18 | 
            +
                  super
         | 
| 23 19 | 
             
                  @string_table = "\0"
         | 
| 24 | 
            -
                  @ | 
| 20 | 
            +
                  @buckets = {}
         | 
| 21 | 
            +
                  @indexs = {}
         | 
| 25 22 | 
             
                end
         | 
| 26 23 |  | 
| 27 24 | 
             
                def hash_lookup_key(key)
         | 
| 28 | 
            -
                   | 
| 29 | 
            -
                  key.each_byte { |byte| result += byte * 13 }
         | 
| 30 | 
            -
                  result
         | 
| 25 | 
            +
                  key.downcase.bytes.inject(:+) * 13
         | 
| 31 26 | 
             
                end
         | 
| 32 27 |  | 
| 33 28 | 
             
                def lookup_key_to_storage_key(key)
         | 
| 34 | 
            -
                  @ | 
| 35 | 
            -
             | 
| 36 | 
            -
                   | 
| 29 | 
            +
                  return @buckets[key] unless @buckets[key].nil?
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  old_si = @string_table.length
         | 
| 32 | 
            +
                  @buckets[key] = old_si
         | 
| 33 | 
            +
                  @string_table << "#{key}\0".b
         | 
| 34 | 
            +
                  @indexs[old_si] = key
         | 
| 37 35 | 
             
                  old_si
         | 
| 38 36 | 
             
                end
         | 
| 39 37 |  | 
| 40 38 | 
             
                def storage_key_to_lookup_key(offset)
         | 
| 41 | 
            -
                  @ | 
| 42 | 
            -
                end
         | 
| 43 | 
            -
              end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
              class StringTraits2
         | 
| 46 | 
            -
                attr_reader :string_table, :buckets, :hash_h
         | 
| 39 | 
            +
                  return @indexs[offset] unless @indexs[offset].nil?
         | 
| 47 40 |  | 
| 48 | 
            -
             | 
| 49 | 
            -
                  @ | 
| 50 | 
            -
                   | 
| 51 | 
            -
                  @buckets = {}
         | 
| 52 | 
            -
                  @hash_h = {}
         | 
| 41 | 
            +
                  key = @string_table[offset..][/[^\0]+/]
         | 
| 42 | 
            +
                  @indexs[offset] = key
         | 
| 43 | 
            +
                  key
         | 
| 53 44 | 
             
                end
         | 
| 45 | 
            +
              end
         | 
| 54 46 |  | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 47 | 
            +
              # StringHashTraits
         | 
| 48 | 
            +
              class StringHashTraits < StringIdentityHashTraits
         | 
| 49 | 
            +
                attr_reader :string_table, :buckets
         | 
| 57 50 |  | 
| 58 | 
            -
             | 
| 59 | 
            -
                   | 
| 60 | 
            -
                  @ | 
| 61 | 
            -
                  result
         | 
| 51 | 
            +
                def initialize(&block)
         | 
| 52 | 
            +
                  super
         | 
| 53 | 
            +
                  @block = block
         | 
| 62 54 | 
             
                end
         | 
| 63 55 |  | 
| 64 | 
            -
                def  | 
| 65 | 
            -
                  return  | 
| 56 | 
            +
                def lookup_key_to_storage_value(key, values)
         | 
| 57 | 
            +
                  return if values.nil? || values.empty?
         | 
| 66 58 |  | 
| 67 | 
            -
                   | 
| 68 | 
            -
                   | 
| 69 | 
            -
                  @string_index += key.length + 1
         | 
| 70 | 
            -
                  @buckets[key] = old_si
         | 
| 71 | 
            -
                  old_si
         | 
| 72 | 
            -
                end
         | 
| 59 | 
            +
                  bs = values.map { |v| lookup_key_to_storage_key(v) }.unshift(key)
         | 
| 60 | 
            +
                  return bs if @block.nil?
         | 
| 73 61 |  | 
| 74 | 
            -
             | 
| 75 | 
            -
                  value.inject([@buckets[key]]) do |sum, v|
         | 
| 76 | 
            -
                    sum << lookup_key_to_storage_key(v)
         | 
| 77 | 
            -
                  end
         | 
| 78 | 
            -
                end
         | 
| 79 | 
            -
             | 
| 80 | 
            -
                def storage_key_to_lookup_key(offset)
         | 
| 81 | 
            -
                  @string_table[offset..][/[^\0]+/]
         | 
| 62 | 
            +
                  @block.call(bs)
         | 
| 82 63 | 
             
                end
         | 
| 83 64 | 
             
              end
         | 
| 84 65 | 
             
            end
         | 
    
        data/lib/hashtable/version.rb
    CHANGED
    
    
    
        data/lib/hashtable.rb
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: hashtable
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.1. | 
| 4 | 
            +
              version: 0.1.4
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Cat1237
         | 
| 8 8 | 
             
            autorequire:
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2022-05- | 
| 11 | 
            +
            date: 2022-05-27 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: bundler
         |