rize 1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8db91982bf1a6ec94bd2b535aad16c487979a0b3
4
+ data.tar.gz: 3f33d42668c3d92d78a180fd708b97adb7537cfa
5
+ SHA512:
6
+ metadata.gz: f3b7a05514ec46cd1e0b3fb0635906c35c96fe55427cf67b6df602696bebce7a507ac33f14e6dc64de74af5531ed5b287ff54b74accfaca1d13e9b5e2c956f48
7
+ data.tar.gz: 4e1ac5fc93e5cdb5ecc23c62a88a460cf5f7bbb4c46816faacec71363c3af2e3347a420744ae73014bacfae06983fae879c1c281f1f5f1d12a81055869ac65a2
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ .byebug_history
11
+ *.gem
@@ -0,0 +1,52 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.2
3
+ Exclude:
4
+ - 'lib/rize/version.rb'
5
+
6
+ Style/SpaceInsideHashLiteralBraces:
7
+ Enabled: false
8
+
9
+ Style/StringLiterals:
10
+ Enabled: false
11
+
12
+ Style/NumericLiterals:
13
+ Enabled: false
14
+
15
+ Metrics/AbcSize:
16
+ Enabled: false
17
+
18
+ Metrics/LineLength:
19
+ Max: 120
20
+
21
+ Style/HashSyntax:
22
+ Enabled: false
23
+
24
+ Style/PercentLiteralDelimiters:
25
+ Enabled: false
26
+
27
+ Style/Documentation:
28
+ Enabled: false
29
+
30
+ Style/UnneededPercentQ:
31
+ Enabled: false
32
+
33
+ Style/BracesAroundHashParameters:
34
+ Enabled: false
35
+
36
+ Metrics/MethodLength:
37
+ Enabled: false
38
+
39
+ Style/Lambda:
40
+ Enabled: false
41
+
42
+ Style/WordArray:
43
+ Enabled: false
44
+
45
+ Style/SymbolProc:
46
+ Enabled: false
47
+
48
+ Metrics/ClassLength:
49
+ Enabled: false
50
+
51
+ Style/GuardClause:
52
+ Enabled: false
@@ -0,0 +1,11 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.0
5
+ before_install: gem install bundler -v 1.12.3
6
+ install:
7
+ gem install rubocop
8
+ script:
9
+ rubocop -D
10
+ notifications:
11
+ email: false
@@ -0,0 +1 @@
1
+ --exclude lib/rize.rb
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at abhijeetkalyan@gmail.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rize.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Abhijeet Kalyan.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,100 @@
1
+ [![Build Status](https://travis-ci.org/abhijeetkalyan/rize.svg?branch=master)](https://travis-ci.org/abhijeetkalyan/rize)
2
+
3
+ # Rize
4
+
5
+ ### A functional toolkit for Ruby.
6
+
7
+ (Inspired by Javascript's [Underscore](http://underscorejs.org/), Python's [toolz](https://github.com/pytoolz/toolz) and Ocaml's [List module](http://caml.inria.fr/pub/docs/manual-ocaml/libref/List.html)).
8
+
9
+ Rize is a collection of useful methods that can make it easier to work with functions, arrays and hashes in Ruby. Some of the interesting things you can do include:
10
+
11
+ - Compose, memoize and partially supply arguments to your functions
12
+ - Control the behaviour of your functions based on how many times they're called - for example, you could create a function that stops executing on the third try.
13
+ - Map or iterate over multiple arrays at once
14
+ - Elegantly map over just the keys, or just the values of a hash.
15
+
16
+ See the [Usage](https://github.com/abhijeetkalyan/rize#usage) section for more on what rize can do.
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ gem 'rize'
24
+ ```
25
+
26
+ And then execute:
27
+
28
+ $ bundle
29
+
30
+ Or install it yourself as:
31
+
32
+ $ gem install rize
33
+
34
+ ## Usage
35
+
36
+ Rize has two primary uses: working with [functions](https://github.com/abhijeetkalyan/rize/blob/master/lib/rize/functional.rb) and working with [iterables](https://github.com/abhijeetkalyan/rize/blob/master/lib/rize/iteration.rb). More on each follows:
37
+
38
+ ### Functions
39
+
40
+ - `memoize` - Creates a new function that caches its results. Useful for expensive computations, which you may not want to re-run multiple times.
41
+ - `compose` - Takes in a list of functions, and creates a new function that composes them together. Useful if you have a bunch of smaller functions, and want to mix them up in various different ways.
42
+ - `partial` - Partially supply arguments to a function. Useful if you have some of the arguments now, but won't get the rest until later. Unlike the stdlib's `Proc#curry`, there are no restrictions on the positions of the arguments - you can have arguments 1 and 3 now, and tell `partial` to supply the second argument when it gets it.
43
+ - `at_most` - Allow a function to be called only a certain number of times. Useful for behaviours you don't want to retry endlessly, like attempting to connect to a database.
44
+ - `at_least` - Allow a function to work only *after* it's been called a certain number of times.
45
+
46
+ ### Iterables
47
+
48
+ - `hmap` - A more concise way of mapping over the keys and values of a hash in one go.
49
+ - `hkeymap` - Map over just the keys of a hash, and leave the values as they are.
50
+ - `hvalmap` - Map over just the values of a hash, and leave the keys as they are.
51
+ - `hd` - Get the first element of an array.
52
+ - `tl` - Everything *but* the first element of an array. Useful in recursive functions.
53
+ - `frequencies` - Count the occurrences of each element in an array, or the occurrences of even numbers, or the occurrences of anything else, depending on the block you pass in.
54
+ - `map_n` - Map over multiple arrays at once. Useful when dealing with matrix operations and the like.
55
+ - `each_n` - Iterate over multiple arrays at once. Useful when dealing with matrix operations and the like.
56
+ - `repeat` - Repeat the passed in block a certain number of times. Useful when repeating repetitive(meta!) operations, like mass-assigning a bunch of random numbers or mass-creating a bunch of test factories.
57
+ - `lazy_repeat` - Lazy version of repeat.
58
+ - `flatter_map` - Map over the underlying elements of an array, regardless of how deeply the array is nested.
59
+
60
+
61
+ ## Development
62
+
63
+ For initial setup:
64
+
65
+ ```
66
+ bundle install
67
+ ```
68
+
69
+ Run the tests:
70
+
71
+ ```
72
+ rake test
73
+ ```
74
+
75
+ To work with Rize in an interactive console:
76
+
77
+ ```
78
+ bundle console
79
+ ```
80
+
81
+
82
+
83
+ ## Contributing
84
+
85
+ Bug reports and pull requests are welcome on [GitHub] (https://github.com/abhijeetkalyan/rize). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
86
+
87
+ Some interesting TODOs might include:
88
+
89
+ - Support timing-related functions such as `throttle` or `debounce` a la [Underscore](http://underscorejs.org/).
90
+ - Lazy versions of the iteration methods
91
+ - C/Java/other extensions for performance
92
+ - Support for passing methods as symbols instead of as method objects, such as `compose(:foo, :bar)` instead of `compose(method(:foo), method(:bar)`
93
+ - More methods available for iteration over multiple arrays at once, in addition to the already-existing `each_n` and `map_n`
94
+ - Integration with a cache such as Redis, to be
95
+
96
+
97
+
98
+ ## License
99
+
100
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ # Run the test suite via 'rake test'
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.libs << "test"
7
+ t.libs << "lib"
8
+ t.test_files = FileList['test/**/*_test.rb']
9
+ end
10
+
11
+ task :default => :test
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rize"
5
+
6
+ require "pry"
7
+ Pry.start
8
+
9
+ # Uncomment the lines below to use irb instead of pry.
10
+ # require "irb"
11
+ # IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,18 @@
1
+ require "rize/version"
2
+ require "rize/iteration"
3
+ require "rize/functional"
4
+
5
+ module Rize
6
+ # A class used when partially supplying positional
7
+ # arguments for a function.
8
+ class DontCare
9
+ end
10
+
11
+ DC = DontCare.new
12
+
13
+ # Error when the function from `after` hasn't been called enough times.
14
+ class TooFewCallsError < StandardError; end
15
+
16
+ # Error when the function from `before` has been called too many times.
17
+ class TooManyCallsError < StandardError; end
18
+ end
@@ -0,0 +1,226 @@
1
+ module Rize
2
+ module_function
3
+
4
+ # Returns a memoized version of a given proc, lambda, or method.
5
+ #
6
+ # @param func [Proc, Lambda, Method] The proc, lambda, or method to memoize.
7
+ #
8
+ # @return [Lambda] A lambda that is the memoized version of the input function.
9
+ # @example Memoize an expensive function.
10
+ # expensive_lambda = lambda do |arg|
11
+ # puts "very expensive computation"
12
+ # arg
13
+ # end
14
+ # memoized = Rize.memoize(expensive_lambda)
15
+ #
16
+ # memoized.call(1)
17
+ # "very expensive computation"
18
+ # 1
19
+ # memoized.call(1)
20
+ # 1
21
+ # memoized.call(2)
22
+ # "very expensive computation"
23
+ # 2
24
+ # memoized.call(2)
25
+ # 2
26
+ def memoize(func)
27
+ memo = {}
28
+ call_count = Hash.new(0)
29
+ lambda do |*args|
30
+ return memo[args] if call_count[args] == 1
31
+ memo[args] = func.call(*args)
32
+ call_count[args] += 1
33
+ memo[args]
34
+ end
35
+ end
36
+
37
+ # Compose multiple procs, lambdas or methods.
38
+ #
39
+ # @param *funcs [Proc, Lambda, Method] A variable-length number of procs, lambdas or methods to compose.
40
+ #
41
+ # @return [Lambda] A lambda that is the composition of the inputs.
42
+ # compose(f, g, h).call(arg) is the same as f(g(h.call(arg)))
43
+ # @example Compose various mathematical operations together to compute (2(a + b))^2.
44
+ # f = lambda { |x| x**2 }
45
+ # g = lambda { |x| 2 * x }
46
+ # h = lambda { |x, y| x + y }
47
+ # composed = Rize.compose(f, g, h)
48
+ # composed.call(2, 3)
49
+ # 100
50
+ def compose(*funcs)
51
+ -> (*args) { call_all(funcs, *args) }
52
+ end
53
+
54
+ # Returns a negated version of a proc, lambda, or method.
55
+ # The input function should return a boolean.
56
+ # @param func [Proc, Lambda, Method] A proc, lambda, or method to negate.
57
+ #
58
+ # @return [Lambda] A lambda that is the negation of func.
59
+ #
60
+ # @example Given a function that checks evenness, create a function that checks oddness.
61
+ # even = lambda { |x| x.even? }
62
+ # odd = Rize.negate(even)
63
+ def negate(func)
64
+ -> (*args) { !func.call(*args) }
65
+ end
66
+
67
+ # TODO: Pull out shared logic between at_least, at_most, and memoize
68
+
69
+ # Raises an error until after a function is called a certain number of times, following which the function is
70
+ # executed.
71
+ # Raises instead of returning nil to provide better transparency to callers.
72
+ #
73
+ # @param func [Proc, Lambda, or Method] A proc, lambda or method.
74
+ # @param allowed_call_count [Fixnum] The minimum number of times this function needs to be called to start executing.
75
+ # @raise TooFewCallsError [StandardError] Exception raised when the function hasn't been called enough times.
76
+ #
77
+ # @return [Lambda] A lambda that places the appropriate call restrictions on func.
78
+ #
79
+ # @example Execute a function only on the 3rd attempt.
80
+ # succeed = lambda { |*args| "success!" }
81
+ # persevere = Rize.at_least(succeed, 3)
82
+ # 3.times do
83
+ # begin
84
+ # persevere.call
85
+ # rescue Rize::TooFewCallsError
86
+ # puts "keep trying"
87
+ # end
88
+ # end
89
+ # "keep trying"
90
+ # "keep trying"
91
+ # "success!"
92
+ def at_least(func, allowed_call_count)
93
+ call_count = 0
94
+ lambda do |*args|
95
+ call_count += 1
96
+ raise TooFewCallsError, "Minimum call count is #{allowed_call_count}." if call_count < allowed_call_count
97
+ func.call(*args)
98
+ end
99
+ end
100
+
101
+ # Executes a function upto a certain number of times, following which an error is raised.
102
+ # Raises instead of returning nil to provide better transparency to callers.
103
+ #
104
+ # @param func [Proc, Lambda, or Method] A proc, lambda or method.
105
+ # @param allowed_call_count [Fixnum] The maximum number of times the function can execute.
106
+ # @raise TooManyCallsError [StandardError] Exception raised when the function has been called too many times.
107
+ #
108
+ # @return [Lambda] A lambda that places the appropriate call restrictions on func.
109
+ #
110
+ # @example Execute a function 2 times, then fail on attempt 3 onwards.
111
+ # are_we_there_yet = lambda { |*args| "Are we there yet?" }
112
+ # but_are_we_really_there_yet = Rize.at_most(are_we_there_yet, 2)
113
+ # 2.times do
114
+ # begin
115
+ # but_are_we_really_there_yet.call
116
+ # rescue Rize::TooManyCallsError
117
+ # puts "That's it, I'm turning this car around"
118
+ # end
119
+ # end
120
+ # "Are we there yet?"
121
+ # "Are we there yet?"
122
+ # "That's it, I'm turning this car around"
123
+ #
124
+ # @example Try connecting to a database 3 times. Give up on attempt 4.
125
+ # MAX_RETRIES = 3
126
+ # try_connect = Rize.at_most( lambda { |db| db.connect }, MAX_RETRIES )
127
+ # loop do
128
+ # begin
129
+ # try_connect.call(db)
130
+ # rescue TooManyCallsError
131
+ # # If we've tried too many times, give up
132
+ # break
133
+ # rescue ConnectionError
134
+ # # If we can't connect, try again
135
+ # try_connect.call(db)
136
+ # end
137
+ # end
138
+ def at_most(func, allowed_call_count)
139
+ call_count = 0
140
+ lambda do |*args|
141
+ call_count += 1
142
+ raise TooManyCallsError, "Maximum call count is #{allowed_call_count}." if call_count > allowed_call_count
143
+ func.call(*args)
144
+ end
145
+ end
146
+
147
+ # Partially supply the arguments to a proc, lambda, or method.
148
+ #
149
+ # @param func [Proc, Lambda, Method] The proc, lambda, or method to partially supply arguments to.
150
+ # @param *args [Object] A variable-length number of positional arguments.
151
+ # Use Rize::DC as a 'don't care' variable to signify that we'd like to supply this argument later.
152
+ # This is useful, for example, if we have arguments 1 and 3, but are waiting on argument 2.
153
+ # @param **kwargs [Object] A variable-length number of keyword arguments.
154
+ #
155
+ # @return [Lambda] A lambda that is the partially filled version of the input function.
156
+ # @example Supply the second and third positional arguments, but not the first.
157
+ # final_lambda = lambda do |a, b, c|
158
+ # (a - b) * c
159
+ # end
160
+ # # Supply b and c.
161
+ # partial_lambda = Rize.partial(final_lambda, Rize::DC, 2, 3)
162
+ # # Supply a.
163
+ # partial_lambda.call(1)
164
+ # -3 # (1 - 2) * 3
165
+ # @example Partial with keyword arguments.
166
+ # final_lambda = lambda do |a:, b:, c:|
167
+ # (a - b) * c
168
+ # end
169
+ # Supply a: and c:.
170
+ # partial_lambda = Rize.partial(final_lambda, a:1, c: 3)
171
+ # Supply b:.
172
+ # partial_lambda.call(b: 2)
173
+ # -3 # (1 - 2) * 3
174
+ # @example Partial with positional and keyword arguments.
175
+ # final_lambda = lambda do |a, b, c:|
176
+ # (a - b) * c
177
+ # end
178
+ # Supply a and c:.
179
+ # partial_lambda = Rize.partial(final_lambda, 1, c: 3)
180
+ # Supply b.
181
+ # partial_lambda.call(2)
182
+ # -3 # (1 - 2) * 3
183
+ def partial(func, *args, **kwargs)
184
+ lambda do |*new_args, **new_kwargs|
185
+ func.call(*(merge_positional(args, new_args) + merge_keyword(kwargs, new_kwargs)))
186
+ end
187
+ end
188
+
189
+ # Internal method used by partial to handle positional arguments.
190
+ # Given arrays [1, Rize::DC, 3] and [2], returns [1, 2, 3].
191
+ # @param prefilled_args [Array] Prefilled args supplied to Rize.partial.
192
+ # @param new_args [Array] Args supplied at call-time of the function.
193
+ #
194
+ # @return [Array] the merged arguments in the manner described above.
195
+ def merge_positional(prefilled_args, new_args)
196
+ tmp_new_args = new_args.dup
197
+ prefilled_args.map do |elem|
198
+ if elem == DC
199
+ tmp_new_args.shift
200
+ else
201
+ elem
202
+ end
203
+ end + tmp_new_args
204
+ end
205
+
206
+ # Internal method used by partial to handle keyword arguments.
207
+ # Returns [] if passed in empty hashes.
208
+ # @param prefilled_kwargs [Hash] Prefilled kwargs supplied to Rize.partial.
209
+ # @param new_kwargs [Hash] kwargs supplied at call-time of the function.
210
+ #
211
+ # @return [Array] An array holding the merged keyword arguments.
212
+ def merge_keyword(prefilled_kwargs, new_kwargs)
213
+ return [] if prefilled_kwargs.empty? && new_kwargs.empty?
214
+ [prefilled_kwargs.merge(new_kwargs)]
215
+ end
216
+
217
+ # Internal helper used by compose to actually call functions.
218
+ #
219
+ # @param funcs [Array] An array of procs, lambdas or methods.
220
+ # @param *args [Object] A variable-length number of arguments to call the actual functions with.
221
+ def call_all(funcs, *args)
222
+ return funcs[0].call(*args) if funcs.length == 1
223
+
224
+ funcs[0].call(call_all(funcs.drop(1), *args))
225
+ end
226
+ end
@@ -0,0 +1,218 @@
1
+ module Rize
2
+ module_function
3
+
4
+ # Map over the keys and values of a hash.
5
+ #
6
+ # @param hsh [Hash] The hash to be mapped over.
7
+ # @yield [key, value] A block which returns in the form [key, value].
8
+ #
9
+ # @return [Hash] Returns a new hash with the results of running the block over it.
10
+ # @example Map over a hash
11
+ # Rize.hmap({a: 1, b: 2}) { |k,v| [k.to_s, v + 1] }
12
+ # { "a" => 2, "b" => 3 }
13
+ def hmap(hsh)
14
+ Hash[hsh.map { |k, v| yield(k, v) }]
15
+ end
16
+
17
+ # Map over the keys of a hash.
18
+ #
19
+ # @param hsh [Hash] The hash to be mapped over.
20
+ # @yield [key] A block that acts upon the hash keys.
21
+ #
22
+ # @return [Hash] Returns a new hash with updated keys, and unchanged values.
23
+ # @example Map over a hash's keys.
24
+ # Rize.hkeymap({a: 1, b: 2}, &:to_s)
25
+ # { "a" => 1, "b" => 2 }
26
+ def hkeymap(hsh)
27
+ Hash[hsh.map { |k, v| [yield(k), v] }]
28
+ end
29
+
30
+ # Map over the values of a hash.
31
+ #
32
+ # @param hsh [Hash] The hash to be mapped over.
33
+ # @yield [value] A block that acts upon the hash values
34
+ #
35
+ # @return [Hash] Returns a new hash with updated values, and unchanged keys.
36
+ # @example Map over a hash's values.
37
+ # Rize.hvalmap({a: 1, b: 2}, &:to_s)
38
+ # { a: "1", b: "2" }
39
+ def hvalmap(hsh)
40
+ Hash[hsh.map { |k, v| [k, yield(v)] }]
41
+ end
42
+
43
+ # Returns the first element of an array, or nil if the array is empty.
44
+ #
45
+ # @param arr [Array] The array from which we want the head.
46
+ #
47
+ # @return [Object] The first element of the array.
48
+ # @example Get the first element of an array.
49
+ # Rize.hd [1, 2, 3]
50
+ # 1
51
+ # @example
52
+ # Rize.hd []
53
+ # nil
54
+ def hd(arr)
55
+ arr[0]
56
+ end
57
+
58
+ # Returns all but the first element of the array.
59
+ #
60
+ # @param arr [Array] The array from which we want the tail.
61
+ #
62
+ # @return [Array] An array containing all but the first element of the input.
63
+ # @example Get all but the first element of the array.
64
+ # Rize.tl [1, 2, 3]
65
+ # [2, 3]
66
+ # @example
67
+ # Rize.tl []
68
+ # []
69
+ def tl(arr)
70
+ arr.drop(1)
71
+ end
72
+
73
+ # Find how many times a block evaluates to a particular result in an array.
74
+ #
75
+ # @param arr [Array] The array over which we're counting frequencies.
76
+ # @yield [elem] A block whose results we use to calculate frequencies.
77
+ #
78
+ # @return [Hash] A hash containing the count of each of block's output values from the array.
79
+ # The keys are the various outputs, and the values are the number of times said outputs occurred.
80
+ # @example Count the elements in an array.
81
+ # Rize.frequencies([1, 2, 3, 1]) { |el| el }
82
+ # { 1 => 2, 2 => 1, 3 => 1 }
83
+ # @example Count the even numbers in an array.
84
+ # Rize.frequencies([1, 2, 3, 1]) { |el| el.even? }
85
+ # { true => 1, false => 3 }
86
+ def frequencies(arr)
87
+ hvalmap(arr.group_by { |el| yield(el) }, &:length)
88
+ end
89
+
90
+ # Map over multiple arrays together.
91
+ #
92
+ # The same as doing [block(a1, b1, c1), block(a2, b2, c2)]
93
+ # for arrays [a1, b1, c1] and [a2, b2, c2].
94
+ #
95
+ # Raises an ArgumentError if arrays are of unequal length.
96
+ #
97
+ # @param args [Array] A variable-length number of arrays.
98
+ # @yield [*args] A block that acts upon elements at a particular index in the array.
99
+ #
100
+ # @return [Array] The result of calling the block over the matching array elements.
101
+ # @example Sum all the elements at the same position across multiple arrays.
102
+ # Rize.map_n([1, 2, 3], [4, 5, 6], [7, 8, 9]) { |*args| args.reduce(:+) }
103
+ # [12, 15, 18]
104
+ # @example Subtract the second array's element from the first, and multiply by the third.
105
+ # Rize.map_n([1, 2, 3], [4, 5, 6], [7, 8, 9]) { |a, b, c| (a - b) * c }
106
+ # [-21, -24, -27]
107
+ # @example Try with arrays of unequal length.
108
+ # Rize.map_n([1, 2], [1, 2, 3]) { |*args| args.reduce(:+) }
109
+ # ArgumentError: Expected all inputs to be of length 2
110
+ def map_n(*args)
111
+ expected_length = args[0].length
112
+ if args.any? { |arr| arr.length != expected_length }
113
+ raise ArgumentError, "Expected all inputs to be of length #{expected_length}"
114
+ end
115
+ hd(args).zip(*tl(args)).map do |elems|
116
+ yield(*elems)
117
+ end
118
+ end
119
+
120
+ # Iterate over multiple arrays together.
121
+ #
122
+ # The same as doing [block(a1, b1, c1), block(a2, b2, c2)]
123
+ # for arrays [a1, b1, c1] and [a2, b2, c2].
124
+ #
125
+ # Raises an ArgumentError if arrays are of unequal length.
126
+ #
127
+ # @param args [Array] A variable-length number of arrays.
128
+ # @yield [*args] A block that acts upon elements at a particular index in the array.
129
+ #
130
+ # @return [Array] The input arrays.
131
+ # @example Print the transposed version of an array of arrays.
132
+ # Rize.each_n([1, 2, 3], [4, 5, 6], [7, 8, 9]) { |a, b, c| puts "#{a} #{b} #{c}" }
133
+ # 1 4 7
134
+ # 2 5 8
135
+ # 3 6 9
136
+ def each_n(*args)
137
+ expected_length = args[0].length
138
+ if args.any? { |arr| arr.length != expected_length }
139
+ raise ArgumentError, "Expected all inputs to be of length #{expected_length}"
140
+ end
141
+ hd(args).zip(*tl(args)).each do |elems|
142
+ yield(*elems)
143
+ end
144
+ end
145
+
146
+ # Repeat a block N times, and return an array of the results.
147
+ #
148
+ # @param count [Fixnum] The number of times to repeat a block.
149
+ # @yield The block to be called.
150
+ #
151
+ # @return [Array] The result of running block, `count` times.
152
+ # @example Mass-assign several variables to different random numbers.
153
+ # a, b, c = Rize.repeat { Random.new.rand(50) }
154
+ # a
155
+ # 24
156
+ # b
157
+ # 10
158
+ # c
159
+ # 18
160
+ # @example Initialize multiple FactoryGirl objects in one go.
161
+ # u1, u2, u3 = Rize.repeat { FactoryGirl.create(:user) }
162
+ # u1
163
+ # <User Object 1>
164
+ # u2
165
+ # <User Object 2>
166
+ # u3
167
+ # <User Object 3>
168
+ def repeat(count)
169
+ result = []
170
+ count.times { result << yield }
171
+ result
172
+ end
173
+
174
+ # Lazy version of repeat.
175
+ # Repeat a block N times, and return a lazy enumerator which can be forced for results.
176
+ #
177
+ # @yield The block to be called.
178
+ #
179
+ # @return [Enumerator::Lazy] A lazy enumerator that can be evaluated for the desired number of results.
180
+ # @example Mass-assign several variables to different random numbers.
181
+ # a, b, c = Rize.repeat { Random.new.rand(50) }.first(3)
182
+ # a
183
+ # 24
184
+ # b
185
+ # 10
186
+ # c
187
+ # 18
188
+ # @example Initialize multiple FactoryGirl objects in one go.
189
+ # u1, u2, u3 = Rize.repeat { FactoryGirl.create(:user) }.first(3)
190
+ # u1
191
+ # <User Object 1>
192
+ # u2
193
+ # <User Object 2>
194
+ # u3
195
+ # <User Object 3>
196
+ def lazy_repeat
197
+ (1..Float::INFINITY).lazy.map { yield }
198
+ end
199
+
200
+ # Variation on Enumerable#flat_map that flattens the array before running the block.
201
+ # Useful when dealing with an array of arrays, where we really want to operate on the underlying elements.
202
+ #
203
+ # @param arr [Array] The array to be operated on.
204
+ #
205
+ # @yield [elem] The block to be called.
206
+ #
207
+ # @return [Array] The result of calling flat_map on the flattened array.
208
+ #
209
+ # @example Capitalize each letter in an array of arrays.
210
+ # Rize.flatter_map([["a", "b"], [["c"], ["d"]]], &:capitalize)
211
+ # ["A", "B", "C", "D"]
212
+ # @example Capitalize each letter in an array of arrays, and also return the non-capitalized version.
213
+ # Rize.flatter_map([["a", "b"], [["c"], ["d"]]]) { |el| [el, el.capitalize] }
214
+ # ["a", "A", "b", "B", "c", "C", "d", "D"]
215
+ def flatter_map(arr, &block)
216
+ arr.flatten.flat_map(&block)
217
+ end
218
+ end
@@ -0,0 +1,3 @@
1
+ module Rize
2
+ VERSION = "1.0.1"
3
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rize/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "rize"
8
+ spec.version = Rize::VERSION
9
+ spec.authors = ["abhijeetkalyan"]
10
+ spec.email = ["abhijeetkalyan@gmail.com"]
11
+
12
+ spec.summary = %q{Write a short summary, because Rubygems requires one.}
13
+ spec.description = %q{Write a longer description or delete this line.}
14
+ spec.homepage = "https://github.com/abhijeetkalyan/rize"
15
+ spec.license = "MIT"
16
+ spec.required_ruby_version = '>= 2.0.0'
17
+
18
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.12"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "minitest", "~> 5.0"
26
+ spec.add_development_dependency "byebug", "~> 8.2"
27
+ spec.add_development_dependency "pry", "~> 0.10.3"
28
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rize
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - abhijeetkalyan
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-05-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: minitest
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '8.2'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '8.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.10.3
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.10.3
83
+ description: Write a longer description or delete this line.
84
+ email:
85
+ - abhijeetkalyan@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rubocop.yml"
92
+ - ".travis.yml"
93
+ - ".yardopts"
94
+ - CODE_OF_CONDUCT.md
95
+ - Gemfile
96
+ - LICENSE.txt
97
+ - README.md
98
+ - Rakefile
99
+ - bin/console
100
+ - bin/setup
101
+ - lib/rize.rb
102
+ - lib/rize/functional.rb
103
+ - lib/rize/iteration.rb
104
+ - lib/rize/version.rb
105
+ - rize.gemspec
106
+ homepage: https://github.com/abhijeetkalyan/rize
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 2.0.0
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubyforge_project:
126
+ rubygems_version: 2.4.8
127
+ signing_key:
128
+ specification_version: 4
129
+ summary: Write a short summary, because Rubygems requires one.
130
+ test_files: []
131
+ has_rdoc: