flex_array 0.2.0
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 +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
|