kxi 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,107 @@
1
+ # Created by Matyáš Pokorný on 2017-12-28.
2
+
3
+ module KXI
4
+ module Collections
5
+ # Makes array enumerable
6
+ class ArrayCollection < KXI::Collections::Enumerable
7
+ # Instantiates the {KXI::Collections::ArrayCollection} class
8
+ # @param array [Array] Array for enumeration
9
+ def initialize(array = [])
10
+ super()
11
+ @arr = array
12
+ end
13
+
14
+ # Creates a new {KXI::Collections::Enumerator} bound to this instance
15
+ # @return [KXI::Collections::Enumerator] Enumerator bound to this instance
16
+ def create_enumerator
17
+ ArrayEnumerator.new(@arr)
18
+ end
19
+
20
+ # Obtains value in array at specific index
21
+ # @param index [Number] Index in array to obtain
22
+ # @return [Object] Value in array at specified index
23
+ # @raise [KXI::Exceptions::OutOfRangeException] Raised when given index is out of range of array
24
+ def [](index)
25
+ raise(KXI::Exceptions::OutOfRangeException.new(index)) if @arr.length == 0
26
+ raise(KXI::Exceptions::OutOfRangeException.new(index, 0, @arr.length - 1)) if index < 0 or index >= @arr.length
27
+ lock do
28
+ return @arr[index]
29
+ end
30
+ end
31
+
32
+ # Sets value of array at specific index
33
+ # @param index [Number] Index in array to set
34
+ # @param value [Object] Value to set
35
+ # @return [Object] Set value
36
+ # @raise [KXI::Exceptions::OutOfRangeException] Raised when given index is out of range of array
37
+ # @raise [KXI::Exceptions::CollectionException] Raised when collection cannot be locked for writing
38
+ def []=(index, value)
39
+ raise(KXI::Exceptions::OutOfRangeException.new(index)) if @arr.length == 0
40
+ raise(KXI::Exceptions::OutOfRangeException.new(index, 0, @arr.length - 1)) if index < 0 or index >= @arr.length
41
+ lock(true) do
42
+ @arr[index] = value
43
+ end
44
+ return value
45
+ end
46
+
47
+ # Adds value into array
48
+ # @param value [Object] Value to set
49
+ # @return [Number] Assigned index
50
+ # @raise [KXI::Exceptions::CollectionException] Raised when collection cannot be locked for writing
51
+ def add(value)
52
+ idx = @arr.length
53
+ lock(true) do
54
+ @arr.push(value)
55
+ end
56
+ return idx
57
+ end
58
+
59
+ # Removes item at specific index
60
+ # @param index [Number] Index to remove
61
+ # @return [Object] Removed item
62
+ # @raise [KXI::Exceptions::OutOfRangeException] Raised when given index is out of range of array
63
+ # @raise [KXI::Exceptions::CollectionException] Raised when collection cannot be locked for writing
64
+ def remove_at(index)
65
+ raise(KXI::Exceptions::OutOfRangeException.new(index)) if @arr.length == 0
66
+ raise(KXI::Exceptions::OutOfRangeException.new(index, 0, @arr.length - 1)) if index < 0 or index >= @arr.length
67
+ ret = nil
68
+ lock(true) do
69
+ ret = @arr.delete_at(index)
70
+ end
71
+ return ret
72
+ end
73
+
74
+ # Enumerates array
75
+ class ArrayEnumerator < KXI::Collections::Enumerator
76
+ # Instantiates the {KXI::Collections::ArrayCollection::ArrayEnumerator} class
77
+ # @param array [Array] Array for enumeration
78
+ def initialize(array)
79
+ @arr = array
80
+ @current = 0
81
+ end
82
+
83
+ # Selects first item in collection
84
+ # @return [Bool] True if collection contains elements; otherwise false
85
+ def rewind
86
+ @current = 0
87
+ return @arr.length > 0
88
+ end
89
+
90
+ # Advances enumerator to next item
91
+ # @return [Bool] True if item is available; false otherwise
92
+ def next
93
+ @current = @current + 1
94
+ return @current < @arr.length
95
+ end
96
+
97
+ # Returns current item
98
+ # @return [Object] Current item
99
+ def current
100
+ @arr[@current]
101
+ end
102
+ end
103
+
104
+ private :create_enumerator
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,528 @@
1
+ # Created by Matyáš Pokorný on 2017-12-28.
2
+
3
+ module KXI
4
+ module Collections
5
+ # Allows it's superclass to be enumerated
6
+ # @note Order of items is not guaranteed
7
+ # @abstract
8
+ class Enumerable
9
+ # Instantiates the {KXI::Collections::Enumerable} class
10
+ def initialize
11
+ @locks = 0
12
+ @alter = false
13
+ end
14
+
15
+ # Allows protected usage of {KXI::Collections::Enumerator} within bounds of block
16
+ # @return [nil]
17
+ # @yield [enumerator] Protected block of code
18
+ # @yieldparam enumerator [KXI::Collections::Enumerator] Enumerator bound to this instance
19
+ # @yieldreturn [void]
20
+ # @raise [KXI::Exceptions::AbstractException] When method {#create_enumerator} is not implemented in superclass
21
+ def enumerator
22
+ lock do
23
+ yield(create_enumerator)
24
+ end
25
+ return nil
26
+ end
27
+
28
+ # Creates a new {KXI::Collections::Enumerator} bound to this instance
29
+ # @return [KXI::Collections::Enumerator] Enumerator bound to this instance
30
+ # @raise [KXI::Exceptions::AbstractException] When method is not implemented in superclass
31
+ # @abstract
32
+ def create_enumerator
33
+ raise(KXI::Exceptions::AbstractException.new(Enumerable))
34
+ end
35
+
36
+ # Calls block with each item of collection
37
+ # @return [nil]
38
+ # @yield [item] Function that works with items of collection
39
+ # @yieldparam item [Object] Item of collection
40
+ # @yieldreturn [Void]
41
+ def foreach
42
+ enumerator do |e|
43
+ return nil unless e.rewind
44
+ loop do
45
+ yield(e.current)
46
+ break unless e.next
47
+ end
48
+ end
49
+ return nil
50
+ end
51
+
52
+ # Selects specific values
53
+ # @return [KXI::Collections::ArrayCollection] Collection of selected values
54
+ # @yield [item] Function that selects values from items of collection
55
+ # @yieldparam item [Object] Item of collection
56
+ # @yieldreturn [Object] Selected value from item
57
+ def select
58
+ ret = []
59
+ foreach do |i|
60
+ ret.push(yield(i))
61
+ end
62
+ return KXI::Collections::ArrayCollection.new(ret)
63
+ end
64
+
65
+ # Concatenates selected collections of items
66
+ # @return [KXI::Collections::ArrayCollection] Collection of selected values
67
+ # @yield [item] Function that selects collections of items from given items
68
+ # @yieldparam item [Object] Item of collection
69
+ # @yieldreturn [Array, KXI::Collections::Enumerable] Selected collection of items
70
+ # @raise [KXI::Exceptions::CollectionException] Raised if selection function returns object that is not of type {KXI::Collections::Enumerable} nor Array
71
+ def select_many
72
+ ret = []
73
+ foreach do |i|
74
+ col = yield(i)
75
+ if col.is_a?(Array)
76
+ col.each { |j| ret.push(j) }
77
+ elsif col.is_a?(Enumerable)
78
+ col.foreach { |j| ret.push(j) }
79
+ else
80
+ raise(KXI::Exceptions::CollectionException.new('Selection function returned invalid type of object!'))
81
+ end
82
+ end
83
+ return KXI::Collections::ArrayCollection.new(ret)
84
+ end
85
+
86
+ # Selects only those items that satisfy given function
87
+ # @return [KXI::Collections::ArrayCollection] Collection of selected items
88
+ # @yield [item] Function that evaluates whether item should be selected
89
+ # @yieldparam item [Object] Item of collection
90
+ # @yieldreturn [Bool] True if item should be selected; otherwise false
91
+ def where
92
+ ret = []
93
+ foreach do |i|
94
+ ret.push(i) if yield(i)
95
+ end
96
+ return KXI::Collections::ArrayCollection.new(ret)
97
+ end
98
+
99
+ # Counts items in collection
100
+ # @return [Number] Number of items
101
+ # @overload count()
102
+ # Counts the total number of items in collection
103
+ # @return [Number] Number of items in collection
104
+ # @overload count()
105
+ # Counts the number of items that satisfy given function
106
+ # @return [Number] Number of items that satisfy given function
107
+ # @yield [item] Function that evaluates whether item should be counted
108
+ # @yieldparam item [Object] Item of collection
109
+ # @yieldreturn [Bool] True if item should be counted; otherwise false
110
+ def count
111
+ count = 0
112
+ if block_given?
113
+ foreach do |item|
114
+ count = count + 1 if yield(item)
115
+ end
116
+ else
117
+ enumerator do |e|
118
+ return 0 unless e.rewind
119
+ count += 1
120
+ while e.next
121
+ count = count + 1
122
+ end
123
+ end
124
+ end
125
+ return count
126
+ end
127
+
128
+ # Checks if collection contains any item
129
+ # @return [Bool] True if collection contains any item; otherwise false
130
+ # @overload any()
131
+ # Checks if collection is not empty
132
+ # @return [Bool] True if collection is not empty; otherwise false
133
+ # @overload any()
134
+ # Checks if there is any item in collection that satisfies given function
135
+ # @return [Bool] True if collection contains item that satisfies given function; otherwise false
136
+ # @yield [item] Function used for checking
137
+ # @yieldparam item [Object] Item of collection
138
+ # @yieldreturn [Bool] True if item satisfies given function; otherwise false
139
+ def any
140
+ if block_given?
141
+ foreach do |item|
142
+ return true if yield(item)
143
+ end
144
+ return false
145
+ else
146
+ enumerator do |e|
147
+ return false unless e.rewind
148
+ return true
149
+ end
150
+ end
151
+ end
152
+
153
+ # Checks if all items in collection satisfy given function
154
+ # @return [Bool] True if all items of collection satisfy given function; otherwise false
155
+ # @yield [item] Function used for checking
156
+ # @yieldparam item [Object] Item of collection
157
+ # @yieldreturn [Bool] True if item satisfies given function; otherwise false
158
+ def all
159
+ foreach do |item|
160
+ return false unless yield(item)
161
+ end
162
+ return true
163
+ end
164
+
165
+ # Gets first item, or default
166
+ # @overload first(df = nil)
167
+ # Gets first item of collection
168
+ # @param df [Object, nil] Default value
169
+ # @return [Object, nil] First item of collection; default value if collection is empty
170
+ # @overload first(df = nil)
171
+ # Gets first item of collection that satisfies given function
172
+ # @param df [Object, nil] Default value
173
+ # @return [Object, nil] First item of collection that satisfies given function; default value if no such item exists
174
+ # @yield [item] Function for evaluation
175
+ # @yieldparam item [Object] Item of collection
176
+ # @yieldreturn [Bool] True if item satisfies given function; false otherwise
177
+ def first(df = nil)
178
+ if block_given?
179
+ foreach do |item|
180
+ return item if yield(item)
181
+ end
182
+ else
183
+ enumerator do |e|
184
+ return e.current if e.rewind
185
+ end
186
+ end
187
+ return df
188
+ end
189
+
190
+
191
+ # Gets first item
192
+ # @overload first!()
193
+ # Gets first item of collection
194
+ # @return [Object, nil] First item of collection
195
+ # @raise [KXI::Exceptions::CollectionException] Raised when collection is empty
196
+ # @overload first!()
197
+ # Gets first item of collection that satisfies given function
198
+ # @return [Object, nil] First item of collection that satisfies given function
199
+ # @raise [KXI::Exceptions::CollectionException] Raised when collection contains no element that satisfies given function
200
+ # @yield [item] Function for evaluation
201
+ # @yieldparam item [Object] Item of collection
202
+ # @yieldreturn [Bool] True if item satisfies given function; false otherwise
203
+ def first!
204
+ if block_given?
205
+ foreach do |item|
206
+ return item if yield(item)
207
+ end
208
+ else
209
+ enumerator do |e|
210
+ return e.current if e.rewind
211
+ end
212
+ end
213
+ raise(KXI::Exceptions::CollectionException.new("Collection contains no #{(block_given? ? 'such ' : '')}items!"))
214
+ end
215
+
216
+ # Gets last item, or default
217
+ # @overload last(df = nil)
218
+ # Gets last item of collection
219
+ # @param df [Object, nil] Default value
220
+ # @return [Object, nil] Last item of collection; default value if collection is empty
221
+ # @overload last(df = nil)
222
+ # Gets last item of collection that satisfies given function
223
+ # @param df [Object, nil] Default value
224
+ # @return [Object, nil] Last item of collection that satisfies given function; default value if no such item exists
225
+ # @yield [item] Function for evaluation
226
+ # @yieldparam item [Object] Item of collection
227
+ # @yieldreturn [Bool] True if item satisfies given function; false otherwise
228
+ def last(df = nil)
229
+ found = false
230
+ ret = nil
231
+ if block_given?
232
+ foreach do |item|
233
+ if yield(item)
234
+ ret = item
235
+ found = true
236
+ end
237
+ end
238
+ else
239
+ foreach do |item|
240
+ ret = item
241
+ found = true
242
+ end
243
+ end
244
+ return df unless found
245
+ return ret
246
+ end
247
+
248
+ # Gets last item
249
+ # @overload last!()
250
+ # Gets last item of collection
251
+ # @return [Object, nil] Last item of collection
252
+ # @raise [KXI::Exceptions::CollectionException] Raised if collection is empty
253
+ # @overload last!()
254
+ # Gets last item of collection that satisfies given function
255
+ # @return [Object, nil] Last item of collection that satisfies given function
256
+ # @raise [KXI::Exceptions::CollectionException] Raised when collection doesn't contain element that satisfies given function
257
+ # @yield [item] Function for evaluation
258
+ # @yieldparam item [Object] Item of collection
259
+ # @yieldreturn [Bool] True if item satisfies given function; false otherwise
260
+ def last!
261
+ found = false
262
+ ret = nil
263
+ if block_given?
264
+ foreach do |item|
265
+ if yield(item)
266
+ ret = item
267
+ found = true
268
+ end
269
+ end
270
+ else
271
+ foreach do |item|
272
+ ret = item
273
+ found = true
274
+ end
275
+ end
276
+ raise(KXI::Exceptions::CollectionException.new("Collection contains no #{(block_given? ? 'such ' : '')}items!")) unless found
277
+ return ret
278
+ end
279
+
280
+ # Skips given number of items and returns the rest
281
+ # @param count [Number] Number of items to skip
282
+ # @return [KXI::Collections::ArrayCollection] Collection of items that weren't skipped
283
+ def skip(count)
284
+ ret = []
285
+ foreach do |item|
286
+ if count > 0
287
+ count -= 1
288
+ else
289
+ ret.push(item)
290
+ end
291
+ end
292
+ return KXI::Collections::ArrayCollection.new(ret)
293
+ end
294
+
295
+ # Returns first N elements
296
+ # @param count [Number] Number of items to take
297
+ # @return [KXI::Collections::ArrayCollection] Collection of first N items
298
+ def take(count)
299
+ ret = []
300
+ foreach do |item|
301
+ break unless count > 0
302
+ count -= 1
303
+ ret.push(item)
304
+ end
305
+ return KXI::Collections::ArrayCollection.new(ret)
306
+ end
307
+
308
+ # Aggregates all items of collection
309
+ # @param start [Object, nil] Starting value
310
+ # @return [Object] Aggregated value
311
+ # @yield [current, item] Aggregation function
312
+ # @yieldparam current [Object] Current aggregate
313
+ # @yieldparam item [Object] Item of collection
314
+ # @yieldreturn [Object] Aggregated value
315
+ def aggregate(start = nil)
316
+ foreach do |item|
317
+ start = yield(start, item)
318
+ end
319
+ return start
320
+ end
321
+
322
+ # Orders collection to ascending order
323
+ # @note Ordering is done using merge sort algorithm
324
+ # @return [KXI::Collections::ArrayCollection] Ordered collection
325
+ # @overload order_by()
326
+ # Orders collection using spaceship operator (<=>)
327
+ # @return [KXI::Collections::ArrayCollection] Ordered collection
328
+ # @overload order_by()
329
+ # Orders collection using given comparison function
330
+ # @return [KXI::Collections::ArrayCollection] Ordered collection
331
+ # @yield [what, with] Comparison function
332
+ # @yieldparam what [Object] Object that is being compared
333
+ # @yieldparam with [Object] Object that is being compared to
334
+ # @yieldreturn [Number] Less then 0 if what < with, equal to 0 if what = with and bigger than 0 if what > with
335
+ def order_by(&block)
336
+ block = lambda { |a, b| a <=> b } if block == nil
337
+ groups = []
338
+ g = []
339
+ foreach do |item|
340
+ if g.length == 0
341
+ g.push(item)
342
+ else
343
+ if block.call(g.last, item) <= 0
344
+ g.push(item)
345
+ else
346
+ groups.push(g)
347
+ g = [item]
348
+ end
349
+ end
350
+ end
351
+ groups.push(g) if g.length > 0
352
+ return KXI::Collections::ArrayCollection.new if groups.length == 0
353
+ while groups.length > 1
354
+ merges = []
355
+ while groups.length >= 2
356
+ merges.push(merge(groups.shift, groups.shift, &block))
357
+ end
358
+ merges.push(groups[0]) if groups.length > 0
359
+ groups = merges
360
+ end
361
+ return KXI::Collections::ArrayCollection.new(groups[0])
362
+ end
363
+
364
+ # Orders collection to descending order
365
+ # @note Ordering is done using merge sort algorithm
366
+ # @return [KXI::Collections::ArrayCollection] Ordered collection
367
+ # @overload order_by()
368
+ # Orders collection using spaceship operator (<=>)
369
+ # @return [KXI::Collections::ArrayCollection] Ordered collection
370
+ # @overload order_by()
371
+ # Orders collection using given comparison function
372
+ # @return [KXI::Collections::ArrayCollection] Ordered collection
373
+ # @yield [what, with] Comparison function
374
+ # @yieldparam what [Object] Object that is being compared
375
+ # @yieldparam with [Object] Object that is being compared to
376
+ # @yieldreturn [Number] Less then 0 if what < with, equal to 0 if what = with and bigger than 0 if what > with
377
+ def order_by_descending(&block)
378
+ if block == nil
379
+ return order_by { |a, b| b <=> a }
380
+ else
381
+ return order_by { |a, b| -block.call(a, b) }
382
+ end
383
+ end
384
+
385
+ # Selects only those items that are of at least one of specified types
386
+ # @param klass [Class] Types to use for selection
387
+ # @return [KXI::Collections::ArrayCollection] Selected items
388
+ # @see Object#is_a?
389
+ def of_type(*klass)
390
+ ret = []
391
+ foreach do |i|
392
+ klass.each do |k|
393
+ if i.is_a?(k)
394
+ ret.push(i)
395
+ break
396
+ end
397
+ end
398
+ end
399
+ return KXI::Collections::ArrayCollection.new(ret)
400
+ end
401
+
402
+ # Selects only those items that are exactly of at least one of specified types
403
+ # @param klass [Class] Types to use for selection
404
+ # @return [KXI::Collections::ArrayCollection] Selected items
405
+ # @see Object#class
406
+ def of_type!(*klass)
407
+ ret = []
408
+ foreach do |i|
409
+ klass.each do |k|
410
+ if i.class == k
411
+ ret.push(i)
412
+ break
413
+ end
414
+ end
415
+ end
416
+ return KXI::Collections::ArrayCollection.new(ret)
417
+ end
418
+
419
+ # Obtains item with the first maximal value
420
+ # @overload max()
421
+ # Compares items using spaceship operator (<=>)
422
+ # @return [Object] Item with first maximal value; nil if collection is empty
423
+ # @overload max()
424
+ # Compares items using given comparison function
425
+ # @return [Object] Item with first maximal value; nil if collection is empty
426
+ # @yield [what, with] Comparison function
427
+ # @yieldparam what [Object] Object that is being compared
428
+ # @yieldparam with [Object] Object that is being compared to
429
+ # @yieldreturn [Number] Less then 0 if what < with, equal to 0 if what = with and bigger than 0 if what > with
430
+ def max(&block)
431
+ block = lambda { |a, b| a <=> b } if block == nil
432
+ ret = nil
433
+ first = true
434
+ foreach do |i|
435
+ if first
436
+ ret = i
437
+ first = false
438
+ elsif block.call(i, ret) > 0
439
+ ret = i
440
+ end
441
+ end
442
+ return ret
443
+ end
444
+
445
+ # Obtains item with the first minimal value
446
+ # @overload min()
447
+ # Compares items using spaceship operator (<=>)
448
+ # @return [Object] Item with first minimal value; nil if collection is empty
449
+ # @overload min()
450
+ # Compares items using given comparison function
451
+ # @return [Object] Item with first minimal value; nil if collection is empty
452
+ # @yield [what, with] Comparison function
453
+ # @yieldparam what [Object] Object that is being compared
454
+ # @yieldparam with [Object] Object that is being compared to
455
+ # @yieldreturn [Number] Less then 0 if what < with, equal to 0 if what = with and bigger than 0 if what > with
456
+ def min(&block)
457
+ block = lambda { |a, b| a <=> b } if block == nil
458
+ ret = nil
459
+ first = true
460
+ foreach do |i|
461
+ if first
462
+ ret = i
463
+ first = false
464
+ elsif block.call(i, ret) < 0
465
+ ret = i
466
+ end
467
+ end
468
+ return ret
469
+ end
470
+
471
+ # Coverts collection to array
472
+ # @return [Array] Array equivalent to collection
473
+ def to_array
474
+ ret = []
475
+ foreach { |item| ret.push(item) }
476
+ return ret
477
+ end
478
+
479
+ # Merges two arrays in order using comparison function
480
+ # @param a [Array] First array
481
+ # @param b [Array] Second array
482
+ # @return [Array] Merged ordered array
483
+ def merge(a, b, &comp)
484
+ ret = []
485
+ while a.length > 0 or b.length > 0
486
+ if a.length > 0 and b.length > 0
487
+ c = comp.call(a[0], b[0])
488
+ if c <= 0
489
+ ret.push(a.shift)
490
+ else
491
+ ret.push(b.shift)
492
+ end
493
+ else
494
+ ret.push(a.length > 0 ? a.shift : b.shift)
495
+ end
496
+ end
497
+ return ret
498
+ end
499
+
500
+ # Indicates whether collection can be altered
501
+ # @return [Bool] True if collection can be altered; otherwise false
502
+ def can_alter?
503
+ @locks == 0 and not @alter
504
+ end
505
+
506
+ def lock(write = false)
507
+ if write
508
+ raise(KXI::Exceptions::CollectionException.new('Collection cannot be locked for writing!')) unless can_alter?
509
+ @alter = true
510
+ yield
511
+ else
512
+ raise(KXI::Exceptions::CollectionException.new('Collection cannot be locked for reading!')) if @alter
513
+ @locks += 1
514
+ yield
515
+ end
516
+ ensure
517
+ if write
518
+ @alter = false
519
+ else
520
+ @locks -= 1
521
+ end
522
+ end
523
+
524
+ protected :lock, :create_enumerator
525
+ private :merge
526
+ end
527
+ end
528
+ end
@@ -0,0 +1,32 @@
1
+ # Created by Matyáš Pokorný on 2017-12-28.
2
+
3
+ module KXI
4
+ module Collections
5
+ # Allows iteration over a collection
6
+ # @abstract
7
+ class Enumerator
8
+ # Selects first item in collection
9
+ # @return [Bool] True if collection contains elements; otherwise false
10
+ # @abstract
11
+ def rewind
12
+ raise(KXI::Exceptions::AbstractException.new(Enumerator))
13
+ end
14
+
15
+ # Advances enumerator to next item
16
+ # @return [Bool] True if item is available; false otherwise
17
+ # @raise [KXI::Exceptions::AbstractException] When method is not implemented in superclass
18
+ # @abstract
19
+ def next
20
+ raise(KXI::Exceptions::AbstractException.new(Enumerator))
21
+ end
22
+
23
+ # Returns current item
24
+ # @return [Object] Current item
25
+ # @raise [KXI::Exceptions::AbstractException] When method is not implemented in superclass
26
+ # @abstract
27
+ def current
28
+ raise(KXI::Exceptions::AbstractException.new(Enumerator))
29
+ end
30
+ end
31
+ end
32
+ end