range_list_rb 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 279924926180cea40ac46209691189d10dc0e0957c2277d95974d9f48881e929
4
+ data.tar.gz: 0e675975de6bbcf768bfbeb217c947d3ab1d0202663736673882945f0580bef8
5
+ SHA512:
6
+ metadata.gz: a09f8b1b8957df0a8d486e9805355915e81cfbf9a1401b97b5cde31845b4330b3ec29ee2a4aa582e03361938f6355e296b76d7b78e4bc2dbd36b7b9300ed9a1e
7
+ data.tar.gz: 42494b7ec55a7c8d800d7e8263b8a4c55ffd894e61e79fbee0a8616f371fcf422d4542acc0a0deb441058580fd9da236f39e79aed85bb7b265da2e1e9f0ad479
data/.rubocop.yml ADDED
@@ -0,0 +1,21 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.6
3
+ Exclude:
4
+ - 'test/**/*'
5
+
6
+ Style/StringLiterals:
7
+ Enabled: true
8
+ EnforcedStyle: double_quotes
9
+
10
+ Style/StringLiteralsInInterpolation:
11
+ Enabled: true
12
+ EnforcedStyle: double_quotes
13
+
14
+ Layout/LineLength:
15
+ Max: 120
16
+
17
+ Metrics/AbcSize:
18
+ Max: 50
19
+
20
+ Metrics/MethodLength:
21
+ Max: 70
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in range_list.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+
10
+ gem "minitest", "~> 5.0"
11
+
12
+ gem "rubocop", "~> 1.28", require: false
data/Gemfile.lock ADDED
@@ -0,0 +1,44 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ range_list_rb (0.2.0)
5
+ rbtree (~> 0.4.5)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ ast (2.4.2)
11
+ minitest (5.15.0)
12
+ parallel (1.22.1)
13
+ parser (3.1.2.0)
14
+ ast (~> 2.4.1)
15
+ rainbow (3.1.1)
16
+ rake (13.0.6)
17
+ rbtree (0.4.5)
18
+ regexp_parser (2.3.1)
19
+ rexml (3.2.5)
20
+ rubocop (1.28.2)
21
+ parallel (~> 1.10)
22
+ parser (>= 3.1.0.0)
23
+ rainbow (>= 2.2.2, < 4.0)
24
+ regexp_parser (>= 1.8, < 3.0)
25
+ rexml
26
+ rubocop-ast (>= 1.17.0, < 2.0)
27
+ ruby-progressbar (~> 1.7)
28
+ unicode-display_width (>= 1.4.0, < 3.0)
29
+ rubocop-ast (1.17.0)
30
+ parser (>= 3.1.1.0)
31
+ ruby-progressbar (1.11.0)
32
+ unicode-display_width (2.1.0)
33
+
34
+ PLATFORMS
35
+ arm64-darwin-21
36
+
37
+ DEPENDENCIES
38
+ minitest (~> 5.0)
39
+ rake (~> 13.0)
40
+ range_list_rb!
41
+ rubocop (~> 1.28)
42
+
43
+ BUNDLED WITH
44
+ 2.3.13
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2022 songzheng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # RangeList
2
+
3
+ RangeList is a library for fast and efficient processing of intervals.
4
+
5
+ A pair of integers define a range, for example: [1, 5). This range includes integers: 1, 2, 3, and 4.
6
+ A range list is an aggregate of these ranges: [1, 5), [10, 11), [100, 201).
7
+
8
+ ## Installation
9
+
10
+ Install the gem and add to the application's Gemfile by executing:
11
+
12
+ $ bundle add range_list
13
+
14
+ If bundler is not being used to manage dependencies, install the gem by executing:
15
+
16
+ $ gem install range_list
17
+
18
+ ## Usage
19
+
20
+ ```ruby
21
+ range_list = RangeList.new # create a RangeList instance
22
+
23
+ range_list.add([1, 5])
24
+ range_list.print # [1, 5)
25
+
26
+ range_list.add([10, 20])
27
+ range_list.print # [1, 5) [10, 20)
28
+
29
+ range_list.add([20, 20])
30
+ range_list.print # [1, 5) [10, 20)
31
+
32
+ range_list.remove([3, 12])
33
+ range_list.print # [1, 3) [12, 20)
34
+ ```
35
+
36
+ ## Development
37
+
38
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
39
+
40
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
41
+
42
+ ## Contributing
43
+
44
+ Bug reports and merge requests are welcome on GitLab at https://gitlab.com/songzheng1997/range_list. This project is intended to be a safe, welcoming space for collaboration.
45
+
46
+ ## License
47
+
48
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
+ end
11
+
12
+ require "rubocop/rake_task"
13
+
14
+ RuboCop::RakeTask.new
15
+
16
+ task default: %i[test rubocop]
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # errors in RangeList
4
+ class RangeList
5
+ # base error define
6
+ class Error < StandardError; end
7
+
8
+ # raised when input arguments are illegal
9
+ class IllegalArgumentError < Error; end
10
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RangeList
4
+ module Map
5
+ # A Map that provides a ordering on its keys.
6
+ module SortedMap
7
+ # Returns the value to which the specified key is mapped, or nil if this map contains no mapping for the key.
8
+ def get(key)
9
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
10
+ end
11
+
12
+ # Associates the specified value with the specified key in this map.
13
+ # If the map previously contained a mapping for the key, the old value is replaced by the specified value.
14
+ def put(key, value)
15
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
16
+ end
17
+
18
+ # Removes the mapping for a key from this map if it is present.
19
+ def remove(key)
20
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
21
+ end
22
+
23
+ # Returns a key-value mapping associated with the greatest key less than or equal to the given key,
24
+ # or null if there is no such key.
25
+ def floor_entry(key)
26
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
27
+ end
28
+
29
+ # Returns a key-value mapping associated with the least key greater than or equal to the given key,
30
+ # or null if there is no such key.
31
+ def ceiling_entry(key)
32
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
33
+ end
34
+
35
+ # Returns a key-value mapping associated with the greatest key strictly less than the given key,
36
+ # or null if there is no such key.
37
+ def lower_entry(key)
38
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
39
+ end
40
+
41
+ # Returns a key-value mapping associated with the least key strictly greater than the given key,
42
+ # or null if there is no such key.
43
+ def higher_entry(key)
44
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
45
+ end
46
+
47
+ # Returns a view of the portion of this map whose keys range from from_key, inclusive, to to_key, exclusive.
48
+ def sub_map(from_key, to_key)
49
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
50
+ end
51
+
52
+ # Support iteration.
53
+ def each(&block)
54
+ raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbtree"
4
+ require_relative "sorted_map"
5
+
6
+ class RangeList
7
+ module Map
8
+ # Sorted map implementation using red-black tree
9
+ class TreeMap
10
+ include SortedMap
11
+
12
+ def initialize
13
+ @rbtree = RBTree.new
14
+ end
15
+
16
+ def get(key)
17
+ @rbtree[key]
18
+ end
19
+
20
+ def put(key, value)
21
+ @rbtree[key] = value
22
+ end
23
+
24
+ def remove(key)
25
+ @rbtree.delete(key)
26
+ end
27
+
28
+ def floor_entry(key)
29
+ @rbtree.upper_bound(key)
30
+ end
31
+
32
+ def ceiling_entry(key)
33
+ @rbtree.lower_bound(key)
34
+ end
35
+
36
+ def sub_map(from_key, to_key)
37
+ @rbtree.bound(from_key, to_key).to_a
38
+ end
39
+
40
+ def each(&block)
41
+ @rbtree.each(&block)
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :rbtree
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RangeList
4
+ VERSION = "0.2.0"
5
+ end
data/lib/range_list.rb ADDED
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "range_list/version"
4
+ require_relative "range_list/map/tree_map"
5
+ require_relative "range_list/errors"
6
+
7
+ # range list
8
+ class RangeList
9
+ include Enumerable
10
+
11
+ def initialize
12
+ @tree_map = Map::TreeMap.new
13
+ end
14
+
15
+ # Add specified range into current RangeList
16
+ # @param range [Array<Integer>] Two integer element such as [left, right], include left, exclude right.
17
+ # @return [RangeList]
18
+ # @raise [IllegalArgumentError] when range is invalid
19
+ def add(range)
20
+ validate_range!(range)
21
+ self if validate_empty_range?(range)
22
+
23
+ # 1. Compute the range of the merged interval
24
+ left_bound = range[0]
25
+ left_floor_range = @tree_map.floor_entry(range[0])
26
+ if !left_floor_range.nil? && left_floor_range[1] >= range[0]
27
+ # left_floor_range and range has overlapping interval
28
+ left_bound = left_floor_range[0]
29
+ end
30
+
31
+ right_bound = range[1]
32
+ right_floor_range = @tree_map.floor_entry(range[1])
33
+ if !right_floor_range.nil? && right_floor_range[1] >= range[1]
34
+ # right_floor_range and range has overlapping interval
35
+ right_bound = right_floor_range[1]
36
+ end
37
+
38
+ # 2. Remove overlapping intervals
39
+ overlapping_ranges = @tree_map.sub_map(left_bound, right_bound)
40
+ overlapping_ranges.each { |left, _right| @tree_map.remove(left) }
41
+
42
+ # 3. Insert new range
43
+ @tree_map.put(left_bound, right_bound)
44
+
45
+ self
46
+ end
47
+
48
+ # Remove specified range from current range
49
+ # @param range [Array<Integer>] Two integer element such as [left, right], include left, exclude right.
50
+ # @return [RangeList]
51
+ # @raise [IllegalArgumentError] when range is invalid
52
+ def remove(range)
53
+ validate_range!(range)
54
+ self if validate_empty_range?(range)
55
+
56
+ # 1. Remove overlapping intervals
57
+ right_lower_range = @tree_map.floor_entry(range[1] - 1)
58
+ if !right_lower_range.nil? && right_lower_range[1] > range[1]
59
+ # right_lower_range and range has overlapping interval
60
+ # insert new range after remove overlapping interval
61
+ @tree_map.put(range[1], right_lower_range[1])
62
+ end
63
+
64
+ left_lower_range = @tree_map.floor_entry(range[0] - 1)
65
+ if !left_lower_range.nil? && left_lower_range[1] > range[0]
66
+ # left_lower_range and range has overlapping interval
67
+ # insert new range after remove overlapping interval
68
+ @tree_map.put(left_lower_range[0], range[0])
69
+ end
70
+
71
+ # 2. Remove ranges completely covered by input range
72
+ # to_key is range[1] - 1 because right bound is exclusive
73
+ overlapping_ranges = @tree_map.sub_map(range[0], range[1] - 1)
74
+ overlapping_ranges.each { |left, _right| @tree_map.remove(left) }
75
+
76
+ self
77
+ end
78
+
79
+ # Print RangeList to stdout
80
+ def print
81
+ Kernel.print to_s
82
+ end
83
+
84
+ def each(&block)
85
+ @tree_map.each(&block)
86
+ end
87
+
88
+ def to_s
89
+ map { |k, v| "[#{k}, #{v})" }.join(" ")
90
+ end
91
+
92
+ private
93
+
94
+ attr_reader :tree_map
95
+
96
+ def validate_range!(range)
97
+ raise IllegalArgumentError, "range cannot be nil" if range.nil?
98
+ raise IllegalArgumentError, "range must be Array type" unless range.is_a?(Array)
99
+ unless range.size == 2 && range.all? { |elem| elem.is_a?(Integer) }
100
+ raise IllegalArgumentError, "range can only contain two Integer elements"
101
+ end
102
+ raise IllegalArgumentError, "range's left element must less equal than right element" unless range[0] <= range[1]
103
+ end
104
+
105
+ def validate_empty_range?(range)
106
+ range[0] >= range[1]
107
+ end
108
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/range_list/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "range_list_rb"
7
+ spec.version = RangeList::VERSION
8
+ spec.authors = ["songzheng"]
9
+ spec.email = ["zhenran.sz@gmail.com"]
10
+
11
+ spec.summary = "A library for fast and efficient processing of intervals."
12
+ spec.description = "A library for fast and efficient processing of intervals."
13
+ spec.homepage = "https://gitlab.com/songzheng1997/range_list"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 2.6.0"
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to your gem server 'https://example.com'"
18
+
19
+ spec.metadata["homepage_uri"] = spec.homepage
20
+ spec.metadata["source_code_uri"] = "https://gitlab.com/songzheng1997/range_list"
21
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
22
+
23
+ # Specify which files should be added to the gem when it is released.
24
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
25
+ spec.files = Dir.chdir(__dir__) do
26
+ `git ls-files -z`.split("\x0").reject do |f|
27
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
28
+ end
29
+ end
30
+ spec.bindir = "exe"
31
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
32
+ spec.require_paths = ["lib"]
33
+
34
+ # Uncomment to register a new dependency of your gem
35
+ spec.add_dependency "rbtree", "~> 0.4.5"
36
+
37
+ # For more information and examples about making a new gem, check out our
38
+ # guide at: https://bundler.io/guides/creating_gem.html
39
+ end
@@ -0,0 +1,4 @@
1
+ module RangeList
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: range_list_rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - songzheng
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-05-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rbtree
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.4.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.4.5
27
+ description: A library for fast and efficient processing of intervals.
28
+ email:
29
+ - zhenran.sz@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - ".rubocop.yml"
35
+ - Gemfile
36
+ - Gemfile.lock
37
+ - LICENSE.txt
38
+ - README.md
39
+ - Rakefile
40
+ - lib/range_list.rb
41
+ - lib/range_list/errors.rb
42
+ - lib/range_list/map/sorted_map.rb
43
+ - lib/range_list/map/tree_map.rb
44
+ - lib/range_list/version.rb
45
+ - range_list.gemspec
46
+ - sig/range_list.rbs
47
+ homepage: https://gitlab.com/songzheng1997/range_list
48
+ licenses:
49
+ - MIT
50
+ metadata:
51
+ homepage_uri: https://gitlab.com/songzheng1997/range_list
52
+ source_code_uri: https://gitlab.com/songzheng1997/range_list
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 2.6.0
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubygems_version: 3.3.7
69
+ signing_key:
70
+ specification_version: 4
71
+ summary: A library for fast and efficient processing of intervals.
72
+ test_files: []