flex_array 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,18 @@
1
+ #* flex_array_forever.rb - Forever looping support for the flexible array class.
2
+ class FlexArray
3
+ #A helper class that encapsulates the idea of looping without end!
4
+ class ForEver
5
+ #Do something until false == true ;-)
6
+ def times(&block)
7
+ loop {block.call}
8
+ end
9
+
10
+ #Convert an eternity into an integer. ;-)
11
+ def to_i
12
+ nil
13
+ end
14
+ end
15
+
16
+ #Create a forever constant for use where infinite looping is needed.
17
+ FOREVER = ForEver.new
18
+ end
@@ -2,20 +2,20 @@
2
2
  class FlexArray
3
3
  #Retrieve the selected data from the \FlexArray.
4
4
  #<br>Parameters
5
- #* indexes - An array with as many entries as the flexible array has
5
+ #* indexes - An array with as many entries as the flexible array has
6
6
  # dimensions. These entries may be an integer that is in the bounds of
7
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
8
+ # and end of the range are in the bounds of the range limit of that
9
9
  # dimension, or it can be the symbol :all for all of the possible values
10
10
  # of that dimension.
11
11
  #<br>Returns
12
12
  #* The data selected by the index or an array of data if the index selects
13
- # more than one cell.
14
- #<br>Note
13
+ # more than one cell.
14
+ #<br>Note
15
15
  #* If the indexes parameter equals a single :all this maps to all
16
16
  # elements of the array, regardless of its dimensionality.
17
17
  def [](*indexes)
18
- validate_all_or_index_count(indexes)
18
+ validate_index_count(indexes)
19
19
  result = []
20
20
  process_indexes(indexes) {|_index, posn| result << @array_data[posn]}
21
21
  result.length == 1 ? result[0] : result
@@ -23,20 +23,22 @@ class FlexArray
23
23
 
24
24
  #Store the value data into the \FlexArray.
25
25
  #<br>Parameters
26
- #* indexes - An array with as many entries as the flexible array has
26
+ #* indexes - An array with as many entries as the flexible array has
27
27
  # dimensions. These entries may be an integer that is in the bounds of
28
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
29
+ # and end of the range are in the bounds of the range limit of that
30
30
  # dimension, or it can be the symbol :all for all of the possible values
31
- # of that dimension.
31
+ # of that dimension.
32
32
  #* value - A value to be stored in the selected array entries.
33
33
  #<br>Returns
34
34
  #* The value stored in the array.
35
- #<br>Note
35
+ #<br>Note
36
36
  #* If the indexes parameter equals a single :all this maps to all
37
37
  # elements of the array, regardless of its dimensionality.
38
38
  def []=(*indexes, value)
39
- validate_all_or_index_count(indexes)
40
- process_indexes(indexes) {|_index, posn| @array_data[posn] = value}
39
+ validate_index_count(indexes)
40
+ source = value.in_array.cycle
41
+ process_indexes(indexes) {|_index, posn| @array_data[posn] = source.next}
42
+ value
41
43
  end
42
44
  end
@@ -2,34 +2,26 @@
2
2
  class FlexArray
3
3
  #Construct a flexible array object.
4
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
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
9
  # integers to represent arrays of more than one dimension.
10
10
  #* default - The optional default value. Defaults to nil.
11
11
  #* init_block - An optional initialization block. The return values from this
12
12
  # block are used to initialize the array. This overrides the default value.
13
13
  #<br>Block Arguments
14
- #* index - An array with one element per dimension with the fully qualified
14
+ #* index - An array with one element per dimension with the fully qualified
15
15
  # index of the element being accessed. Note that the same array is passed
16
16
  # in each time the block is called, so, if this parameter is to stored
17
17
  # anywhere, it's best that it be cloned or duplicated first.
18
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
19
+ @array_specs = SpecArray.new(array_specs.in_array)
20
+ @transposed = false
29
21
 
30
22
  #Allocate the data for the array.
31
- @array_data = Array.new(@count, default)
32
-
23
+ @array_data = Array.new(@array_specs.spec_count, default)
24
+
33
25
  #Set up the array with the optional init_block.
34
26
  if init_block
35
27
  process_all {|index, posn| @array_data[posn] = init_block.call(index)}
@@ -39,6 +31,10 @@ class FlexArray
39
31
  alias_method :shallow_dup, :dup
40
32
 
41
33
  #Create a duplicate of this array.
34
+ #<br>Warning
35
+ #* The rdoc tool messes this up. shallow_dup is NOT an alias for this
36
+ # dup, but rather the original system implementation of dup. This dup,
37
+ # overrides the default dup and calls shallow_dup as part of its processing.
42
38
  def dup
43
39
  other = self.shallow_dup
44
40
  other.array_specs = @array_specs.dup
@@ -48,12 +44,12 @@ class FlexArray
48
44
 
49
45
  #Construct a \FlexArray using other as a template or data source.
50
46
  #<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
47
+ #* array_specs - The specification of the flex array subscript limits.
48
+ # These can either be integers, in which case the limits are 0...n or
49
+ # they can be ranges of integers, in which case the limits are those
50
+ # of the range, or they can be an array of integers and/or ranges of
55
51
  # integers to represent arrays of more than one dimension.
56
- #* other - The array or flex array that is the source of the data.
52
+ #* other - The array or flex array that is the source of the data.
57
53
  #<br>Note:
58
54
  #* To make a copy of a flex array, use dup instead.
59
55
  def self.new_from(array_specs, other)
@@ -61,27 +57,27 @@ class FlexArray
61
57
  FlexArray.new(array_specs) {iterator.next}
62
58
  end
63
59
 
64
- #Construct a \FlexArray using a all or a portion of portion of another
60
+ #Construct a \FlexArray using a all or a portion of portion of another
65
61
  #\FlexArray as a data source.
66
62
  #<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
63
+ #* array_specs - The specification of the flex array subscript limits.
64
+ # These can either be integers, in which case the limits are 0...n or
65
+ # they can be ranges of integers, in which case the limits are those
66
+ # of the range, or they can be an array of integers and/or ranges of
71
67
  # 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
68
+ #* other - The flex array source of the data used to build this flex
69
+ # array. If no limits or other_indexes are specified, the limits of the
74
70
  # other array are used instead.
75
- #* selection - The indexes of the required sub-set of data to use
71
+ #* selection - The indexes of the required sub-set of data to use
76
72
  # in setting up the new array.
77
73
  #<br>Notes:
78
- #* If the entire source array is to be used, use new_from instead.
74
+ #* If the entire source array is to be used, use new_from instead.
79
75
  #* To make a copy of a flex array, use dup instead.
80
76
  def self.new_from_selection(array_specs, other, selection)
81
- iterator = other.cycle(selection)
77
+ iterator = other.select_cycle(selection)
82
78
  FlexArray.new(array_specs) {iterator.next}
83
79
  end
84
-
80
+
85
81
  #Construct a \FlexArray as a duplicate of a source array or flex array.
86
82
  #<br>Parameters
87
83
  #* other - the array or flex array that is the source.
@@ -93,6 +89,6 @@ class FlexArray
93
89
  result = FlexArray.new(0)
94
90
  result.array_specs = other.array_specs.dup
95
91
  result.array_data = other.array_data
96
- result.update_count_and_dimensions
92
+ result
97
93
  end
98
94
  end
@@ -4,27 +4,27 @@ class FlexArray
4
4
  #Process a \FlexArray index array. This is the heart of the \FlexArray
5
5
  #indexing process.
6
6
  #<br>Parameters
7
- #* indexes - An array with as many entries as the flexible array has
7
+ #* indexes - An array with as many entries as the flexible array has
8
8
  # dimensions. See [] for more details. Note that since indexes is NOT
9
9
  # a splat parameter, it must be passed as an array explicitly. If passed
10
10
  # in as [:all] all elements of the array are processed.
11
11
  #* block - a block to be called for each value selected by this process.
12
12
  #<br>Block Arguments
13
- #* current - An array with one element per dimension with the fully qualified
14
- # index of the element being accessed.
13
+ #* current - An array with one element per dimension with the fully qualified
14
+ # index of the element being accessed.
15
15
  #* posn - The position of the element being accessed in the data array.
16
16
  def process_indexes(indexes, &block)
17
- current = Array.new(@dimensions, 0)
18
-
17
+ current = Array.new(dimensions, 0)
18
+
19
19
  if indexes == [:all]
20
20
  process_all_worker(0, 0, current, &block)
21
21
  else
22
22
  specs = @array_specs.each
23
-
23
+
24
24
  checked = indexes.collect do |index|
25
25
  index.to_index_range(specs.next)
26
26
  end
27
-
27
+
28
28
  process_indexes_worker(0, 0, checked, current, &block)
29
29
  end
30
30
  end
@@ -32,71 +32,76 @@ class FlexArray
32
32
  #The worker bee for process_indexes.
33
33
  #<br>Parameters
34
34
  #* depth - The index of the dimension being processed.
35
- #* posn - The partially computed position of the element being accessed
35
+ #* posn - The partially computed position of the element being accessed
36
36
  # in the data array.
37
- #* indexes - An array of array indexes. See the method [] for details on
37
+ #* indexes - An array of array indexes. See the method [] for details on
38
38
  # what sorts of things these can be.
39
39
  #* current - The partial fully qualified index of the element being accessed.
40
40
  #* block - A block to be called for each value selected by this process.
41
41
  #<br>Block Arguments
42
- #* current - An array with one element per dimension with the fully qualified
43
- # index of the element being accessed.
42
+ #* current - An array with one element per dimension with the fully qualified
43
+ # index of the element being accessed.
44
44
  #* posn - The position of the element being accessed in the data array.
45
45
  #<br>Note
46
46
  #This is a recursive function. The recursion runs for index in 0..#dimensions
47
+ #<br>Endemic Code Smells
48
+ # :reek:LongParameterList
47
49
  def process_indexes_worker(depth, posn, indexes, current, &block)
48
- if depth == @dimensions
49
- block.call(current, posn)
50
+ if depth == dimensions #Is there more work to do?
51
+ block.call(current, posn) #Index ready, call the block.
50
52
  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
53
+ spec = @array_specs[depth] #Get the current specification.
54
+ min, stride = spec.min, spec.stride #Extract the relevant info.
55
+
56
+ indexes[depth].each do |index| #Iterate over the range.
57
+ current[depth] = index #Update the current index.
58
+ #Process the next component in the array index.
59
+ process_indexes_worker(depth+1,
60
+ posn + (index-min) * stride,
61
+ indexes,
62
+ current,
63
+ &block)
60
64
  end
61
65
  end
62
66
  end
63
-
67
+
64
68
  #Special case where all of the array is being processed.
65
69
  #<br>Parameters
66
70
  #* block - A block to be called for each value selected by this process.
67
71
  #<br>Block Arguments
68
- #* current - An array with one element per dimension with the fully qualified
69
- # index of the element being accessed.
72
+ #* current - An array with one element per dimension with the fully qualified
73
+ # index of the element being accessed.
70
74
  #* posn - The position of the element being accessed in the data array.
71
75
  def process_all(&block)
72
- current = Array.new(@dimensions, 0)
76
+ current = Array.new(dimensions, 0)
73
77
  process_all_worker(0, 0, current, &block)
74
78
  end
75
-
79
+
76
80
  #The worker bee for process_all.
77
81
  #<br>Parameters
78
82
  #* depth - The index of the dimension being processed.
79
- #* posn - The partially computed position of the element being accessed
83
+ #* posn - The partially computed position of the element being accessed
80
84
  # in the data array.
81
85
  #* current - The partial fully qualified index of the element being accessed.
82
86
  #* block - A block to be called for each value selected by this process.
83
87
  #<br>Block Arguments
84
- #* current - An array with one element per dimension with the fully qualified
85
- # index of the element being accessed.
88
+ #* current - An array with one element per dimension with the fully qualified
89
+ # index of the element being accessed.
86
90
  #* posn - The position of the element being accessed in the data array.
87
91
  #<br>Note
88
92
  #This is a recursive function. The recursion runs for index in 0..#dimensions
89
93
  def process_all_worker(depth, posn, current, &block)
90
- if depth == @dimensions
91
- block.call(current, posn)
94
+ if depth == dimensions #Is there more work to do?
95
+ block.call(current, posn) #Index ready, call the block.
92
96
  else
93
- spec = @array_specs[depth]
97
+ spec = @array_specs[depth] #Get the current specification.
94
98
  stride = spec.stride
95
-
96
- spec.each do |index|
97
- current[depth] = index
99
+
100
+ spec.each do |index| #Iterate over the range.
101
+ current[depth] = index #Update the current index.
102
+ #Process the next component in the array specification.
98
103
  process_all_worker(depth+1, posn, current, &block)
99
- posn += stride
104
+ posn += stride #Step to the next position.
100
105
  end
101
106
  end
102
107
  end
@@ -1,12 +1,12 @@
1
1
  #* flex_array_reshape.rb - The flexible array class reshape and related methods.
2
2
  class FlexArray
3
- #Return a copy of this \FlexArray, recast in a new shape, dropping or
3
+ #Return a copy of this \FlexArray, recast in a new shape, dropping or
4
4
  #repeating data elements as required.
5
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
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
10
  # integers to represent arrays of more than one dimension.
11
11
  #<br>Returns
12
12
  #* The reshaped flexible array.
@@ -15,31 +15,23 @@ class FlexArray
15
15
  FlexArray.new(array_specs) {iterator.next}
16
16
  end
17
17
 
18
- #Recast this \FlexArray in a new shape, dropping or
18
+ #Recast this \FlexArray in a new shape, dropping or
19
19
  #repeating data elements as required.
20
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
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
25
  # integers to represent arrays of more than one dimension.
26
26
  #<br>Returns
27
27
  #* The reshaped, flexible array.
28
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
29
+ temp = self.reshape(array_specs)
30
+ @array_specs, @array_data = temp.array_specs, temp.array_data
33
31
  self
34
32
  end
35
33
 
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.
34
+ #Convert the flex array to a simple array. Contained arrays are not affected.
43
35
  def to_a
44
36
  @array_data.dup
45
37
  end
@@ -7,11 +7,24 @@ class FlexArray
7
7
  #<br>Note
8
8
  #* Transposing an array disables the speed-up for processing the array with
9
9
  # an index of [:all].
10
- def transpose(dim_a, dim_b)
10
+ def transpose!(dim_a, dim_b)
11
11
  validate_dimension(dim_a)
12
12
  validate_dimension(dim_b)
13
13
  @array_specs[dim_a], @array_specs[dim_b] = @array_specs[dim_b], @array_specs[dim_a]
14
- @transposed = true
14
+ @transposed = @array_specs.transposed?
15
15
  self
16
16
  end
17
+
18
+ #Return a reference to this array's data with the specified dimensions
19
+ #transposed. This may change the "shape" of the array if the transposed
20
+ #dimensions were of different limits.
21
+ #<br>Returns
22
+ #* The flex array transposed.
23
+ #<br>Note
24
+ #* Transposing an array disables the speed-up for processing the array with
25
+ # an index of [:all].
26
+ def transpose(dim_a, dim_b)
27
+ FlexArray.new_from_array(self).transpose!(dim_a, dim_b)
28
+ end
29
+
17
30
  end
@@ -7,31 +7,31 @@ class FlexArray
7
7
  #* true if the arrays are compatible.
8
8
  def compatible?(other)
9
9
  @array_specs == other.array_specs
10
+ rescue Exception
11
+ false
10
12
  end
11
13
 
12
14
  private
13
-
15
+
14
16
  #Validate the dimensionality of the indexes passed in.
15
17
  #<br>Parameters
16
18
  #* 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
19
  #<br>Exceptions
20
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."
21
+ def validate_index_count(indexes)
22
+ unless indexes == [:all]
23
+ if dimensions != indexes.length
24
+ fail ArgumentError, "Incorrect number of indexes: #{dimensions} expected."
25
+ end
26
26
  end
27
27
  end
28
-
28
+
29
29
  #Is this a valid dimension selector?
30
30
  #<br>Exceptions
31
31
  #* ArgumentError - raised on invalid dimension.
32
32
  def validate_dimension(dim)
33
- unless (0...@dimensions) === dim
34
- fail ArgumentError, "Invalid dimesnsion selector: #{dim}"
33
+ unless (0...dimensions) === dim
34
+ fail ArgumentError, "Invalid dimension selector: #{dim}"
35
35
  end
36
36
  end
37
37
  end