n-array 0.0.1

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/narray.rb +204 -0
  3. metadata +46 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a707fc3fefdb36a781b6e95cf03bd682499310a0
4
+ data.tar.gz: e6810c8c7e1bd895f100f86d9ef05abe888e0318
5
+ SHA512:
6
+ metadata.gz: 15c6217c2d7f9fe22ed77ea15403181dc598334314173a297d87b538a26c4823d6ff21da35dd6d3014d03e17a6b20a1ae26531d09238a4ccb6efba6b9a3795b5
7
+ data.tar.gz: 4ab6714b75861f9a3bf8a473bd6ef5b97e81e94b9fd8a944a9ef509feffaaa9786a6bbe4b090dff8f5a2a3f42beea16b226f504b00175af7aef39434aa74516f
@@ -0,0 +1,204 @@
1
+ # Multidimensional array for Ruby
2
+
3
+ class NArray < Array
4
+ attr_reader :dimensions
5
+ def initialize dimensions = nil, *values, &blck
6
+ # Make a difference between no arguments and nil
7
+ values = NArray::_extract values
8
+
9
+ # In case the only parameter is a NArray, duplicate it
10
+ if dimensions.is_a? NArray and !NArray::_arg? values
11
+ self.replace dimensions.dup
12
+
13
+ # In case the parameter is an array, multiple possibilities
14
+ elsif dimensions.is_a? Array
15
+ # 1) The array decribes dimensions and provide a default value to fill in the blanks
16
+ if NArray.is_valid_description? dimensions and (NArray::_arg? values or block_given?)
17
+ # then we build the n-array recursively and fill the values with what has been provided
18
+ @dimensions = dimensions.length
19
+ @dimensions == 1 ?
20
+ # A little detour to avoid warnings on nil values
21
+ !NArray::_arg?(values) ? super(dimensions[0], &blck) : super(dimensions[0], values) :
22
+ super( [*0...dimensions[0]].map { NArray.new(dimensions.drop(1), values, &blck) } )
23
+
24
+ # 2) the array does not provide a default value
25
+ elsif !NArray::_arg? values and !block_given?
26
+ # then we create a NArray fitting the litteral given
27
+ @dimensions = NArray.count_dimensions dimensions #inefficient but GTD
28
+ @dimensions == 1 ?
29
+ super(dimensions) : # giving a block here has no effect with mri and the doc doesn't say anything
30
+ super(dimensions.map { |e| NArray.new(e) })
31
+
32
+ # 3) the array is not a valid description but a default value is given, i.e. user mistake. Scold him!
33
+ else
34
+ raise RuntimeError, "#{dimensions} is not a valid description:
35
+ An array of striclty positive Integers is expected"
36
+ end
37
+
38
+ # In case the dimension is valid
39
+ elsif NArray.is_valid_dimension? dimensions
40
+ @dimensions = dimensions
41
+ if dimensions == 1
42
+ super([], &blck)
43
+ else
44
+ super([NArray.new(dimensions - 1, values, &blck)])
45
+ end
46
+
47
+ # Empty constructor
48
+ elsif dimensions.nil? and !NArray::_arg? values
49
+ super(&blck)
50
+
51
+ # Bad user, bad
52
+ else
53
+ raise RuntimeError \
54
+ "Invalid dimension (expecting an Integer or array of Integer all strictly positives, got #{dimensions}"
55
+ end
56
+ end
57
+
58
+ # Returns an array of the lengths of each dimension
59
+ def lengths
60
+ #NArray.calculate_dimensions(self) # doesn't work for some weird reason
61
+ dimensions == 1 ? [length] : [length, *self[0].lengths]
62
+ end
63
+
64
+ # Returns the length of the dimension given as parameter
65
+ #
66
+ # Starts at 0
67
+ def length d = 0
68
+ raise "Expecting positive Integer < #{dimensions}, got #{d}" unless d < dimensions and d >= 0
69
+ d == 0 ? super() : self[0].length(d - 1)
70
+ end
71
+
72
+
73
+
74
+ # Fetch the value at the position of the arguments
75
+ #
76
+ # In case the argument contains nil values, returns a NArray of the elements
77
+ # satisfying the coordinates given by the arguments (to-do)
78
+ def [] *pos
79
+ raise "1..#{dimensions} arguments expected, given #{pos.length}" if pos.length > dimensions or pos.length == 0
80
+ pos.length == 1 ? super(*pos) : super(pos[0])[*pos.drop(1)]
81
+ end
82
+
83
+ # Sets the value at the position of the arguments
84
+ def []= *pos, v
85
+ raise "#{dimensions} arguments expected, given #{pos.length}" if pos.length != dimensions
86
+ pos.length == 1 ? super(*pos, v) : self[pos[0]][*pos.drop(1)] = v
87
+ end
88
+
89
+ # Returns the total number of elements in the n-array
90
+ def size
91
+ lengths.reduce(&:*)
92
+ end
93
+
94
+ # Iterate over the elements of the n-array applying the given block to each element
95
+ def each &blck
96
+ if dimensions > 1
97
+ super() do |e|
98
+ e.each(&blck)
99
+ end
100
+ else
101
+ super(&blck)
102
+ end
103
+ end
104
+
105
+ # Iterate over the elements of the n-array apply the given bloc and collect
106
+ # the results into a nested collection of arrays identical to the structure of the caller
107
+ def map &blck
108
+ if dimensions > 1
109
+ super() do |e|
110
+ e.map(&blck)
111
+ end
112
+ else
113
+ super(&blck)
114
+ end
115
+ end
116
+ # See #map
117
+ def collect
118
+ map
119
+ end
120
+
121
+ class << self
122
+ # Returns the number of dimensions that can be generated from the argument while keeping the array well-formed
123
+ #
124
+ # Checks the maximum level of nesting so that any n-vector {v1, v2...vn} with 0 <= vm < length(m)
125
+ # correctly refers to an element in the structure
126
+ def count_dimensions array
127
+ array.class == NArray ? array.dimensions : calculate_dimensions(array).length
128
+ end
129
+
130
+ # Returns an array of lengths for an array (only works with Array, don't ask me why)
131
+ #
132
+ # Each dimension in a n-array has a maximum size, those are collected and ordered into an array,
133
+ # the first being the top array, and the last the deepest group
134
+ def calculate_dimensions array
135
+ _count(array).take_while { |e| e >= 0 }
136
+ end
137
+
138
+
139
+ # Check whether the argument is a valid dimension
140
+ #
141
+ # Returns true if dimensions is a strictly positive Integer, false otherwise
142
+ def is_valid_dimension? dimensions
143
+ dimensions.is_a? Integer and dimensions > 0
144
+ end
145
+
146
+ # Check whether the argument is a valid description of dimensions
147
+ #
148
+ # Returns true if the argument is an Array of values satisfying is_valid_dimension?, false otherwise
149
+ def is_valid_description? dimensions
150
+ dimensions.is_a? Array and dimensions.all? { |e| is_valid_dimension? e }
151
+ end
152
+
153
+ # Create a n-array fitting the given description
154
+ def[] *description
155
+ NArray.new(description)
156
+ end
157
+
158
+ private
159
+ def _count array, n = [], d = 0
160
+ unless array.is_a? Array or array.is_a? NArray # if not an array, set the dimension as beeing incoherent
161
+ n[d] = -1
162
+ return n # and abort
163
+ end
164
+
165
+ # if length <= depth it means this dimension hasn't been explored yet,
166
+ # so we set it at the first value we encounter, which will not change
167
+ # if the array is well formed
168
+ n << array.length if n.length <= d
169
+
170
+ if array.length != n[d] #or n[d] < 0 # the second part should never
171
+ # be executed since array.length >= 0 (
172
+ # if n[d] < 0 then the first test automatically fails)
173
+ # The dimension is in an incoherent state, abort
174
+ n[d] = -1
175
+ return n
176
+ end
177
+
178
+ # At this point the array is still in a coherent state,
179
+ # we just need to check sub elements unless we already know
180
+ # that we can't proceed further because of previous results
181
+ array.each do |e|
182
+ if n.length > d + 1 and n[d + 1] < 0 # In case the next element has
183
+ # already been decided to be incoherent
184
+ return n # Abort
185
+ end
186
+ _count e, n, d + 1
187
+ end
188
+ n
189
+ end
190
+
191
+ public
192
+ def _extract args
193
+ raise ParameterError, "Expected 1..2 arguments, got #{args.length + 1}" if args.length > 1
194
+ args.length == 1 ? args[0] : NArray::EmptyArgument.new
195
+ end
196
+
197
+ def _arg? arg
198
+ !arg.is_a? NArray::EmptyArgument
199
+ end
200
+ end
201
+ private
202
+ class EmptyArgument
203
+ end
204
+ end
metadata ADDED
@@ -0,0 +1,46 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: n-array
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Sylvain Leclercq
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-11-02 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Manipulating nested arrays in Ruby is a pain and there doesn't seem to
14
+ be any general purpose solutions (i.e. not math-oriented) available so here is a
15
+ shot at it.
16
+ email: maisbiensurqueoui@gmail.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/narray.rb
22
+ homepage: http://www.github.com/de-passage/narray
23
+ licenses:
24
+ - MIT
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 2.4.8
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: A n-dimensional storage structure
46
+ test_files: []