Class: PositionRange::List
- Inherits:
-
Array
- Object
- Array
- PositionRange::List
- Defined in:
- lib/position_range/list.rb
Overview
–# Copyright: © 2006-2008 The LogiLogi Foundation <[email protected]>
License:
This file is part of the PositionRange Library. PositionRange is Free
Software. You can run/distribute/modify PositionRange under the terms of
the GNU Affero General Public License version 3. The Affero GPL states
that running a modified version or a derivative work also requires you to
make the sourcecode of that work available to everyone that can interact
with it. We chose the Affero GPL to ensure that PositionRange remains open
and libre (LICENSE.txt contains the full text of the legally binding
license).
++#
Keeps a list of PositionRanges.
Supports basic set operations, as well as many others, like clustering overlaps and getting sizes.
Constant Summary collapse
- CHECK_POSITION_RANGE_LIST_RE =
Check-regexps
/^(#{PositionRange::BLOCK_POSITION_RANGE}(\:#{PositionRange::BLOCK_POSITION_RANGE})*)?$/
Class Method Summary collapse
-
.around(string) ⇒ Object
Returns a new PositionRangeList for the provided string, covering it from start to end (the ‘string’ can also be an array).
-
.from_s(position_range_list_string, pass_on_options = {}) ⇒ Object
Parses a list of PositionRanges from a string.
Instance Method Summary collapse
-
#&(other) ⇒ Object
Applies an intersection in the sense of Set theory.
-
#-(other) ⇒ Object
Applies a substraction in the sense of Set theory.
-
#apply_to_string(string, options = {}) ⇒ Object
Returns a new string containing only the parts of the old string designated by position_ranges.
-
#below?(size) ⇒ Boolean
Returns true if all PositionRanges in this list don’t refer to positions bigger than size.
-
#cluster_overlaps ⇒ Object
Adds all items to a cluster-array, where overlapping PositionRanges are added to the same cluster_array position.
-
#delete(p_r) ⇒ Object
Deletion returning a new list.
-
#delete!(p_r) ⇒ Object
Deletes the position_range that is specified.
-
#index(position_range, options = {}) ⇒ Object
Returns the index of the given PositionRange.
-
#insert_at_ranges(ranges_to_insert, ranges_at_which_to_insert, ranges_to_skip = []) ⇒ Object
Inserting at ranges returning a new list.
-
#insert_at_ranges!(ranges_to_insert, ranges_at_which_to_insert, ranges_to_skip = []) ⇒ Object
The ranges_to_insert are inserted at the ranges_at_which_to_insert of this list, counted in range_size from it’s beginning, and inter- luded with ranges_to_skip.
-
#invert(maximum_size = PositionRange::MaximumSize) ⇒ Object
Inversion returning a new list.
-
#invert!(maximum_size = PositionRange::MaximumSize) ⇒ Object
Results in all positions being included, being excluded now, and all positions that were excluded, being included now, upto the range below maximum_size.
-
#line_up_overlaps ⇒ Object
Lining up overlaps returning a new list.
-
#line_up_overlaps! ⇒ Object
Makes sure that there are no non-overlapping borders between PositionRanges.
-
#merge_adjacents(options = {}) ⇒ Object
Merging adjacents returning a new list.
-
#merge_adjacents!(options = {}) ⇒ Object
Simplifies the PositionRange::List by merging adjacent PositionRanges.
-
#range_size ⇒ Object
Returns the combined size of the ranges in this list.
-
#stack_adjacent(options = {}) ⇒ Object
Stacks the PositionRanges in the List adjacent in a new PositionRange::List, while maintaining their size.
-
#substract(other, options = {}) ⇒ Object
Substraction returning a new list.
-
#substract!(other, options = {}) ⇒ Object
Applies a substraction in the sense of Set theory.
-
#to_s ⇒ Object
Parses a PositionRange::List to a string.
-
#translate(integer) ⇒ Object
Translation returning a new list.
-
#translate!(integer) ⇒ Object
Translates the PositionRange::List in space, along the given vector.
-
#translate_from_view(view_position_range_list) ⇒ Object
Translates the PositionRange::List into absolute space.
-
#translate_to_view(view_position_range_list) ⇒ Object
Translates the PositionRange::List into the relative space defined by the view_position_range_list.
-
#within?(other) ⇒ Boolean
Returns true if all PositionRanges in this list fall within the PositionRanges in the given other PositionRange::List.
Class Method Details
.around(string) ⇒ Object
Returns a new PositionRangeList for the provided string, covering it from start to end (the ‘string’ can also be an array).
60 61 62 63 64 65 66 |
# File 'lib/position_range/list.rb', line 60 def self.around(string) if string.size > 0 return PositionRange::List.new([PositionRange.new(0,string.size)]) else return PositionRange::List.new end end |
.from_s(position_range_list_string, pass_on_options = {}) ⇒ Object
Parses a list of PositionRanges from a string.
Syntax: <position range string>[:<position range string>]*
Options: The argument pass_on_options allows you to give options to be passed on to the PositionRanges created from the string
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/position_range/list.rb', line 39 def self.from_s(position_range_list_string, = {}) if position_range_list_string if position_range_list_string !~ CHECK_POSITION_RANGE_LIST_RE raise StandardError.new(), 'Invalid position_range_list string given: ' + position_range_list_string end p_r_l = PositionRange::List.new p_r_s_arr = position_range_list_string.split(':') p_r_s_arr.each {|p_r_s| p_r_l.push(PositionRange.from_s(p_r_s, )) } return p_r_l else return PositionRange::List.new end end |
Instance Method Details
#&(other) ⇒ Object
Applies an intersection in the sense of Set theory.
All PositionRanges and parts of PositionRanges that fall outside the PositionRanges given in the intersection_list are removed.
Example: 1,5:7,8:10,12’ becomes ‘2,5:11,12’ after limiting to ‘2,6:11,40’
136 137 138 139 |
# File 'lib/position_range/list.rb', line 136 def &(other) substraction_list = other.invert return self.substract(substraction_list, :ignore_attributes => true) end |
#-(other) ⇒ Object
Applies a substraction in the sense of Set theory.
See substract
145 146 147 |
# File 'lib/position_range/list.rb', line 145 def -(other) self.substract(other) end |
#apply_to_string(string, options = {}) ⇒ Object
Returns a new string containing only the parts of the old string designated by position_ranges.
Appends the string in the order in which they are found in this list.
Options :separator
=> The string to insert between the parts
545 546 547 548 549 550 551 552 553 554 555 |
# File 'lib/position_range/list.rb', line 545 def apply_to_string(string, = {}) separator = [:separator] || '' new_string = '' self.each {|p_r| if p_r.end > string.size raise StandardError, 'End-range bigger than string' end new_string += string[p_r] + separator } return new_string[0..-1 - separator.size] end |
#below?(size) ⇒ Boolean
Returns true if all PositionRanges in this list don’t refer to positions bigger than size. Otherwise false.
Attributes are ignored.
89 90 91 92 |
# File 'lib/position_range/list.rb', line 89 def below?(size) return self.within?( PositionRange::List.new([PositionRange.new(0,size)])) end |
#cluster_overlaps ⇒ Object
Adds all items to a cluster-array, where overlapping PositionRanges are added to the same cluster_array position.
So PositionRange::List.from_s(‘1,2:1,2:10,18:14,18’).cluster_overlaps will get you a cluster arr equal to the following:
[PositionRange::List.from_s(‘1,2:1,2’),
PositionRange::List.from_s('10,14'),
PositionRange::List.from_s('14,18:14,18')]
Except that the pointer_attributes are of course kept in order
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 |
# File 'lib/position_range/list.rb', line 519 def cluster_overlaps if !self.empty? lined_up_self = self.line_up_overlaps clusters = [PositionRange::List.new().push(lined_up_self.shift)] lined_up_self.each {|p_r| if p_r == clusters.last[0] clusters.last.push(p_r) else clusters.push(PositionRange::List.new([p_r])) end } return clusters else return self.dup end end |
#delete(p_r) ⇒ Object
Deletion returning a new list.
See delete!
222 223 224 |
# File 'lib/position_range/list.rb', line 222 def delete(p_r) self.substract(PositionRange::List.new([p_r])) end |
#delete!(p_r) ⇒ Object
Deletes the position_range that is specified.
214 215 216 |
# File 'lib/position_range/list.rb', line 214 def delete!(p_r) self.substract!(PositionRange::List.new([p_r])) end |
#index(position_range, options = {}) ⇒ Object
Returns the index of the given PositionRange.
Options :dont_ignore_attributes
=> true, finds the one that has also equal attributes, defaults to false
113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/position_range/list.rb', line 113 def index(position_range, = {}) if [:dont_ignore_attributes] self.each_with_index do |s_p_r, i| if position_range == s_p_r and position_range.has_equal_pointer_attributes?(s_p_r) return i end end return nil else super(position_range) end end |
#insert_at_ranges(ranges_to_insert, ranges_at_which_to_insert, ranges_to_skip = []) ⇒ Object
Inserting at ranges returning a new list.
See insert_at_ranges!
440 441 442 443 444 |
# File 'lib/position_range/list.rb', line 440 def insert_at_ranges(ranges_to_insert, ranges_at_which_to_insert, ranges_to_skip = []) return self.dup.insert_at_ranges!(ranges_to_insert, ranges_at_which_to_insert, ranges_to_skip) end |
#insert_at_ranges!(ranges_to_insert, ranges_at_which_to_insert, ranges_to_skip = []) ⇒ Object
The ranges_to_insert are inserted at the ranges_at_which_to_insert of this list, counted in range_size from it’s beginning, and inter- luded with ranges_to_skip.
So PositionRange::List.from_s(‘39,49:16,20’).insert_at_ranges!(
PositionRange::List.from_s('100,102:6,7'),
PositionRange::List.from_s('10,12:19,20'),
PositionRange::List.from_s('12,19'))
will result in: PositionRange::List.from_s(‘39,49:100,102:6,7:16,20’)
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 |
# File 'lib/position_range/list.rb', line 397 def insert_at_ranges!(ranges_to_insert, ranges_at_which_to_insert, ranges_to_skip = []) if ranges_to_insert.range_size != ranges_at_which_to_insert.range_size raise StandardError, 'Ranges to insert, and at which to insert are ' + 'of different range_sizes: ' + ranges_to_insert.to_s + ', ' + ranges_at_which_to_insert.to_s end ranges_to_act = ranges_at_which_to_insert.each {|p_r| p_r.action = :ins}.concat( ranges_to_skip).sort! i = -1 self_p = 0 ins_p = 0 ranges_to_act.each {|p_r| while self_p < p_r.begin - 1 i += 1 self_p += self[i].size end if self_p > p_r.begin copy = self[i] cut = copy.end + p_r.begin - self_p self[i] = copy.new_dup(copy.begin, cut) self.insert(i + 1, copy.new_dup(cut, copy.end)) self_p = p_r.begin end if p_r.action == :ins inner_p = 0 while inner_p < p_r.size self.insert(i + 1, ranges_to_insert[ins_p]) inner_p += ranges_to_insert[ins_p].size i += 1 ins_p += 1 end end self_p += p_r.size } return self end |
#invert(maximum_size = PositionRange::MaximumSize) ⇒ Object
Inversion returning a new list.
See invert!
269 270 271 |
# File 'lib/position_range/list.rb', line 269 def invert(maximum_size = PositionRange::MaximumSize) self.dup.invert!(maximum_size) end |
#invert!(maximum_size = PositionRange::MaximumSize) ⇒ Object
Results in all positions being included, being excluded now, and all positions that were excluded, being included now, upto the range below maximum_size.
NOTE: new ranges are created as PositionRanges, so references to objects or ordering_positions of subclasses are not maintained, as they are meaningless for inverted lists of ranges.
NOTE: Also that self is sorted.
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 |
# File 'lib/position_range/list.rb', line 236 def invert!(maximum_size = PositionRange::MaximumSize) if self.size > 0 self.sort!.merge_adjacents! # sorts and prevents problems with adjacent ranges if self[-1].end > maximum_size raise PositionRange::Error.new(self[-1].begin, self[-1].end), 'PositionRange larger than the maximum' end start_point = 0 if self[0].begin > 0 self.insert(0, PositionRange.new(0, self[0].begin)) start_point += 1 end if self.size > 1 (start_point...(self.size - 1)).each {|i| self[i] = PositionRange.new(self[i].end, self[i + 1].begin) } end if self[-1].end < maximum_size - 1 self[-1] = PositionRange.new(self[-1].end, maximum_size) else self.delete_at(-1) end elsif maximum_size > 0 self.push(PositionRange.new(0, maximum_size)) end return self end |
#line_up_overlaps ⇒ Object
Lining up overlaps returning a new list.
See line_up_overlaps!
328 329 330 |
# File 'lib/position_range/list.rb', line 328 def line_up_overlaps self.dup.line_up_overlaps! end |
#line_up_overlaps! ⇒ Object
Makes sure that there are no non-overlapping borders between PositionRanges.
The guaranteed situation after calling this method:
-
Multiple PositionRanges can refer to the same ranges, but if they do they will have the same begin and end position.
-
All positions associated with an object (a Link or an Authorship for example) will still be associated with that same object, but possibly through a different or a new PositionRange.
Example: ‘3,7->a:5,9->b’ lined up will be ‘3,5->a:5,7->a:5,7->b:7,9->b’
Where the ->X indicates an association with object X
This is used for simplifying PositionRanges for parsing Links into Logis.
291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/position_range/list.rb', line 291 def line_up_overlaps! self.sort!.merge_adjacents! # note that the merging and the sorting done by merge_adjacents # assures that he PositionRanges are always sorted by # begin-position AND size (short to long). i = 0 while i < (self.size - 1) if self[i].end > self[i + 1].begin # found an overlap if self[i].begin != self[i + 1].begin # the beginnings are not lined up, so align them self.insert(i + 1, self[i].new_dup(self[i + 1].begin, self[i].end)) self[i] = self[i].new_dup(self[i].begin, self[i + 1].begin) i = -1; self.sort! # restart in case more than 1 overlap elsif self[i].end != self[i + 1].end # the beginnings are already lined up, now do the ends if self[i].end < self[i + 1].end # i is the shortest, so self[i].end is used self.insert(i + 2, self[i + 1].new_dup(self[i].end, self[i + 1].end)) self[i + 1] = self[i + 1].new_dup(self[i + 1].begin, self[i].end) else # i + 1 is the shortest, so self[i + 1].end is used self.insert(i + 2, self[i].new_dup(self[i + 1].end, self[i].end)) self[i] = self[i].new_dup(self[i].begin, self[i + 1].end) end i = -1; self.sort! # restart in case more than 1 overlap end end i += 1 end return self end |
#merge_adjacents(options = {}) ⇒ Object
Merging adjacents returning a new list.
See merge_adjacents!
361 362 363 |
# File 'lib/position_range/list.rb', line 361 def merge_adjacents( = {}) self.dup.merge_adjacents!() end |
#merge_adjacents!(options = {}) ⇒ Object
Simplifies the PositionRange::List by merging adjacent PositionRanges.
Example: 1,4:4,7:10,11 => 1,7:10,11
Only merges adjacent PositionRanges if all their attributes (except for first and last) are the same
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 |
# File 'lib/position_range/list.rb', line 340 def merge_adjacents!( = {}) ignore_attributes = [:ignore_attributes] if self.size > 1 i = 0 while i < self.size if self[i - 1].end == self[i].begin and (ignore_attributes or self[i - 1].has_equal_pointer_attributes?(self[i])) self[i - 1] = self[i - 1].new_dup(self[i - 1].begin, self[i].end) self.delete_at(i) else i += 1 end end end return self end |
#range_size ⇒ Object
Returns the combined size of the ranges in this list.
76 77 78 79 80 81 82 |
# File 'lib/position_range/list.rb', line 76 def range_size range_size = 0 self.each {|range| range_size += range.size } return range_size end |
#stack_adjacent(options = {}) ⇒ Object
Stacks the PositionRanges in the List adjacent in a new PositionRange::List, while maintaining their size.
So PositionRangeList.from_s(‘50,53:11,30’).stack_adjacents returns: PositionRangeList.from_s(‘0,3:4,23’)
Options :space
=> The space to leave inbetween
495 496 497 498 499 500 501 502 503 504 505 |
# File 'lib/position_range/list.rb', line 495 def stack_adjacent( = {}) space = [:space] || 0 adjacent = PositionRange::List.new adjacent_p = 0 self.collect do |p_r| step = p_r.size adjacent << PositionRange.new(adjacent_p, adjacent_p + step) adjacent_p += step + space end return adjacent end |
#substract(other, options = {}) ⇒ Object
Substraction returning a new list.
See substract!
208 209 210 |
# File 'lib/position_range/list.rb', line 208 def substract(other, = {}) self.dup.substract!(other, ) end |
#substract!(other, options = {}) ⇒ Object
Applies a substraction in the sense of Set theory.
It removes all PositionRanges and parts of PositionRanges that overlap with the PositionRanges given as the other.
So for example: 1,5:7,9:11,12’ becomes ‘1,4:7,8:11,12’ after substracting ‘4,6:8,9’
Only substracts PositionRanges if all their attributes (except for first and last) are the same, unless ignore_attributes is specified.
Options :ignore_attributes
=> Ignores attributes
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/position_range/list.rb', line 163 def substract!(other, = {}) ignore_attributes = [:ignore_attributes] sorted_self = self.sort if sorted_self.size > 0 and other.size > 0 other = other.sort.merge_adjacents! last_i = 0 other.each do |p_r| i = last_i while sorted_self[i] and sorted_self[i].end < p_r.begin i += 1 end last_i = i while sorted_self[i] and sorted_self[i].begin < p_r.end if ignore_attributes or sorted_self[i].has_equal_pointer_attributes?(p_r) self_i = self.index(sorted_self[i], :dont_ignore_attributes => !ignore_attributes) if sorted_self[i].begin < p_r.begin copy = sorted_self[i].dup sorted_self[i] = copy.new_dup(copy.begin, p_r.begin) self[self_i] = sorted_self[i] sorted_self.insert(i + 1, copy.new_dup(p_r.begin, copy.end)) self.insert(self_i + 1, sorted_self[i + 1]) i += 1 elsif sorted_self[i].end <= p_r.end sorted_self.delete_at(i) self.delete_at(self_i) else sorted_self[i] = sorted_self[i].new_dup( p_r.end, sorted_self[i].end) self[self_i] = sorted_self[i] end else i += 1 end end end end return self end |
#to_s ⇒ Object
Parses a PositionRange::List to a string
561 562 563 564 565 566 567 568 |
# File 'lib/position_range/list.rb', line 561 def to_s self.sort p_r_l_string = '' self.each {|p_r| p_r_l_string += p_r.to_s + ':' } return p_r_l_string[0...-1] end |
#translate(integer) ⇒ Object
Translation returning a new list.
See translate!
381 382 383 |
# File 'lib/position_range/list.rb', line 381 def translate(integer) self.dup.translate!(integer) end |
#translate!(integer) ⇒ Object
Translates the PositionRange::List in space, along the given vector.
367 368 369 370 371 372 373 374 375 |
# File 'lib/position_range/list.rb', line 367 def translate!(integer) if !integer.kind_of?(Integer) raise StandardError.new, 'Tried to translate a PositionRange::List with a non-integer' end (0...self.size).each {|i| self[i] = self[i].new_dup(self[i].first + integer,self[i].last + integer) } return self end |
#translate_from_view(view_position_range_list) ⇒ Object
Translates the PositionRange::List into absolute space
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 |
# File 'lib/position_range/list.rb', line 469 def translate_from_view(view_position_range_list) absolute = PositionRange::List.new self.each do |p_r| view_p = 0 p_r_list = PositionRange::List.new([p_r]) view_position_range_list.each do |snippet_p_r| translate_list = p_r_list & PositionRange::List.new( [PositionRange.new(view_p,view_p + snippet_p_r.size)]) vector = snippet_p_r.first - view_p absolute.concat(translate_list.translate!(vector)) view_p += snippet_p_r.size end end absolute.merge_adjacents! return absolute end |
#translate_to_view(view_position_range_list) ⇒ Object
Translates the PositionRange::List into the relative space defined by the view_position_range_list
451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 |
# File 'lib/position_range/list.rb', line 451 def translate_to_view(view_position_range_list) relative = PositionRange::List.new self.each do |p_r| view_p = 0 p_r_list = PositionRange::List.new([p_r]) view_position_range_list.each do |snippet_p_r| translate_list = p_r_list & PositionRange::List.new([snippet_p_r]) vector = view_p - snippet_p_r.first relative.concat(translate_list.translate!(vector)) view_p += snippet_p_r.size end end relative.merge_adjacents! return relative end |
#within?(other) ⇒ Boolean
Returns true if all PositionRanges in this list fall within the PositionRanges in the given other PositionRange::List
Attributes are ignored.
99 100 101 102 103 104 105 |
# File 'lib/position_range/list.rb', line 99 def within?(other) if (self.substract(other, :ignore_attributes => true)).empty? return true else return false end end |