functional-ruby 0.7.4 → 0.7.5

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,403 @@
1
+ module Functional
2
+
3
+ module Collection
4
+ extend self
5
+
6
+ # Returns a random sample of #size integers between 0 and 100 or
7
+ # the provided :min and/or :max options.
8
+ #
9
+ # @param [Integer] size the size of the sample to create
10
+ # @param [Hash] opts processing options
11
+ #
12
+ # @option opts [Integer] :min the minimum value in the sample
13
+ # @option opts [Integer] :max the maximun value in the sample
14
+ #
15
+ # @return [Array] an array of integers
16
+ def random_sample(size, opts={})
17
+ min = opts[:min].to_i
18
+ max = opts[:max] || 100
19
+ sample = []
20
+ size.times do
21
+ sample << rand(max-min) + min
22
+ end
23
+ return sample
24
+ end
25
+
26
+ # Return the index where to insert item x in list a, assuming a is sorted.
27
+ #
28
+ # The return value i is such that all e in a[:i] have e < x, and all e in
29
+ # a[i:] have e >= x. So if x already appears in the list, a.insert(x) will
30
+ # insert just before the leftmost x already there.
31
+ #
32
+ # Optional args lo (default 0) and hi (default len(a)) bound the
33
+ # slice of a to be searched.
34
+ #
35
+ # @see http://docs.python.org/3/library/bisect.html
36
+ # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
37
+ # @see http://effbot.org/librarybook/bisect.htm
38
+ def bisect_left(a, x, opts={})
39
+ return nil if a.nil?
40
+ return 0 if a.empty?
41
+
42
+ lo = (opts[:lo] || opts[:low]).to_i
43
+ hi = opts[:hi] || opts[:high] || a.length
44
+
45
+ while lo < hi
46
+ mid = (lo + hi) / 2
47
+ v = (block_given? ? yield(a[mid]) : a[mid])
48
+ if v < x
49
+ lo = mid + 1
50
+ else
51
+ hi = mid
52
+ end
53
+ end
54
+ return lo
55
+ end
56
+
57
+ # Return the index where to insert item x in list a, assuming a is sorted.
58
+ #
59
+ # The return value i is such that all e in a[:i] have e <= x, and all e in
60
+ # a[i:] have e > x. So if x already appears in the list, a.insert(x) will
61
+ # insert just after the rightmost x already there.
62
+ #
63
+ # Optional args lo (default 0) and hi (default len(a)) bound the
64
+ # slice of a to be searched.
65
+ #
66
+ # @see http://docs.python.org/3/library/bisect.html
67
+ # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
68
+ # @see http://effbot.org/librarybook/bisect.htm
69
+ def bisect_right(a, x, opts={})
70
+ return nil if a.nil?
71
+ return 0 if a.empty?
72
+
73
+ lo = (opts[:lo] || opts[:low]).to_i
74
+ hi = opts[:hi] || opts[:high] || a.length
75
+
76
+ while lo < hi
77
+ mid = (lo + hi) / 2
78
+ v = (block_given? ? yield(a[mid]) : a[mid])
79
+ if x < v
80
+ hi = mid
81
+ else
82
+ lo = mid + 1
83
+ end
84
+ end
85
+ return lo
86
+ end
87
+
88
+ alias_method :bisect, :bisect_right
89
+
90
+ # Insert item x in list a, and keep it sorted assuming a is sorted.
91
+ #
92
+ # If x is already in a, insert it to the left of the leftmost x.
93
+ #
94
+ # Optional args lo (default 0) and hi (default len(a)) bound the
95
+ # slice of a to be searched.
96
+ #
97
+ # @see http://docs.python.org/3/library/bisect.html
98
+ # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
99
+ # @see http://effbot.org/librarybook/bisect.htm
100
+ def insort_left(a, x, opts={}, &block)
101
+ return [x] if a.nil?
102
+ if a.respond_to?(:dup)
103
+ a = a.dup
104
+ else
105
+ a = collect(a)
106
+ end
107
+ return insort_left!(a, x, opts, &block)
108
+ end
109
+
110
+ # Insert item x in list a, and keep it sorted assuming a is sorted.
111
+ # Returns a duplicate of the original list, leaving it intact.
112
+ #
113
+ # If x is already in a, insert it to the left of the leftmost x.
114
+ #
115
+ # Optional args lo (default 0) and hi (default len(a)) bound the
116
+ # slice of a to be searched.
117
+ #
118
+ # @see http://docs.python.org/3/library/bisect.html
119
+ # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
120
+ # @see http://effbot.org/librarybook/bisect.htm
121
+ def insort_left!(a, x, opts={}, &block)
122
+ return [x] if a.nil?
123
+ return a << x if a.empty?
124
+
125
+ v = (block_given? ? yield(x) : x)
126
+ index = bisect_left(a, v, opts, &block)
127
+ return a.insert(index, x)
128
+ end
129
+
130
+ # Insert item x in list a, and keep it sorted assuming a is sorted.
131
+ # Returns a duplicate of the original list, leaving it intact.
132
+ #
133
+ # If x is already in a, insert it to the right of the rightmost x.
134
+ #
135
+ # Optional args lo (default 0) and hi (default len(a)) bound the
136
+ # slice of a to be searched.
137
+ #
138
+ # @see http://docs.python.org/3/library/bisect.html
139
+ # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
140
+ # @see http://effbot.org/librarybook/bisect.htm
141
+ def insort_right(a, x, opts={}, &block)
142
+ return [x] if a.nil?
143
+ if a.respond_to?(:dup)
144
+ a = a.dup
145
+ else
146
+ a = collect(a)
147
+ end
148
+ return insort_right!(a, x, opts, &block)
149
+ end
150
+
151
+ alias_method :insort, :insort_right
152
+
153
+ # Insert item x in list a, and keep it sorted assuming a is sorted.
154
+ #
155
+ # If x is already in a, insert it to the right of the rightmost x.
156
+ #
157
+ # Optional args lo (default 0) and hi (default len(a)) bound the
158
+ # slice of a to be searched.
159
+ #
160
+ # @see http://docs.python.org/3/library/bisect.html
161
+ # @see http://hg.python.org/cpython/file/3.3/Lib/bisect.py
162
+ # @see http://effbot.org/librarybook/bisect.htm
163
+ def insort_right!(a, x, opts={}, &block)
164
+ return [x] if a.nil?
165
+ return a << x if a.empty?
166
+
167
+ v = (block_given? ? yield(x) : x)
168
+ index = bisect_right(a, v, opts, &block)
169
+ return a.insert(index, x)
170
+ end
171
+
172
+ alias_method :insort!, :insort_right!
173
+
174
+ # Collect sample data from a generic collection, processing each item
175
+ # with a block when given. Returns an array of the items from +data+
176
+ # in order.
177
+ #
178
+ # When a block is given the block will be applied to both arguments.
179
+ # Using a block in this way allows computation against a specific field
180
+ # in a data set of hashes or objects.
181
+ #
182
+ # @yield iterates over each element in the data set
183
+ # @yieldparam item each element in the data set
184
+ #
185
+ # @param [Enumerable] data the data set to be collected
186
+ #
187
+ # @return [Array] an array of zero or more items
188
+ def collect(data, opts={})
189
+ return [] if data.nil?
190
+ sample = []
191
+ data.each do |datum|
192
+ datum = yield(datum) if block_given?
193
+ sample << datum
194
+ end
195
+ return sample
196
+ end
197
+
198
+ # Collect sample data from a generic collection, processing each item
199
+ # with a block when given. Returns an array of arrays. Each element
200
+ # is a two-element array where the first element is the index within
201
+ # the outer array and the second element is the corresponding item
202
+ # from within +data+. The elements in the returned array are in the
203
+ # same order as the original +data+ collection.
204
+ #
205
+ # @example
206
+ # sample = [5, 1, 9, 3, 14, 9, 7]
207
+ # Collection.catalog(sample) #=> [[0, 5], [1, 1], [2, 9], [3, 3], [4, 14], [5, 9], [6, 7]]
208
+ #
209
+ # When a block is given the block will be applied to both arguments.
210
+ # Using a block in this way allows computation against a specific field
211
+ # in a data set of hashes or objects.
212
+ #
213
+ # @yield iterates over each element in the data set
214
+ # @yieldparam item each element in the data set
215
+ #
216
+ # @param [Enumerable] data the data set to be collected
217
+ #
218
+ # @return [Array] an array of zero or more items
219
+ def index_and_catalog(data, opts={})
220
+ return [] if data.nil?
221
+ sample = []
222
+ index = 0
223
+ data.each do |datum|
224
+ datum = yield(datum) if block_given?
225
+ sample << [index, datum]
226
+ index += 1
227
+ end
228
+ return sample
229
+ end
230
+
231
+ alias_method :index_and_catalogue, :index_and_catalog
232
+
233
+ # Convert a hash to catalog.
234
+ #
235
+ # When a block is given the block will be applied to both arguments.
236
+ # Using a block in this way allows computation against a specific field
237
+ # in a data set of hashes or objects.
238
+ #
239
+ # @yield iterates over each element in the data set
240
+ # @yieldparam item each element in the data set
241
+ #
242
+ # @param [Enumerable] data the data to convert
243
+ # @param [Hash] opts search options
244
+ #
245
+ # @return [Array] if the data set is in ascending order
246
+ def catalog_hash(data, opts={})
247
+ return [] if data.nil? || data.empty?
248
+ catalog = []
249
+ data.each do |key, value|
250
+ value = yield(value) if block_given?
251
+ catalog << [key, value]
252
+ end
253
+ return catalog
254
+ end
255
+
256
+ alias_method :catalogue_hash, :catalog_hash
257
+
258
+ # Convert a catalog to a hash. Keeps the last value when keys are
259
+ # duplicated.
260
+ #
261
+ # When a block is given the block will be applied to both arguments.
262
+ # Using a block in this way allows computation against a specific field
263
+ # in a data set of hashes or objects.
264
+ #
265
+ # @yield iterates over each element in the data set
266
+ # @yieldparam item each element in the data set
267
+ #
268
+ # @param [Enumerable] data the data to convert
269
+ # @param [Hash] opts search options
270
+ #
271
+ # @return [Hash] if the data set is in ascending order
272
+ def hash_catalog(data, opts={})
273
+ return {} if data.nil? || data.empty?
274
+ hash = {}
275
+ data.each do |item|
276
+ value = (block_given? ? yield(item.last) : item.last)
277
+ hash[item.first] = value
278
+ end
279
+ return hash
280
+ end
281
+
282
+ alias_method :hash_catalogue, :hash_catalog
283
+
284
+ # Scan a collection and determine if the elements are all in
285
+ # ascending order. Returns true for an empty set and false for
286
+ # a nil sample.
287
+ #
288
+ # When a block is given the block will be applied to both arguments.
289
+ # Using a block in this way allows computation against a specific field
290
+ # in a data set of hashes or objects.
291
+ #
292
+ # @yield iterates over each element in the data set
293
+ # @yieldparam item each element in the data set
294
+ #
295
+ # @param [Enumerable] data the data set to search
296
+ # @param [Hash] opts search options
297
+ #
298
+ # @return [true, false] if the data set is in ascending order
299
+ def ascending?(data, opts={})
300
+ return false if data.nil?
301
+ (data.size-1).times do |i|
302
+ if block_given?
303
+ return false if yield(data[i]) > yield(data[i+1])
304
+ else
305
+ return false if data[i] > data[i+1]
306
+ end
307
+ end
308
+ return true
309
+ end
310
+
311
+ # Scan a collection and determine if the elements are all in
312
+ # descending order. Returns true for an empty set and false for
313
+ # a nil sample.
314
+ #
315
+ # When a block is given the block will be applied to both arguments.
316
+ # Using a block in this way allows computation against a specific field
317
+ # in a data set of hashes or objects.
318
+ #
319
+ # @yield iterates over each element in the data set
320
+ # @yieldparam item each element in the data set
321
+ #
322
+ # @param [Enumerable] data the data set to search
323
+ # @param [Hash] opts search options
324
+ #
325
+ # @return [true, false] if the data set is in descending order
326
+ def descending?(data, opts={})
327
+ return false if data.nil?
328
+ (data.size-1).times do |i|
329
+ if block_given?
330
+ return false if yield(data[i]) < yield(data[i+1])
331
+ else
332
+ return false if data[i] < data[i+1]
333
+ end
334
+ end
335
+ return true
336
+ end
337
+
338
+ # Override of #slice from Ruby Array. Provides a consistent interface
339
+ # to slice data structures that do not have a native #slice method.
340
+ #
341
+ # Returns the element at index, or returns a subarray starting at
342
+ # start and continuing for length elements, or returns a subarray
343
+ # specified by range. Negative indices count backward from the end
344
+ # of the array (-1 is the last element). Returns nil if the index
345
+ # (or starting index) is out of range.
346
+ #
347
+ # @overload slice(data, index)
348
+ # @param [Enumerable] data the collection to slice
349
+ # @param [Integer] index the index to slice
350
+ #
351
+ # @overload slice(data, start, length)
352
+ # @param [Enumerable] data the collection to slice
353
+ # @param [Integer] start the start index for the slice
354
+ # @param [Integer] length the length of the slice
355
+ #
356
+ # @overload slice(data, range)
357
+ # @param [Enumerable] data the collection to slice
358
+ # @param [Range] range range of indices to include in the slice
359
+ #
360
+ # @return [Array] the slice
361
+ def slice(data, *args)
362
+ index = args[0]
363
+ length = args[1]
364
+ if args.size == 1
365
+ if index.is_a? Range
366
+ slice_with_range(data, index)
367
+ else
368
+ slice_with_index(data, index)
369
+ end
370
+ elsif args.size == 2
371
+ slice_with_length(data, index, length)
372
+ else
373
+ raise ArgumentError.new("wrong number of arguments (#{args.size} for 2..3)")
374
+ end
375
+ end
376
+
377
+ # :nodoc:
378
+ # @private
379
+ def slice_with_index(data, index)
380
+ return data[index]
381
+ end
382
+
383
+ # :nodoc:
384
+ # @private
385
+ def slice_with_length(data, start, length)
386
+ range = Range.new(start, start+length-1)
387
+ slice_with_range(data, range)
388
+ end
389
+
390
+ # :nodoc:
391
+ # @private
392
+ def slice_with_range(data, range)
393
+ return nil if range.first < 0 || range.first >= data.size
394
+ last = [range.last, data.size-1].min
395
+ range = Range.new(range.first, last)
396
+ slice = []
397
+ range.each do |index|
398
+ slice << data[index]
399
+ end
400
+ return slice
401
+ end
402
+ end
403
+ end
@@ -0,0 +1,127 @@
1
+ module Functional
2
+
3
+ module Inflect
4
+ extend self
5
+
6
+ # By default, +camelize+ converts strings to UpperCamelCase. If the argument
7
+ # to +camelize+ is set to <tt>:lower</tt> then +camelize+ produces
8
+ # lowerCamelCase.
9
+ #
10
+ # +camelize+ will also convert '/' to '::' which is useful for converting
11
+ # paths to namespaces.
12
+ #
13
+ # 'active_model'.camelize # => "ActiveModel"
14
+ # 'active_model'.camelize(:lower) # => "activeModel"
15
+ # 'active_model/errors'.camelize # => "ActiveModel::Errors"
16
+ # 'active_model/errors'.camelize(:lower) # => "activeModel::Errors"
17
+ #
18
+ # As a rule of thumb you can think of +camelize+ as the inverse of
19
+ # +underscore+, though there are cases where that does not hold:
20
+ #
21
+ # 'SSLError'.underscore.camelize # => "SslError"
22
+ #
23
+ # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
24
+ def camelize(term, uppercase_first_letter = true)
25
+ string = term.to_s
26
+ if uppercase_first_letter == true
27
+ string = string.sub(/^[a-z\d]*/) { $&.capitalize }
28
+ else
29
+ string = string.sub(/^(?:(?=\b|[A-Z_])|\w)/) { $&.downcase }
30
+ end
31
+ string.gsub(/(?:_|(\/))([a-z\d]*)/i) { "#{$1}#{$2.capitalize}" }.gsub('/', '::')
32
+ end
33
+
34
+ # Makes an underscored, lowercase form from the expression in the string.
35
+ #
36
+ # Changes '::' to '/' to convert namespaces to paths.
37
+ #
38
+ # 'ActiveModel'.underscore # => "active_model"
39
+ # 'ActiveModel::Errors'.underscore # => "active_model/errors"
40
+ #
41
+ # As a rule of thumb you can think of +underscore+ as the inverse of
42
+ # +camelize+, though there are cases where that does not hold:
43
+ #
44
+ # 'SSLError'.underscore.camelize # => "SslError"
45
+ #
46
+ # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
47
+ def underscore(camel_cased_word)
48
+ word = camel_cased_word.to_s.dup
49
+ word.gsub!('::', '/')
50
+ word.gsub!(/\s+/, '_')
51
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
52
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
53
+ word.tr!("-", "_")
54
+ word.downcase!
55
+ word
56
+ end
57
+
58
+ # Capitalizes the first word and turns underscores into spaces and strips a
59
+ # trailing "_id", if any. Like +titleize+, this is meant for creating pretty
60
+ # output.
61
+ #
62
+ # 'employee_salary'.humanize # => "Employee salary"
63
+ # 'author_id'.humanize # => "Author"
64
+ #
65
+ # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
66
+ def humanize(lower_case_and_underscored_word)
67
+ result = lower_case_and_underscored_word.to_s.dup
68
+ result.gsub!(/_id$/, "")
69
+ result.tr!('_', ' ')
70
+ result.gsub(/([a-z\d]*)/i) { |match| "#{match.downcase}" }.gsub(/^\w/) { $&.upcase }
71
+ end
72
+
73
+ # Capitalizes all the words and replaces some characters in the string to
74
+ # create a nicer looking title. +titleize+ is meant for creating pretty
75
+ # output. It is not used in the Rails internals.
76
+ #
77
+ # +titleize+ is also aliased as +titlecase+.
78
+ #
79
+ # 'man from the boondocks'.titleize # => "Man From The Boondocks"
80
+ # 'x-men: the last stand'.titleize # => "X Men: The Last Stand"
81
+ # 'TheManWithoutAPast'.titleize # => "The Man Without A Past"
82
+ # 'raiders_of_the_lost_ark'.titleize # => "Raiders Of The Lost Ark"
83
+ #
84
+ # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
85
+ def titleize(word)
86
+ #humanize(underscore(word)).gsub(/\b(?<!['`])[a-z]/) { $&.capitalize }
87
+ humanize(underscore(word)).gsub(/\b["'`]?[a-z]/) { $&.capitalize }
88
+ end
89
+
90
+ # Replaces underscores with dashes in the string.
91
+ #
92
+ # 'puni_puni'.dasherize # => "puni-puni"
93
+ #
94
+ # @see https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflector/methods.rb
95
+ def dasherize(underscored_word)
96
+ underscored_word.to_s.dup.tr('_', '-')
97
+ end
98
+
99
+ # Add the given extension to the given file name. Removes the current
100
+ # extension if one exists. Strips trailing characters from the file name
101
+ # if present. Does nothing if the file name already has the given
102
+ # extension.
103
+ #
104
+ # @example
105
+ # extensionize('my_file.png', :png) #=> 'my_file.png'
106
+ # extensionize('my_file', :png) #=> 'my_file.png'
107
+ # extensionize('my_file.png', :jpg) #=> 'my_file.png.jpg'
108
+ # extensionize('My File', :png) #=> 'My File.png'
109
+ # extensionize('My File ', :png) #=> 'My File.png'
110
+ # extensionize('my_file.png', :jpg, :chomp => true) #=> 'my_file.jpg'
111
+ #
112
+ # @param [String] fname the name of the file to add an extension to
113
+ # @param [String, Symbol] ext the extension to append, with or without a leading dot
114
+ # @param [Hash] opts processing options
115
+ #
116
+ # @option opts [Symbol] :chomp when true removes the existing entension
117
+ # from the file name (default: false)
118
+ #
119
+ # @return [String] the *fname* with *ext* appended
120
+ def extensionize(fname, ext, opts={})
121
+ extname = File.extname(fname)
122
+ return fname if (extname =~ /\.?#{ext}$/i) == 0
123
+ fname = fname.gsub(/#{extname}$/, '') if opts[:chomp] == true
124
+ return fname.strip + '.' + ext.to_s.gsub(/^\./, '')
125
+ end
126
+ end
127
+ end