functional-ruby 0.7.4 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -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