kxi 1.0.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.
@@ -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