flex_array 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/lib/flex_array/array.rb +55 -0
- data/lib/flex_array/flex_array_append.rb +51 -0
- data/lib/flex_array/flex_array_each.rb +98 -0
- data/lib/flex_array/flex_array_index.rb +42 -0
- data/lib/flex_array/flex_array_new.rb +98 -0
- data/lib/flex_array/flex_array_process.rb +103 -0
- data/lib/flex_array/flex_array_reshape.rb +46 -0
- data/lib/flex_array/flex_array_transpose.rb +17 -0
- data/lib/flex_array/flex_array_validate.rb +37 -0
- data/lib/flex_array/integer.rb +34 -0
- data/lib/flex_array/object.rb +29 -0
- data/lib/flex_array/range.rb +44 -0
- data/lib/flex_array/spec_component.rb +71 -0
- data/lib/flex_array.rb +89 -0
- data/license.txt +21 -0
- data/rakefile.rb +44 -0
- data/readme.txt +2 -0
- data/reek.txt +29 -0
- data/tests/array_test.rb +62 -0
- data/tests/flex_array_append_test.rb +82 -0
- data/tests/flex_array_each_test.rb +120 -0
- data/tests/flex_array_index_test.rb +222 -0
- data/tests/flex_array_new_test.rb +131 -0
- data/tests/flex_array_reshape_test.rb +76 -0
- data/tests/flex_array_test.rb +58 -0
- data/tests/flex_array_transpose_test.rb +32 -0
- data/tests/flex_array_validate_test.rb +29 -0
- data/tests/integer_test.rb +40 -0
- data/tests/object_test.rb +38 -0
- data/tests/range_test.rb +41 -0
- data/tests/spec_component_test.rb +88 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
YzZiYThmNDY2YTU2NGE5YmQzNjUxNjRhODdkMzJiMGEwMWNiM2MwZg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
M2NjZmZmMjM2ZmEzMGNkNzc1NjFiNWEwNDE4ZTc3Y2M3ZmVkZjYzOQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
MmJlZDVkZTRiMzJhMmU3ZTEyNmU3MmQyM2Q0NWQ3MDAxNTRjZDkzMGRlZjE4
|
10
|
+
ZjMyY2EzYzA4N2RjY2U5YzVlNjg3NGM4ZTQwNTAxOGFlNWYzYzAyN2MyNjE1
|
11
|
+
M2FlMWI1N2YwYTk0MGQ4ZTBjMmQ3N2M1MDZjMzYzNjM1YmRmZWY=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MjYwNDJkODI2YzA2YTE0NTY5MmZiZmQxNGYyNzA0NzExYzI0YWI1N2U3MGVm
|
14
|
+
NDliYWIxZTQyYjJhYzQzM2I4NDljOTI0NGZlZDQwNjlhMThmYjU2NDA5OGJk
|
15
|
+
NjQxZGM3YWEzZWIyYmU0MzA4NzNjYjgyYjNlNjkwMTdmZDgwMjg=
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative '../flex_array'
|
2
|
+
|
3
|
+
#Extensions to Array needed to support flex array.
|
4
|
+
class Array
|
5
|
+
#Get the specifications of the array index values.
|
6
|
+
#<br>Returns
|
7
|
+
#* An array with one range in it.
|
8
|
+
def limits
|
9
|
+
[0...self.length]
|
10
|
+
end
|
11
|
+
|
12
|
+
#Quick access to the limits for internal use.
|
13
|
+
#<br>Returns
|
14
|
+
#* An array with one spec component in it.
|
15
|
+
def array_specs
|
16
|
+
[SpecComponent.new(0...self.length, 1)]
|
17
|
+
end
|
18
|
+
|
19
|
+
#Quick access to the array data for internal use.
|
20
|
+
#<br>Returns
|
21
|
+
#* An array -- self
|
22
|
+
def array_data
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
alias :length :count
|
27
|
+
|
28
|
+
#Convert this array to an range index against the spec.
|
29
|
+
#<br>Parameters
|
30
|
+
#* spec - The spec component used to validate this index.
|
31
|
+
#<br>Returns
|
32
|
+
#* A range.
|
33
|
+
#<br>Exceptions
|
34
|
+
#* IndexError if the range is not valid.
|
35
|
+
def to_index_range(spec)
|
36
|
+
self_min, self_max, spec_max = Integer(self[0]), Integer(self[1]), spec.max
|
37
|
+
self_min = spec_max + self_min + 1 if self_min < 0
|
38
|
+
self_max = spec_max + self_max + 1 if self_max < 0
|
39
|
+
|
40
|
+
if self.length == 2 && self_min <= self_max && spec === self_min && spec === self_max
|
41
|
+
self_min..self_max
|
42
|
+
else
|
43
|
+
fail IndexError, "Subscript invalid or out of range: #{self.inspect}"
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
#Return this flex array as a flex array!
|
48
|
+
#<br>Returns
|
49
|
+
#* A flex array that references this array.
|
50
|
+
#<br>Note
|
51
|
+
#* To avoid a shared reference, use my_array.dup.to_flex_array instead.
|
52
|
+
def to_flex_array
|
53
|
+
FlexArray.new_from_array(self)
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#* flex_array_append.rb - The flexible array class << and related methods.
|
2
|
+
class FlexArray
|
3
|
+
#Append to the flex array.
|
4
|
+
#<br>Parameters
|
5
|
+
#* data - the data being appended to this array.
|
6
|
+
#<br>Returns
|
7
|
+
#* The array spec of the array being appended.
|
8
|
+
#<br>Exceptions
|
9
|
+
#* ArgumentError if the data may not be appended.
|
10
|
+
def << (data)
|
11
|
+
specs = get_append_specs(data = data.in_array)
|
12
|
+
data.array_data.each {|element| @array_data << element }
|
13
|
+
@array_specs[0].enlarge(specs[0].span)
|
14
|
+
@count += data.count
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
#Make a copy of the other's data.
|
19
|
+
#<br>Parameters
|
20
|
+
#* other - The flex array whose data is to be copied.
|
21
|
+
def copy_data(other)
|
22
|
+
fail ArgumentError, "Incompatible data copy." unless compatible?(other)
|
23
|
+
@array_data = other.array_data.dup
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
#Extract and validate the append array_spec
|
28
|
+
#<br>Parameters
|
29
|
+
#* data - the data being appended to this array.
|
30
|
+
#<br>Returns
|
31
|
+
#* The array spec of the array being appended.
|
32
|
+
#<br>Exceptions
|
33
|
+
#* ArgumentError if the data may not be appended.
|
34
|
+
def get_append_specs(data)
|
35
|
+
spec_len = (specs = data.array_specs).length
|
36
|
+
|
37
|
+
if @dimensions == spec_len+1
|
38
|
+
specs.insert(0, SpecComponent.new(0...1, nil))
|
39
|
+
elsif @dimensions != spec_len
|
40
|
+
fail ArgumentError, "Incompatible dimensionality error on <<."
|
41
|
+
end
|
42
|
+
|
43
|
+
(1...@dimensions).each do |index|
|
44
|
+
unless @array_specs[index] == specs[index]
|
45
|
+
fail ArgumentError, "Dimension mismatch error on <<."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
specs
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
#* flex_array_each.rb - The flexible array class each and related methods.
|
2
|
+
class FlexArray
|
3
|
+
#Process the standard :each operator.
|
4
|
+
#<br>Parameters
|
5
|
+
#* indexes - An array with as many entries as the flexible array has
|
6
|
+
# dimensions. See [] for more details. Note that since indexes is NOT
|
7
|
+
# a splat parameter, it must be passed as an array explicitly. If omitted
|
8
|
+
# or passed in as [:all] all elements of the array are processed.
|
9
|
+
#* block - The optional block to be executed for each selected array element.
|
10
|
+
# The return value is the last value returned by the block. If the block
|
11
|
+
# is not present, an enumerator object is returned.
|
12
|
+
#<br>Block Arguments
|
13
|
+
#* value - Each value selected by the iteration.
|
14
|
+
def each(indexes=[:all], &block)
|
15
|
+
if validate_all_or_index_count(indexes)
|
16
|
+
@array_data.each(&block)
|
17
|
+
elsif block_given?
|
18
|
+
process_indexes(indexes) {|_index, posn| block.call(@array_data[posn])}
|
19
|
+
else
|
20
|
+
Enumerator.new do |yielder|
|
21
|
+
process_indexes(indexes) {|_index, posn| yielder.yield(@array_data[posn])}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
#Retrieve data from the array endlessly repeating as needed.
|
27
|
+
#<br>Parameters
|
28
|
+
#* indexes - An array with as many entries as the flexible array has
|
29
|
+
# dimensions. See [] for more details. Note that since indexes is NOT
|
30
|
+
# a splat parameter, it must be passed as an array explicitly. If omitted
|
31
|
+
# or passed in as [:all] all elements of the array are processed.
|
32
|
+
#* block - The optional block to be executed for each selected array element.
|
33
|
+
# The return value is the last value returned by the block. If the block
|
34
|
+
# is not present, then an enumerator object is returned.
|
35
|
+
#<br>Block Arguments
|
36
|
+
#* value - Each value selected by the iteration.
|
37
|
+
def cycle(indexes=[:all], &block)
|
38
|
+
if validate_all_or_index_count(indexes)
|
39
|
+
@array_data.cycle(&block)
|
40
|
+
elsif block_given?
|
41
|
+
loop {process_indexes(indexes) {|_index, posn| block.call(@array_data[posn])}}
|
42
|
+
else
|
43
|
+
Enumerator.new do |yielder|
|
44
|
+
loop {process_indexes(indexes) {|_index, posn| yielder.yield(@array_data[posn])}}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
#Process the standard :each_with_index operator.
|
50
|
+
#<br>Parameters
|
51
|
+
#* indexes - An array with as many entries as the flexible array has
|
52
|
+
# dimensions. See [] for more details. Note that since indexes is NOT
|
53
|
+
# a splat parameter, it must be passed as an array explicitly. If omitted
|
54
|
+
# or passed in as [:all] all elements of the array are processed.
|
55
|
+
#* block - The optional block to be executed for each selected array element.
|
56
|
+
# The return value is the last value returned by the block. If the block
|
57
|
+
# is not present, then an enumerator object is returned.
|
58
|
+
#<br>Block Arguments
|
59
|
+
#* value - Each value selected by the iteration.
|
60
|
+
#* index - An array with the full index of the selected value.
|
61
|
+
def each_with_index(indexes=[:all], &block)
|
62
|
+
validate_all_or_index_count(indexes)
|
63
|
+
|
64
|
+
if block_given?
|
65
|
+
process_indexes(indexes) {|index, posn| block.call(@array_data[posn], index)}
|
66
|
+
else
|
67
|
+
Enumerator.new do |yielder|
|
68
|
+
process_indexes(indexes) {|index, posn| yielder.yield(@array_data[posn], index)}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
#A specialized each variant that passes the low level data, the index
|
74
|
+
#and the position to the block.
|
75
|
+
#<br>Parameters
|
76
|
+
#* indexes - An array with as many entries as the flexible array has
|
77
|
+
# dimensions. See [] for more details. Note that since indexes is NOT
|
78
|
+
# a splat parameter, it must be passed as an array explicitly. If omitted
|
79
|
+
# or passed in as [:all] all elements of the array are processed.
|
80
|
+
#* block - The optional block to be executed for each selected array element.
|
81
|
+
# The return value is the last value returned by the block. If the block
|
82
|
+
# is not present, then an enumerator object is returned.
|
83
|
+
#<br>Block Arguments
|
84
|
+
#* data - A reference to the low level data store.
|
85
|
+
#* index - An array with the full index of the selected value.
|
86
|
+
#* posn - The position of the data in the low level data store.
|
87
|
+
def _each_raw(indexes=[:all], &block)
|
88
|
+
validate_all_or_index_count(indexes)
|
89
|
+
|
90
|
+
if block_given?
|
91
|
+
process_indexes(indexes) {|index, posn| block.call(@array_data, index, posn)}
|
92
|
+
else
|
93
|
+
Enumerator.new do |yielder|
|
94
|
+
process_indexes(indexes) {|index, posn| yielder.yield(@array_data, index, posn)}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#* flex_array_index.rb - The flexible array indexing and related methods.
|
2
|
+
class FlexArray
|
3
|
+
#Retrieve the selected data from the \FlexArray.
|
4
|
+
#<br>Parameters
|
5
|
+
#* indexes - An array with as many entries as the flexible array has
|
6
|
+
# dimensions. These entries may be an integer that is in the bounds of
|
7
|
+
# the range limit of that dimension, or it can be a range where the start
|
8
|
+
# and end of the range are in the bounds of the range limit of that
|
9
|
+
# dimension, or it can be the symbol :all for all of the possible values
|
10
|
+
# of that dimension.
|
11
|
+
#<br>Returns
|
12
|
+
#* The data selected by the index or an array of data if the index selects
|
13
|
+
# more than one cell.
|
14
|
+
#<br>Note
|
15
|
+
#* If the indexes parameter equals a single :all this maps to all
|
16
|
+
# elements of the array, regardless of its dimensionality.
|
17
|
+
def [](*indexes)
|
18
|
+
validate_all_or_index_count(indexes)
|
19
|
+
result = []
|
20
|
+
process_indexes(indexes) {|_index, posn| result << @array_data[posn]}
|
21
|
+
result.length == 1 ? result[0] : result
|
22
|
+
end
|
23
|
+
|
24
|
+
#Store the value data into the \FlexArray.
|
25
|
+
#<br>Parameters
|
26
|
+
#* indexes - An array with as many entries as the flexible array has
|
27
|
+
# dimensions. These entries may be an integer that is in the bounds of
|
28
|
+
# the range limit of that dimension, or it can be a range where the start
|
29
|
+
# and end of the range are in the bounds of the range limit of that
|
30
|
+
# dimension, or it can be the symbol :all for all of the possible values
|
31
|
+
# of that dimension.
|
32
|
+
#* value - A value to be stored in the selected array entries.
|
33
|
+
#<br>Returns
|
34
|
+
#* The value stored in the array.
|
35
|
+
#<br>Note
|
36
|
+
#* If the indexes parameter equals a single :all this maps to all
|
37
|
+
# elements of the array, regardless of its dimensionality.
|
38
|
+
def []=(*indexes, value)
|
39
|
+
validate_all_or_index_count(indexes)
|
40
|
+
process_indexes(indexes) {|_index, posn| @array_data[posn] = value}
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
#* flex_array_new.rb - The flexible array class constructor and related methods.
|
2
|
+
class FlexArray
|
3
|
+
#Construct a flexible array object.
|
4
|
+
#<br>Parameters
|
5
|
+
#* array_specs - The specification of the flex array subscript limits.
|
6
|
+
# These can either be integers, in which case the limits are 0...n or
|
7
|
+
# they can be ranges of integers, in which case the limits are those
|
8
|
+
# of the range, or they can be an array of integers and/or ranges of
|
9
|
+
# integers to represent arrays of more than one dimension.
|
10
|
+
#* default - The optional default value. Defaults to nil.
|
11
|
+
#* init_block - An optional initialization block. The return values from this
|
12
|
+
# block are used to initialize the array. This overrides the default value.
|
13
|
+
#<br>Block Arguments
|
14
|
+
#* index - An array with one element per dimension with the fully qualified
|
15
|
+
# index of the element being accessed. Note that the same array is passed
|
16
|
+
# in each time the block is called, so, if this parameter is to stored
|
17
|
+
# anywhere, it's best that it be cloned or duplicated first.
|
18
|
+
def initialize(array_specs, default=nil, &init_block)
|
19
|
+
@array_specs, @count, @transposed = [], 1, false
|
20
|
+
|
21
|
+
#Parse the array limits.
|
22
|
+
array_specs.in_array.reverse_each do |spec|
|
23
|
+
@array_specs.insert(0, spec.to_spec_component(@count))
|
24
|
+
@count *= @array_specs[0].span
|
25
|
+
end
|
26
|
+
|
27
|
+
#Cache the dimensionality of the flex array.
|
28
|
+
@dimensions = @array_specs.length
|
29
|
+
|
30
|
+
#Allocate the data for the array.
|
31
|
+
@array_data = Array.new(@count, default)
|
32
|
+
|
33
|
+
#Set up the array with the optional init_block.
|
34
|
+
if init_block
|
35
|
+
process_all {|index, posn| @array_data[posn] = init_block.call(index)}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
alias_method :shallow_dup, :dup
|
40
|
+
|
41
|
+
#Create a duplicate of this array.
|
42
|
+
def dup
|
43
|
+
other = self.shallow_dup
|
44
|
+
other.array_specs = @array_specs.dup
|
45
|
+
other.array_data = @array_data.dup
|
46
|
+
other
|
47
|
+
end
|
48
|
+
|
49
|
+
#Construct a \FlexArray using other as a template or data source.
|
50
|
+
#<br>Parameters:
|
51
|
+
#* array_specs - The specification of the flex array subscript limits.
|
52
|
+
# These can either be integers, in which case the limits are 0...n or
|
53
|
+
# they can be ranges of integers, in which case the limits are those
|
54
|
+
# of the range, or they can be an array of integers and/or ranges of
|
55
|
+
# integers to represent arrays of more than one dimension.
|
56
|
+
#* other - The array or flex array that is the source of the data.
|
57
|
+
#<br>Note:
|
58
|
+
#* To make a copy of a flex array, use dup instead.
|
59
|
+
def self.new_from(array_specs, other)
|
60
|
+
iterator = other.array_data.cycle
|
61
|
+
FlexArray.new(array_specs) {iterator.next}
|
62
|
+
end
|
63
|
+
|
64
|
+
#Construct a \FlexArray using a all or a portion of portion of another
|
65
|
+
#\FlexArray as a data source.
|
66
|
+
#<br>Parameters
|
67
|
+
#* array_specs - The specification of the flex array subscript limits.
|
68
|
+
# These can either be integers, in which case the limits are 0...n or
|
69
|
+
# they can be ranges of integers, in which case the limits are those
|
70
|
+
# of the range, or they can be an array of integers and/or ranges of
|
71
|
+
# integers to represent arrays of more than one dimension.
|
72
|
+
#* other - The flex array source of the data used to build this flex
|
73
|
+
# array. If no limits or other_indexes are specified, the limits of the
|
74
|
+
# other array are used instead.
|
75
|
+
#* selection - The indexes of the required sub-set of data to use
|
76
|
+
# in setting up the new array.
|
77
|
+
#<br>Notes:
|
78
|
+
#* If the entire source array is to be used, use new_from instead.
|
79
|
+
#* To make a copy of a flex array, use dup instead.
|
80
|
+
def self.new_from_selection(array_specs, other, selection)
|
81
|
+
iterator = other.cycle(selection)
|
82
|
+
FlexArray.new(array_specs) {iterator.next}
|
83
|
+
end
|
84
|
+
|
85
|
+
#Construct a \FlexArray as a duplicate of a source array or flex array.
|
86
|
+
#<br>Parameters
|
87
|
+
#* other - the array or flex array that is the source.
|
88
|
+
#<br>Returns
|
89
|
+
#* A flex array.
|
90
|
+
#<br>Note
|
91
|
+
#* The data array of the new flex array is a reference to the source array.
|
92
|
+
def self.new_from_array(other)
|
93
|
+
result = FlexArray.new(0)
|
94
|
+
result.array_specs = other.array_specs.dup
|
95
|
+
result.array_data = other.array_data
|
96
|
+
result.update_count_and_dimensions
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
#* flex_array_process.rb - The flexible array class index processing routines.
|
2
|
+
class FlexArray
|
3
|
+
private
|
4
|
+
#Process a \FlexArray index array. This is the heart of the \FlexArray
|
5
|
+
#indexing process.
|
6
|
+
#<br>Parameters
|
7
|
+
#* indexes - An array with as many entries as the flexible array has
|
8
|
+
# dimensions. See [] for more details. Note that since indexes is NOT
|
9
|
+
# a splat parameter, it must be passed as an array explicitly. If passed
|
10
|
+
# in as [:all] all elements of the array are processed.
|
11
|
+
#* block - a block to be called for each value selected by this process.
|
12
|
+
#<br>Block Arguments
|
13
|
+
#* current - An array with one element per dimension with the fully qualified
|
14
|
+
# index of the element being accessed.
|
15
|
+
#* posn - The position of the element being accessed in the data array.
|
16
|
+
def process_indexes(indexes, &block)
|
17
|
+
current = Array.new(@dimensions, 0)
|
18
|
+
|
19
|
+
if indexes == [:all]
|
20
|
+
process_all_worker(0, 0, current, &block)
|
21
|
+
else
|
22
|
+
specs = @array_specs.each
|
23
|
+
|
24
|
+
checked = indexes.collect do |index|
|
25
|
+
index.to_index_range(specs.next)
|
26
|
+
end
|
27
|
+
|
28
|
+
process_indexes_worker(0, 0, checked, current, &block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
#The worker bee for process_indexes.
|
33
|
+
#<br>Parameters
|
34
|
+
#* depth - The index of the dimension being processed.
|
35
|
+
#* posn - The partially computed position of the element being accessed
|
36
|
+
# in the data array.
|
37
|
+
#* indexes - An array of array indexes. See the method [] for details on
|
38
|
+
# what sorts of things these can be.
|
39
|
+
#* current - The partial fully qualified index of the element being accessed.
|
40
|
+
#* block - A block to be called for each value selected by this process.
|
41
|
+
#<br>Block Arguments
|
42
|
+
#* current - An array with one element per dimension with the fully qualified
|
43
|
+
# index of the element being accessed.
|
44
|
+
#* posn - The position of the element being accessed in the data array.
|
45
|
+
#<br>Note
|
46
|
+
#This is a recursive function. The recursion runs for index in 0..#dimensions
|
47
|
+
def process_indexes_worker(depth, posn, indexes, current, &block)
|
48
|
+
if depth == @dimensions
|
49
|
+
block.call(current, posn)
|
50
|
+
else
|
51
|
+
spec = @array_specs[depth]
|
52
|
+
range = indexes[depth]
|
53
|
+
posn += spec.index_step(range.min)
|
54
|
+
stride = spec.stride
|
55
|
+
|
56
|
+
range.each do |index|
|
57
|
+
current[depth] = index
|
58
|
+
process_indexes_worker(depth+1, posn, indexes, current, &block)
|
59
|
+
posn += stride
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#Special case where all of the array is being processed.
|
65
|
+
#<br>Parameters
|
66
|
+
#* block - A block to be called for each value selected by this process.
|
67
|
+
#<br>Block Arguments
|
68
|
+
#* current - An array with one element per dimension with the fully qualified
|
69
|
+
# index of the element being accessed.
|
70
|
+
#* posn - The position of the element being accessed in the data array.
|
71
|
+
def process_all(&block)
|
72
|
+
current = Array.new(@dimensions, 0)
|
73
|
+
process_all_worker(0, 0, current, &block)
|
74
|
+
end
|
75
|
+
|
76
|
+
#The worker bee for process_all.
|
77
|
+
#<br>Parameters
|
78
|
+
#* depth - The index of the dimension being processed.
|
79
|
+
#* posn - The partially computed position of the element being accessed
|
80
|
+
# in the data array.
|
81
|
+
#* current - The partial fully qualified index of the element being accessed.
|
82
|
+
#* block - A block to be called for each value selected by this process.
|
83
|
+
#<br>Block Arguments
|
84
|
+
#* current - An array with one element per dimension with the fully qualified
|
85
|
+
# index of the element being accessed.
|
86
|
+
#* posn - The position of the element being accessed in the data array.
|
87
|
+
#<br>Note
|
88
|
+
#This is a recursive function. The recursion runs for index in 0..#dimensions
|
89
|
+
def process_all_worker(depth, posn, current, &block)
|
90
|
+
if depth == @dimensions
|
91
|
+
block.call(current, posn)
|
92
|
+
else
|
93
|
+
spec = @array_specs[depth]
|
94
|
+
stride = spec.stride
|
95
|
+
|
96
|
+
spec.each do |index|
|
97
|
+
current[depth] = index
|
98
|
+
process_all_worker(depth+1, posn, current, &block)
|
99
|
+
posn += stride
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
#* flex_array_reshape.rb - The flexible array class reshape and related methods.
|
2
|
+
class FlexArray
|
3
|
+
#Return a copy of this \FlexArray, recast in a new shape, dropping or
|
4
|
+
#repeating data elements as required.
|
5
|
+
#<br>Parameters
|
6
|
+
#* array_specs - The specification of the flex array subscript limits.
|
7
|
+
# These can either be integers, in which case the limits are 0...n or
|
8
|
+
# they can be ranges of integers, in which case the limits are those
|
9
|
+
# of the range, or they can be an array of integers and/or ranges of
|
10
|
+
# integers to represent arrays of more than one dimension.
|
11
|
+
#<br>Returns
|
12
|
+
#* The reshaped flexible array.
|
13
|
+
def reshape(array_specs)
|
14
|
+
iterator = @array_data.cycle
|
15
|
+
FlexArray.new(array_specs) {iterator.next}
|
16
|
+
end
|
17
|
+
|
18
|
+
#Recast this \FlexArray in a new shape, dropping or
|
19
|
+
#repeating data elements as required.
|
20
|
+
#<br>Parameters
|
21
|
+
#* array_specs - The specification of the flex array subscript limits.
|
22
|
+
# These can either be integers, in which case the limits are 0...n or
|
23
|
+
# they can be ranges of integers, in which case the limits are those
|
24
|
+
# of the range, or they can be an array of integers and/or ranges of
|
25
|
+
# integers to represent arrays of more than one dimension.
|
26
|
+
#<br>Returns
|
27
|
+
#* The reshaped, flexible array.
|
28
|
+
def reshape!(array_specs)
|
29
|
+
iterator = @array_data.cycle
|
30
|
+
temp = FlexArray.new(array_specs) {iterator.next}
|
31
|
+
@array_specs, @array_data, @dimensions =
|
32
|
+
temp.array_specs, temp.array_data, temp.dimensions
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
#Unravel the multi-dimensional structure, and any contained arrays into
|
37
|
+
#a simple array.
|
38
|
+
def flatten
|
39
|
+
@array_data.flatten
|
40
|
+
end
|
41
|
+
|
42
|
+
#Convert the flex array to a simple array. Contained arrays are not affected.
|
43
|
+
def to_a
|
44
|
+
@array_data.dup
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#* flex_array_transpose.rb - The flexible array class transpose and related methods.
|
2
|
+
class FlexArray
|
3
|
+
#Transpose the specified dimensions. This may change the "shape" of the array
|
4
|
+
#if the transposed dimensions were of different limits.
|
5
|
+
#<br>Returns
|
6
|
+
#* The flex array transposed.
|
7
|
+
#<br>Note
|
8
|
+
#* Transposing an array disables the speed-up for processing the array with
|
9
|
+
# an index of [:all].
|
10
|
+
def transpose(dim_a, dim_b)
|
11
|
+
validate_dimension(dim_a)
|
12
|
+
validate_dimension(dim_b)
|
13
|
+
@array_specs[dim_a], @array_specs[dim_b] = @array_specs[dim_b], @array_specs[dim_a]
|
14
|
+
@transposed = true
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
#* flex_array_validate.rb - The flexible array class index validation routines.
|
2
|
+
class FlexArray
|
3
|
+
#Is this array compatible with other?
|
4
|
+
#<br>Parameters
|
5
|
+
#* other - The object being tested for compatibility.
|
6
|
+
#<br>Returns
|
7
|
+
#* true if the arrays are compatible.
|
8
|
+
def compatible?(other)
|
9
|
+
@array_specs == other.array_specs
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
#Validate the dimensionality of the indexes passed in.
|
15
|
+
#<br>Parameters
|
16
|
+
#* indexes - An array of indexes to be validated.
|
17
|
+
#<br>Returns
|
18
|
+
#* true if the indexes value is [:all] and the array is not transposed.
|
19
|
+
#<br>Exceptions
|
20
|
+
#* ArgumentError - raised on invalid dimensionality.
|
21
|
+
def validate_all_or_index_count(indexes)
|
22
|
+
if indexes == [:all]
|
23
|
+
!@transposed
|
24
|
+
elsif @dimensions != indexes.length
|
25
|
+
fail ArgumentError, "Incorrect number of indexes: #{@dimensions} expected."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
#Is this a valid dimension selector?
|
30
|
+
#<br>Exceptions
|
31
|
+
#* ArgumentError - raised on invalid dimension.
|
32
|
+
def validate_dimension(dim)
|
33
|
+
unless (0...@dimensions) === dim
|
34
|
+
fail ArgumentError, "Invalid dimesnsion selector: #{dim}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative 'spec_component'
|
2
|
+
|
3
|
+
#Extensions to Integer needed to support flex array.
|
4
|
+
class Integer
|
5
|
+
#Convert this integer to a limits component.
|
6
|
+
#<br>Parameters
|
7
|
+
#* stride - the number of cells separating data with adjacent indexes.
|
8
|
+
#<br>Returns
|
9
|
+
#* A SpecComponent object of 0...self
|
10
|
+
def to_spec_component(stride)
|
11
|
+
if self >= 0
|
12
|
+
SpecComponent.new(0...self, stride)
|
13
|
+
else
|
14
|
+
fail ArgumentError, "Invalid flex array dimension: #{self.inspect}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
#Convert this integer to an range index against the spec.
|
19
|
+
#<br>Parameters
|
20
|
+
#* spec - The spec component used to validate this index.
|
21
|
+
#<br>Returns
|
22
|
+
#* A range.
|
23
|
+
#<br>Exceptions
|
24
|
+
#* IndexError if the range is not valid.
|
25
|
+
def to_index_range(spec)
|
26
|
+
alter_ego = (self >= 0) ? self : (spec.max + self + 1)
|
27
|
+
|
28
|
+
if spec === alter_ego
|
29
|
+
alter_ego..alter_ego
|
30
|
+
else
|
31
|
+
fail IndexError, "Subscript out of range: #{self.inspect}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'spec_component'
|
2
|
+
|
3
|
+
#Extensions to Object needed to support flex array.
|
4
|
+
class Object
|
5
|
+
#Fail with message since the array dimension is invalid.
|
6
|
+
#<br>Parameters
|
7
|
+
#* _stride - unused, required to maintain signature.
|
8
|
+
#<br>Returns
|
9
|
+
#* Nothing, always fails.
|
10
|
+
def to_spec_component(_stride)
|
11
|
+
fail ArgumentError, "Invalid flex array dimension: #{self.inspect}"
|
12
|
+
end
|
13
|
+
|
14
|
+
#Convert this object to an range index against the spec.
|
15
|
+
#<br>Parameters
|
16
|
+
#* spec - The spec component used to validate this index.
|
17
|
+
#<br>Returns
|
18
|
+
#* A range.
|
19
|
+
#<br>Exceptions
|
20
|
+
#* IndexError if the range is not valid.
|
21
|
+
def to_index_range(spec)
|
22
|
+
if self == :all
|
23
|
+
spec.range
|
24
|
+
else
|
25
|
+
fail IndexError, "Invalid subscript: #{self.inspect}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require_relative 'spec_component'
|
2
|
+
|
3
|
+
#Extensions to Range needed to support flex array.
|
4
|
+
class Range
|
5
|
+
#Convert this integer to a limits component.
|
6
|
+
#<br>Parameters
|
7
|
+
#* stride - the number of cells separating data with adjacent indexes.
|
8
|
+
#<br>Returns
|
9
|
+
#* A SpecComponent object with the same range as self.
|
10
|
+
def to_spec_component(stride)
|
11
|
+
min = self.min
|
12
|
+
|
13
|
+
if self == (0...0)
|
14
|
+
SpecComponent.new(0...0, stride)
|
15
|
+
elsif !self.none? && min.is_a?(Integer) && (min >= 0) && self.max.is_a?(Integer)
|
16
|
+
SpecComponent.new(self, stride)
|
17
|
+
else
|
18
|
+
fail ArgumentError, "Invalid flex array dimension: #{self.inspect}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
#Convert this range to an range index against the spec.
|
23
|
+
#<br>Parameters
|
24
|
+
#* spec - The spec component used to validate this index.
|
25
|
+
#<br>Returns
|
26
|
+
#* A range.
|
27
|
+
#<br>Exceptions
|
28
|
+
#* IndexError if the range is not valid.
|
29
|
+
def to_index_range(spec)
|
30
|
+
self_min, self_max, spec_max = self.min, self.max, spec.max
|
31
|
+
|
32
|
+
if self_min < 0 && self_max < 0
|
33
|
+
alter_ego = (spec_max + self_min + 1)..(spec_max + self_max + 1)
|
34
|
+
else
|
35
|
+
alter_ego = self
|
36
|
+
end
|
37
|
+
|
38
|
+
if spec === alter_ego.min && spec === alter_ego.max
|
39
|
+
alter_ego
|
40
|
+
else
|
41
|
+
fail IndexError, "Subscript out of range: #{self.inspect}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|