functions 0.0.16 → 0.0.17
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.
- checksums.yaml +5 -5
- data/.rspec +0 -1
- data/.travis.yml +4 -0
- data/Gemfile +7 -1
- data/README.md +8 -0
- data/Rakefile +20 -6
- data/examples/Gemfile +6 -0
- data/examples/Rakefile +22 -0
- data/examples/prelude_lambda.rb +8 -11
- data/examples/spec/prelude_lambda_spec.rb +14 -14
- data/functions.gemspec +2 -2
- data/lib/functions/prelude_enumerable/hash.rb +2 -32
- data/lib/functions/prelude_lambda/basic.rb +23 -18
- data/lib/functions/prelude_lambda/sorting.rb +7 -7
- data/lib/functions/version.rb +1 -1
- data/performance/prelude_performance.rb +4 -4
- data/spec/prelude_enumerable_spec.rb +57 -57
- data/spec/prelude_lambda_math_spec.rb +20 -22
- data/spec/prelude_lambda_spec.rb +16 -18
- data/spec/spec_helper.rb +9 -7
- data/test/test_prelude_performance.rb +3 -1
- metadata +9 -6
- data/examples/multi_hash_map_recursive.rb +0 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d884db3d112ebd90fee4cf4ab66325b3800988821512fa097a5bd684dac6024b
|
4
|
+
data.tar.gz: 640c89f3e47031e0f0320c8c9eb8ad811af88779658c6cb0befef25222aaecad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ed27fb9cface5baeb4dd000385eda0886425549ec90db99ea2f2afdde51b019cae3f479790d3d5107f48695feded0225e5fed7a50cd2196684df00bf9714a88a
|
7
|
+
data.tar.gz: 5613d93811123dfbd9be92ff0e6c33101cb652af8ad8a4ca3afa4ec68c453ebd26317d5176bf09435f31d1b4d0b868a6d23b72d968aefc27c02c9ae10de63bdf
|
data/.rspec
CHANGED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -3,6 +3,12 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in functions.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
+
# gem 'coveralls', require: false
|
7
|
+
# gem 'rake'
|
8
|
+
|
6
9
|
group :test do
|
7
|
-
gem 'test-unit'
|
10
|
+
# gem 'test-unit'
|
11
|
+
gem 'rake'
|
12
|
+
gem 'rspec'
|
13
|
+
gem 'coveralls', require: false
|
8
14
|
end
|
data/README.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
|
2
|
+
[](https://rubygems.org/gems/functions)
|
3
|
+
[](https://gemnasium.com/koenhandekyn/functions)
|
4
|
+
[](https://travis-ci.org/koenhandekyn/functions)
|
5
|
+
[](https://codeclimate.com/github/koenhandekyn/functions)
|
6
|
+
[](https://coveralls.io/r/koenhandekyn/functions)
|
7
|
+
|
8
|
+
|
1
9
|
# Functional
|
2
10
|
|
3
11
|
This library facilitates writing code in a more functional style inside Ruby.
|
data/Rakefile
CHANGED
@@ -1,8 +1,22 @@
|
|
1
|
-
require "bundler/gem_tasks"
|
2
|
-
require 'rake/testtask'
|
1
|
+
# require "bundler/gem_tasks"
|
2
|
+
# require 'rake/testtask'
|
3
3
|
|
4
|
-
Rake::TestTask.new do |t|
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
# Rake::TestTask.new do |t|
|
5
|
+
# t.libs << "test" << "."
|
6
|
+
# t.test_files = FileList['test/test*.rb']
|
7
|
+
# t.verbose = false
|
8
|
+
# end
|
9
|
+
|
10
|
+
require 'rspec/core/rake_task'
|
11
|
+
|
12
|
+
desc 'run the basic specs'
|
13
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
14
|
+
spec.rspec_opts = ['--options', 'spec/rspec.opts']
|
8
15
|
end
|
16
|
+
|
17
|
+
desc 'do some basic performance tests'
|
18
|
+
task :performance do
|
19
|
+
load 'performance/prelude_performance.rb'
|
20
|
+
end
|
21
|
+
|
22
|
+
task default: [:spec, :performance]
|
data/examples/Gemfile
ADDED
data/examples/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# require "bundler/gem_tasks"
|
2
|
+
# require 'rake/testtask'
|
3
|
+
|
4
|
+
# Rake::TestTask.new do |t|
|
5
|
+
# t.libs << "test" << "."
|
6
|
+
# t.test_files = FileList['test/test*.rb']
|
7
|
+
# t.verbose = false
|
8
|
+
# end
|
9
|
+
|
10
|
+
require 'rspec/core/rake_task'
|
11
|
+
|
12
|
+
desc 'run the example specs'
|
13
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
14
|
+
spec.rspec_opts = ['--options', 'spec/rspec.opts']
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'run the examples'
|
18
|
+
task :examples do
|
19
|
+
load 'prelude_lambda.rb'
|
20
|
+
end
|
21
|
+
|
22
|
+
task default: [:spec, :examples]
|
data/examples/prelude_lambda.rb
CHANGED
@@ -7,14 +7,14 @@ module PreludeLambdaUsage
|
|
7
7
|
Power = ->(p,x) { x**p }.curry
|
8
8
|
Square = Power.(2)
|
9
9
|
Squares = Map.(Square)
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
SumOfSquares1 = Compose.(Sum).(Squares)
|
11
|
+
SumOfSquares2 = Sum < Squares
|
12
|
+
SumOfSquares = SumOfSquares2
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
14
|
+
IncWith = ->(f, n, m) { n + f.(m) }.curry
|
15
|
+
SumOf1 = ->(f,arr) { Foldl.( IncWith.(f), 0, arr) }.curry
|
16
|
+
SumOf2 = ->(f) { ->(arr) { Foldl.( IncWith.(f), 0, arr) } }
|
17
|
+
SumOf = SumOf1
|
18
18
|
|
19
19
|
# Average variations
|
20
20
|
Average1 = After.( Parallel.(Sum).(Length) ).( Divide )
|
@@ -22,9 +22,7 @@ module PreludeLambdaUsage
|
|
22
22
|
Average3 = After.( [Sum,Length] ).( Divide )
|
23
23
|
|
24
24
|
# Gcd variations
|
25
|
-
Gcd1 = ->(a, b) {
|
26
|
-
(1..[a,b].min).select { |c| a % c == 0 && b % c == 0 }.max
|
27
|
-
}
|
25
|
+
Gcd1 = ->(a, b) { (1..[a,b].min).select { |c| a % c == 0 && b % c == 0 }.max }
|
28
26
|
Gcd2 = ->(a,b) { (Divisors.(a) & Divisors.(b)).max }
|
29
27
|
|
30
28
|
# GcdA variations
|
@@ -37,7 +35,6 @@ module PreludeLambdaUsage
|
|
37
35
|
Dividers = Map.(Divisors)
|
38
36
|
GcdA4 = Greatest < Common < Dividers
|
39
37
|
|
40
|
-
|
41
38
|
# LcmA variations
|
42
39
|
LcmA1 = ->(arr) { arr.inject { |a, r| Lcm.(a, r) } } # the lcm of an array as an iteration of the lcm over the elements
|
43
40
|
LcmA2 = ReduceLeft.(Lcm) # the same but without arguments
|
@@ -5,37 +5,37 @@ include PreludeLambdaUsage
|
|
5
5
|
describe PreludeLambdaUsage, "basic lambda prelude usage" do
|
6
6
|
|
7
7
|
it "sums" do
|
8
|
-
|
9
|
-
|
8
|
+
expect(SumOfSquares.([2, 3, 4])).to eq(2*2+3*3+4*4)
|
9
|
+
expect(SumOf.(Square).([2, 3, 1])).to eq(2*2+3*3+1*1)
|
10
10
|
end
|
11
11
|
|
12
12
|
it "averages" do
|
13
|
-
Average.([2, 3, 8]).
|
13
|
+
expect(Average.([2, 3, 8])).to eq(4)
|
14
14
|
end
|
15
15
|
|
16
16
|
it "flattens" do
|
17
|
-
Flatten.([[1, 2, 3], [2, 3]]).
|
17
|
+
expect(Flatten.([[1, 2, 3], [2, 3]])).to eq([1, 2, 3, 2, 3])
|
18
18
|
end
|
19
19
|
|
20
20
|
it "folds" do
|
21
|
-
Foldl.(->(n, a) { n/a }, 1.0, [1.0, 2.0, 3.0]).
|
22
|
-
Foldr.(->(n, a) { n/a }, 1.0, [1.0, 2.0, 3.0]).
|
21
|
+
expect(Foldl.(->(n, a) { n/a }, 1.0, [1.0, 2.0, 3.0])).to eq(1.0/1.0/2.0/3.0)
|
22
|
+
expect(Foldr.(->(n, a) { n/a }, 1.0, [1.0, 2.0, 3.0])).to eq(1.0/3.0/2.0/1.0)
|
23
23
|
end
|
24
24
|
|
25
25
|
it "knows about GCD alternatives" do
|
26
26
|
# test variant implementations
|
27
|
-
Gcd1.(4,8).
|
28
|
-
Gcd2.(4,8).
|
29
|
-
GcdA1.([4,8,2]).
|
30
|
-
GcdA2.([4,8,2]).
|
31
|
-
GcdA3.([4,8,2]).
|
32
|
-
GcdA4.([4,8,2]).
|
27
|
+
expect(Gcd1.(4,8)).to eq(4)
|
28
|
+
expect(Gcd2.(4,8)).to eq(4)
|
29
|
+
expect(GcdA1.([4,8,2])).to eq(2)
|
30
|
+
expect(GcdA2.([4,8,2])).to eq(2)
|
31
|
+
expect(GcdA3.([4,8,2])).to eq(2)
|
32
|
+
expect(GcdA4.([4,8,2])).to eq(2)
|
33
33
|
end
|
34
34
|
|
35
35
|
it "knows about LCM variations" do
|
36
36
|
# test variant implementations
|
37
|
-
LcmA1.([12,9,2]).
|
38
|
-
LcmA2.([12,9,2]).
|
37
|
+
expect(LcmA1.([12,9,2])).to eq(36)
|
38
|
+
expect(LcmA2.([12,9,2])).to eq(36)
|
39
39
|
end
|
40
40
|
|
41
41
|
|
data/functions.gemspec
CHANGED
@@ -8,10 +8,10 @@ Gem::Specification.new do |gem|
|
|
8
8
|
gem.version = Functions::VERSION
|
9
9
|
gem.authors = ["Koen Handekyn"]
|
10
10
|
gem.email = ["koen.handekyn@up-nxt.com"]
|
11
|
-
gem.description = %q{functional programming in ruby}
|
11
|
+
gem.description = %q{A prelude library that belongs to the book "functional programming in ruby".}
|
12
12
|
gem.summary = %q{functional programming in ruby}
|
13
13
|
gem.homepage = "https://github.com/koenhandekyn/functions"
|
14
|
-
gem.license = '
|
14
|
+
gem.license = 'LGPL-3.0+'
|
15
15
|
gem.files = `git ls-files`.split($/)
|
16
16
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
17
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
@@ -16,13 +16,6 @@ class Hash
|
|
16
16
|
Hash[self.map{|k, v| [k, v.is_a?(Hash) ? v.map_values_recurse(&b) : b.(v)] }]
|
17
17
|
end
|
18
18
|
|
19
|
-
# definition of maarten
|
20
|
-
# def map_values &blk
|
21
|
-
# res = {}
|
22
|
-
# each {|k,v| res[k] = blk.call(v)}
|
23
|
-
# res
|
24
|
-
# end
|
25
|
-
|
26
19
|
# TODO discuss if we should not map onto array as values can colide
|
27
20
|
def map_keys
|
28
21
|
Hash[self.map{|k, v| [yield(k), v] }]
|
@@ -32,21 +25,6 @@ class Hash
|
|
32
25
|
Hash[self.map{|k, v| [b.(k), v.is_a?(Hash) ? v.map_keys_recurse(&b) : v ] }]
|
33
26
|
end
|
34
27
|
|
35
|
-
# def map_keys
|
36
|
-
# each_with_object({}) { |h, (k,v)| h[yield(k)] = v }
|
37
|
-
# end
|
38
|
-
|
39
|
-
# def map_keys &blk
|
40
|
-
# each_with_object({}) {|h, (k,v)| h[blk.(k)] = v}
|
41
|
-
# end
|
42
|
-
|
43
|
-
# definition of maarten
|
44
|
-
# def map_keys &blk
|
45
|
-
# res = {}
|
46
|
-
# each {|k,v| res[blk.call(k)] = v}
|
47
|
-
# res
|
48
|
-
# end
|
49
|
-
|
50
28
|
# map a hash into a hash
|
51
29
|
def map_hash
|
52
30
|
Hash[ self.map{|k, v| yield(k,v) } ]
|
@@ -54,8 +32,8 @@ class Hash
|
|
54
32
|
|
55
33
|
# map a hash into a hash recursively.
|
56
34
|
# the mapping function needs to check itself wether or not the value is hash and act appropriately
|
57
|
-
def
|
58
|
-
Hash[ self.map{|k, v| b.(k, v.is_a?(Hash) ? v.
|
35
|
+
def map_recurse(&b)
|
36
|
+
Hash[ self.map{|k, v| b.(k, v.is_a?(Hash) ? v.map_recurse(&b) : v) } ]
|
59
37
|
end
|
60
38
|
|
61
39
|
# map a hash into a hash recursively with a seperate key mapping and value mapping function
|
@@ -68,14 +46,6 @@ class Hash
|
|
68
46
|
Hash[ self.map{|k, v| b.(k, ( v.is_a?(Hash) ? v.multi_map(&b) : v) ) }.flatten(1) ]
|
69
47
|
end
|
70
48
|
|
71
|
-
# def map_hash &blk
|
72
|
-
# res = {}
|
73
|
-
# each do |k,v|
|
74
|
-
# kk, vv = blk.call(k,v)
|
75
|
-
# res[kk] = vv
|
76
|
-
# end
|
77
|
-
# res
|
78
|
-
# end
|
79
49
|
|
80
50
|
end
|
81
51
|
|
@@ -8,15 +8,18 @@ module Functions
|
|
8
8
|
# the constant function
|
9
9
|
Const = ->(c, x) { c }
|
10
10
|
|
11
|
+
# make a function resilient to nil inputs
|
12
|
+
Maybe = ->(fn) { ->(x) { fn.(x) unless x.nil? } }
|
13
|
+
|
11
14
|
# splits a list xs in peices of size n
|
12
|
-
|
15
|
+
SplitIn = ->(n, xs) { xs.each_slice((xs.length+1)/n).to_a }
|
13
16
|
|
14
17
|
# splits a list in half
|
15
|
-
|
18
|
+
SplitInHalf = SplitIn.curry.(2)
|
16
19
|
|
17
20
|
# merges two ordered lists by a function f that compares the values
|
18
21
|
# if no function is given the values are compared by the "<" operator
|
19
|
-
|
22
|
+
MergeBy = ->(f, xs, ys) do
|
20
23
|
|
21
24
|
return xs if ys.empty?
|
22
25
|
return ys if xs.empty?
|
@@ -24,13 +27,13 @@ module Functions
|
|
24
27
|
x, *xt = xs
|
25
28
|
y, *yt = ys
|
26
29
|
|
27
|
-
return
|
28
|
-
return
|
30
|
+
return MergeBy.(f, xt, ys) >> x if f.nil? ? x <= y : f.(x) <= f.(y)
|
31
|
+
return MergeBy.(f, xs, yt) >> y
|
29
32
|
|
30
33
|
end
|
31
34
|
|
32
35
|
# merges two list by the natural comparison operator <
|
33
|
-
Merge =
|
36
|
+
Merge = MergeBy.partial(nil)
|
34
37
|
|
35
38
|
# composes two functions
|
36
39
|
Compose = ->(f, g, x) { f.(g.(x)) }.curry
|
@@ -38,6 +41,8 @@ module Functions
|
|
38
41
|
# manually curried version of the Compose function
|
39
42
|
ComposeCurried = ->(f) { ->(g) { ->(x) { f.(g.(x)) } } }
|
40
43
|
|
44
|
+
Chain = ->(*fns) { fns.reduce { |f, g| lambda { |x| f.(g.(x)) } } }
|
45
|
+
|
41
46
|
# composes two functions in reverse sequence
|
42
47
|
After = ->(f, g, x) {
|
43
48
|
f = Par.(f) if Array === f;
|
@@ -81,17 +86,17 @@ module Functions
|
|
81
86
|
|
82
87
|
Zip = ->(a, b) { a.zip(b) }.curry
|
83
88
|
|
84
|
-
|
89
|
+
MergeHash = ->(as, bs) { as.merge(bs) { |k, a, b| [a, b] } }.curry
|
85
90
|
|
86
|
-
|
91
|
+
ZipHashLeft = ->(as, bs) { as.each_with_object({}) { |(k, a), h| h[k] = [a, bs[k]]; h } }.curry
|
87
92
|
|
88
|
-
|
93
|
+
ZipHashInner = ->(as, bs) { as.each_with_object({}) { |(k, a), h| b = bs[k]; h[k] = [a, b] if b; h } }.curry
|
89
94
|
|
90
|
-
|
95
|
+
ZipHashRight = ->(as, bs) { bs.each_with_object({}) { |(k, b), h| h[k] = [as[k], b]; h } }.curry
|
91
96
|
|
92
97
|
Map = ->(f, a) { a.map { |x| f.(x) } }.curry
|
93
98
|
|
94
|
-
|
99
|
+
MapHash = ->(f, h) { Hash[h.map{|k, v| [k, f.(v)] }] }.curry
|
95
100
|
|
96
101
|
Filter = ->(f, xs) { xs.select { |x| f.(x) } }.curry
|
97
102
|
|
@@ -101,23 +106,23 @@ module Functions
|
|
101
106
|
|
102
107
|
Intersect = ->(as) { as.inject(:&) }
|
103
108
|
|
104
|
-
Group = ->(f,a) { a.group_by(&f) }.curry
|
109
|
+
Group = ->(f,a) { a.group_by(&f) }.curry
|
105
110
|
|
106
111
|
Values = Send.(:values)
|
107
112
|
|
108
113
|
# TODO investigate semantics
|
109
|
-
Partition = ->(f) { Group.(f) > Values }
|
114
|
+
Partition = ->(f) { Group.(f) > Values }
|
110
115
|
|
111
116
|
FromTo = ->(from) { ->(to) { Range.new(from, to) } }
|
112
117
|
|
113
118
|
FromOneTo = FromTo.(1)
|
114
119
|
|
115
|
-
|
116
|
-
# count_by = ->(f) { group_by.( f ) < map_hash.( send.(:length) ) }
|
117
|
-
|
118
|
-
Count = ->(a) { a.inject( Hash.new(0) ) { |h,e| h[e] += 1; h } } # probably a bit faster
|
120
|
+
CountBy = ->(f,a) { a.inject( Hash.new(0) ) { |h,e| h[f.(e)] += 1; h } }.curry
|
121
|
+
# count_by = ->(f) { group_by.( f ) < map_hash.( send.(:length) ) }
|
122
|
+
|
123
|
+
Count = ->(a) { a.inject( Hash.new(0) ) { |h,e| h[e] += 1; h } } # probably a bit faster
|
119
124
|
# count = count_by.(identity) # alternative definition (generic)
|
120
125
|
|
121
126
|
end
|
122
127
|
|
123
|
-
end
|
128
|
+
end
|
@@ -2,18 +2,18 @@ module Functions
|
|
2
2
|
|
3
3
|
module Prelude
|
4
4
|
|
5
|
-
|
5
|
+
MergeSortBy = ->(f, xs) do
|
6
6
|
|
7
7
|
return xs if xs.length <= 1 # stopcondition
|
8
8
|
|
9
|
-
left, right =
|
10
|
-
|
9
|
+
left, right = SplitInHalf.(xs)
|
10
|
+
MergeBy.(f, MergeSortBy.(f, left), MergeSortBy.(f, right))
|
11
11
|
end
|
12
12
|
|
13
|
-
|
13
|
+
MergeSort = MergeSortBy.partial(nil)
|
14
14
|
# = Merge_Sort_By.partial(Identity)
|
15
15
|
|
16
|
-
|
16
|
+
QuickSortBy = ->(f, list) do
|
17
17
|
|
18
18
|
return [] if list.size == 0
|
19
19
|
return list if list.size == 1
|
@@ -21,10 +21,10 @@ module Functions
|
|
21
21
|
pivot, *xs = *list
|
22
22
|
smaller_than = f.nil? ? ->(y) { y < pivot } : ->(y) { f.(y) < f.(pivot) }
|
23
23
|
less, more = xs.partition &smaller_than
|
24
|
-
|
24
|
+
QuickSortBy.(f, less) + [pivot] + QuickSortBy.(f, more)
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
QuickSort = QuickSortBy.partial(nil)
|
28
28
|
# = Quick_Sort_By.partial(Identity)
|
29
29
|
|
30
30
|
end
|
data/lib/functions/version.rb
CHANGED
@@ -8,7 +8,7 @@ include PreludeLambdaUsage
|
|
8
8
|
|
9
9
|
[10, 100, 1000, 10000].each do |n|
|
10
10
|
Benchmark.bm(40) do |b|
|
11
|
-
b.report("Sum_Of_Squares(n=#{n}): ") { (100000/n).times {
|
11
|
+
b.report("Sum_Of_Squares(n=#{n}): ") { (100000/n).times { SumOfSquares.((1..n).to_a) } }
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -27,14 +27,14 @@ end
|
|
27
27
|
[10, 100, 1000].each do |n|
|
28
28
|
random_array = (0..n).to_a.shuffle
|
29
29
|
Benchmark.bm(40) do |b|
|
30
|
-
b.report("Merge_Sort(n=#{n}): ") { (100000/n).times {
|
30
|
+
b.report("Merge_Sort(n=#{n}): ") { (100000/n).times { MergeSort.(random_array) } }
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
34
|
[10, 100, 1000].each do |n|
|
35
35
|
random_array = (0..n).to_a.shuffle
|
36
36
|
Benchmark.bm(40) do |b|
|
37
|
-
b.report("Quick_Sort(n=#{n}): ") { (100000/n).times {
|
38
|
-
b.report("Quick_Sort/identity(n=#{n}): ") { (100000/n).times {
|
37
|
+
b.report("Quick_Sort(n=#{n}): ") { (100000/n).times { QuickSort.(random_array) } }
|
38
|
+
b.report("Quick_Sort/identity(n=#{n}): ") { (100000/n).times { QuickSortBy.(Id, random_array) } }
|
39
39
|
end
|
40
40
|
end
|
@@ -1,131 +1,131 @@
|
|
1
|
-
require '
|
1
|
+
require 'spec_helper'
|
2
2
|
|
3
3
|
describe "enumerable" do
|
4
4
|
|
5
5
|
it "zip_map" do
|
6
|
-
[1,2,3].zip([2,3,4]).map { |a,b| a+b }.
|
7
|
-
[1,2,3].zip_map([2,3,4]) { |a,b| a+b }.
|
8
|
-
[1,2,3].zip_map([2,3,4],[0,1,0]) { |a,b,c| a+b+c }.
|
9
|
-
[1,2,3].zip_map([2,3,4]).with_index { |(a,b),n| a+b+n }.
|
6
|
+
expect([1,2,3].zip([2,3,4]).map { |a,b| a+b }).to eq([3,5,7])
|
7
|
+
expect([1,2,3].zip_map([2,3,4]) { |a,b| a+b }).to eq([3,5,7])
|
8
|
+
expect([1,2,3].zip_map([2,3,4],[0,1,0]) { |a,b,c| a+b+c }).to eq([3,6,7])
|
9
|
+
expect([1,2,3].zip_map([2,3,4]).with_index { |(a,b),n| a+b+n }).to eq([3,6,9])
|
10
10
|
end
|
11
11
|
|
12
12
|
it "transpose" do
|
13
|
-
[[1,2,3]].transpose.
|
14
|
-
[[1,2,3],[:a,:b,:c]].transpose.
|
13
|
+
expect([[1,2,3]].transpose).to eq([[1],[2],[3]])
|
14
|
+
expect([[1,2,3],[:a,:b,:c]].transpose).to eq([[1,:a],[2,:b],[3,:c]])
|
15
15
|
end
|
16
16
|
|
17
17
|
it "zip, unzip" do
|
18
18
|
ns = [1,2,3]
|
19
19
|
as = [:a,:b,:c]
|
20
|
-
ns.zip(as).unzip.
|
20
|
+
expect(ns.zip(as).unzip).to eq([ns, as])
|
21
21
|
end
|
22
22
|
|
23
23
|
it "split_in" do
|
24
|
-
[1,2,3].split_in(1).
|
25
|
-
[1,2,3].split_in(2).
|
26
|
-
[1,2,3].split_in(3).
|
27
|
-
[1,2,3].split_in(4).
|
28
|
-
[1,2,3].split_in_half.
|
24
|
+
expect([1,2,3].split_in(1)).to eq([[1,2,3]])
|
25
|
+
expect([1,2,3].split_in(2)).to eq([[1,2],[3]])
|
26
|
+
expect([1,2,3].split_in(3)).to eq([[1],[2],[3]])
|
27
|
+
expect([1,2,3].split_in(4)).to eq([[1],[2],[3],[]]) # do we want this ? length 3 ? or lenght 4 ?
|
28
|
+
expect([1,2,3].split_in_half).to eq([[1,2],[3]])
|
29
29
|
end
|
30
30
|
|
31
31
|
it "merge" do
|
32
32
|
# starts with ordered sets
|
33
|
-
[1,3,5].merge([3,4,8]).
|
34
|
-
[1,3,5].merge([2]).
|
35
|
-
[].merge([1,2,3]).
|
36
|
-
[1,2,3].merge([]).
|
37
|
-
[3,2,1].merge([4,2,1]) { |a,b| a > b }.
|
33
|
+
expect([1,3,5].merge([3,4,8])).to eq([1,3,3,4,5,8])
|
34
|
+
expect([1,3,5].merge([2])).to eq([1,2,3,5])
|
35
|
+
expect([].merge([1,2,3])).to eq([1,2,3])
|
36
|
+
expect([1,2,3].merge([])).to eq([1,2,3])
|
37
|
+
expect([3,2,1].merge([4,2,1]) { |a,b| a > b }).to eq([4,3,2,2,1,1])
|
38
38
|
end
|
39
39
|
|
40
40
|
it "interleave" do
|
41
|
-
%w(sex druggs rock roll).interleave([", "," and "," & "]).join("").
|
42
|
-
[3,1,4].interleave([:a,:d,:b]).
|
43
|
-
[3,1,4].interleave([:a,:d]).
|
44
|
-
[3,1,4].interleave([:a]).
|
45
|
-
[3,1,4].interleave([]).
|
41
|
+
expect(%w(sex druggs rock roll).interleave([", "," and "," & "]).join("")).to eq("sex, druggs and rock & roll")
|
42
|
+
expect([3,1,4].interleave([:a,:d,:b])).to eq([3,:a,1,:d,4,:b])
|
43
|
+
expect([3,1,4].interleave([:a,:d])).to eq([3,:a,1,:d,4])
|
44
|
+
expect([3,1,4].interleave([:a])).to eq([3,:a,1,4])
|
45
|
+
expect([3,1,4].interleave([])).to eq([3,1,4])
|
46
46
|
end
|
47
47
|
|
48
48
|
it "zip_hash" do
|
49
49
|
ab = {a: 'a', b: 'b'}
|
50
50
|
ad = {a: 'a', d: 'd'}
|
51
51
|
cb = {c: 'c', b: 'b'}
|
52
|
-
ab.zip_hash_left(ad).
|
53
|
-
ab.zip_hash_left(cb).
|
54
|
-
ad.zip_hash_left(cb).
|
55
|
-
ab.zip_hash_inner(ad).
|
56
|
-
ab.zip_hash_inner(cb).
|
57
|
-
ad.zip_hash_inner(cb).
|
52
|
+
expect(ab.zip_hash_left(ad)).to eq({a: ['a','a'], b:['b',nil]})
|
53
|
+
expect(ab.zip_hash_left(cb)).to eq({a: ['a',nil], b:['b','b']})
|
54
|
+
expect(ad.zip_hash_left(cb)).to eq({a: ['a',nil], d:['d',nil]})
|
55
|
+
expect(ab.zip_hash_inner(ad)).to eq({a:['a','a']})
|
56
|
+
expect(ab.zip_hash_inner(cb)).to eq({b:['b','b']})
|
57
|
+
expect(ad.zip_hash_inner(cb)).to eq({})
|
58
58
|
end
|
59
59
|
|
60
60
|
it "map_values" do
|
61
61
|
ab = {a: 1, b: 2}
|
62
|
-
ab.map_values { |x| x*2}.
|
63
|
-
ab.map_values { |x| x.even? }.
|
62
|
+
expect(ab.map_values { |x| x*2}).to eq({a: 2, b: 4})
|
63
|
+
expect(ab.map_values { |x| x.even? }).to eq({a: false, b: true})
|
64
64
|
end
|
65
65
|
|
66
66
|
it "map_values_recurse" do
|
67
67
|
abcde = {a: 1, b: 2, c: { d: 1, e: 0 } }
|
68
|
-
abcde.map_values_recurse { |x| x*2}.
|
69
|
-
abcde
|
68
|
+
expect(abcde.map_values_recurse { |x| x*2}).to eq({a: 2, b: 4, c: { d: 2, e: 0}})
|
69
|
+
expect(abcde.map_values_recurse { |x| x.even? }).to eq({a: false, b: true, c: { d:false, e: true}})
|
70
70
|
end
|
71
71
|
|
72
72
|
it "map_keys" do
|
73
73
|
ab = {a: 1, b: 2}
|
74
|
-
ab.map_keys { |k| k.to_s }.
|
75
|
-
ab.map_keys { |k| k.to_s[0].ord }.
|
76
|
-
ab.map_keys { |k| k.to_s.length }.
|
74
|
+
expect(ab.map_keys { |k| k.to_s }).to eq({"a" => 1, "b" => 2})
|
75
|
+
expect(ab.map_keys { |k| k.to_s[0].ord }).to eq({ 97 => 1, 98 => 2 })
|
76
|
+
expect(ab.map_keys { |k| k.to_s.length }).to eq({ 1 => 2 })
|
77
77
|
end
|
78
78
|
|
79
79
|
it "map_keys_recurse" do
|
80
80
|
abcde = {a: 1, b: 2, c: { d: 1, e: 0 } }
|
81
|
-
abcde.map_keys_recurse { |k| k.to_s }.
|
81
|
+
expect(abcde.map_keys_recurse { |k| k.to_s }).to eq({"a" => 1, "b" => 2, "c" => { "d" => 1, "e" => 0 }})
|
82
82
|
end
|
83
83
|
|
84
84
|
it "map_hash" do
|
85
85
|
ab = {a: 1, b: 2}
|
86
|
-
ab.map_hash { |k,v| [k.to_s, v*2] }.
|
87
|
-
ab.map_hash { |k,v| [k.to_s[0].ord, v.even?] }.
|
88
|
-
ab.map_hash { |k,v| [k.to_s.length, v.even?] }.
|
86
|
+
expect(ab.map_hash { |k,v| [k.to_s, v*2] }).to eq({"a" => 2, "b" => 4})
|
87
|
+
expect(ab.map_hash { |k,v| [k.to_s[0].ord, v.even?] }).to eq({97 => false, 98 => true})
|
88
|
+
expect(ab.map_hash { |k,v| [k.to_s.length, v.even?] }).to eq({1 => true})
|
89
89
|
end
|
90
90
|
|
91
91
|
it "map_recursive" do
|
92
92
|
abcde = {a: 1, b: 2, c: { d: 1, e: 0 } }
|
93
|
-
abcde.
|
94
|
-
abcde.
|
95
|
-
abcde.
|
93
|
+
expect(abcde.map_recurse { |k,v| v.is_a?(Hash) ? [k.to_s, v] : [k.to_s, v*2] }).to eq({"a" => 2, "b" => 4, "c" => { "d" => 2, "e" => 0 }})
|
94
|
+
expect(abcde.map_recurse { |k,v| v.is_a?(Hash) ? [k.to_s[0].ord, v] : [k.to_s[0].ord, v.even?] }).to eq({97 => false, 98 => true, 99 => { 100 => false, 101 => true }})
|
95
|
+
expect(abcde.map_recurse { |k,v| v.is_a?(Hash) ? [k.to_s.length, v] : [k.to_s.length, v.even?] }).to eq({1 => { 1=>true } })
|
96
96
|
end
|
97
97
|
|
98
98
|
it "map_keys_and_values" do
|
99
|
-
|
99
|
+
|
100
100
|
s = ->(x) { x.to_s }
|
101
101
|
s_ord = ->(x) { x.to_s[0].ord }
|
102
102
|
s_length = ->(x) { x.to_s.length }
|
103
103
|
times_2 = ->(x) { x * 2 }
|
104
104
|
even = ->(x) { x.even? }
|
105
|
-
|
105
|
+
|
106
106
|
abcde = {a: 1, b: 2, c: { d: 1, e: 0 } }
|
107
107
|
|
108
|
-
abcde.map_keys_and_values(s, times_2).
|
109
|
-
abcde.map_keys_and_values(s_ord, even).
|
110
|
-
abcde.map_keys_and_values(s_length, even).
|
108
|
+
expect(abcde.map_keys_and_values(s, times_2)).to eq({"a" => 2, "b" => 4, "c" => { "d" => 2, "e" => 0 }})
|
109
|
+
expect(abcde.map_keys_and_values(s_ord, even)).to eq({97 => false, 98 => true, 99 => { 100 => false, 101 => true }})
|
110
|
+
expect(abcde.map_keys_and_values(s_length, even)).to eq({1 => { 1=>true } })
|
111
111
|
|
112
112
|
end
|
113
113
|
|
114
114
|
it "counted_set" do
|
115
|
-
[1,2,3,2,2,1,2].counted_set.
|
116
|
-
['a','b','a','d'].counted_set.
|
115
|
+
expect([1,2,3,2,2,1,2].counted_set).to eq({1 => 2, 2 => 4, 3 => 1})
|
116
|
+
expect(['a','b','a','d'].counted_set).to eq({'a' => 2, 'b' => 1, 'd' => 1})
|
117
117
|
end
|
118
118
|
|
119
119
|
it "grouped_by" do
|
120
|
-
[1,2,3,2,2,1,2].grouped_by { |x| x.odd? }.
|
121
|
-
%w(some words are longer then others).grouped_by { |x| x.length > 3 }.
|
120
|
+
expect([1,2,3,2,2,1,2].grouped_by { |x| x.odd? }).to eq([[1,3,1],[2,2,2,2]])
|
121
|
+
expect(%w(some words are longer then others).grouped_by { |x| x.length > 3 }).to eq([%w(some words longer then others),%w(are)])
|
122
122
|
end
|
123
123
|
|
124
|
-
it "
|
124
|
+
it "multi_map" do
|
125
125
|
|
126
126
|
folders = { 'main[2]' => { 'child[3]' => 'leaf', 'simple' => 'leaf' } }
|
127
127
|
|
128
|
-
multiply_folder = ->
|
128
|
+
multiply_folder = ->(k,v) do
|
129
129
|
match = k.match(/(.*)\[(\d+)\]/)
|
130
130
|
k, count = match ? [match[1], match[2].to_i] : [k, nil]
|
131
131
|
if count
|
@@ -136,8 +136,8 @@ describe "enumerable" do
|
|
136
136
|
end
|
137
137
|
|
138
138
|
multiplied = folders.multi_map &multiply_folder
|
139
|
-
multiplied.length.
|
140
|
-
multiplied['main_1'].length.
|
139
|
+
expect(multiplied.length).to eq(2)
|
140
|
+
expect(multiplied['main_1'].length).to eq(4)
|
141
141
|
|
142
142
|
end
|
143
|
-
end
|
143
|
+
end
|
@@ -1,44 +1,42 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
include Functions::Prelude
|
1
|
+
require 'spec_helper'
|
4
2
|
|
5
3
|
describe Functions::Prelude, "math" do
|
6
4
|
|
7
5
|
it "divides" do
|
8
|
-
Divide.([9,2]).
|
9
|
-
Divide.([8,2,2]).
|
6
|
+
expect(Divide.([9,2])).to eq(9/2)
|
7
|
+
expect(Divide.([8,2,2])).to eq(8/2/2)
|
10
8
|
end
|
11
9
|
|
12
10
|
it "power" do
|
13
|
-
Power.(2,3).
|
14
|
-
Power.(0,3).
|
15
|
-
Power.(3,2).
|
11
|
+
expect(Power.(2,3)).to eq(9)
|
12
|
+
expect(Power.(0,3)).to eq(1)
|
13
|
+
expect(Power.(3,2)).to eq(8)
|
16
14
|
end
|
17
15
|
|
18
16
|
it "knows about divisors" do
|
19
|
-
IsDivisor.(8,2).
|
20
|
-
IsDivisor.(8,3).
|
21
|
-
Divisors.(12).sort.
|
17
|
+
expect(IsDivisor.(8,2)).to eq(true)
|
18
|
+
expect(IsDivisor.(8,3)).to eq(false)
|
19
|
+
expect(Divisors.(12).sort).to eq([1,2,3,4,6,12])
|
22
20
|
end
|
23
21
|
|
24
22
|
it "knows about range building" do
|
25
|
-
FromOneTo.(3).
|
26
|
-
FromOneTo.(3).to_a.
|
27
|
-
FromTo.(2).(8).to_a.
|
23
|
+
expect(FromOneTo.(3)).to eq(1..3)
|
24
|
+
expect(FromOneTo.(3).to_a).to eq([1,2,3])
|
25
|
+
expect(FromTo.(2).(8).to_a).to eq([2,3,4,5,6,7,8])
|
28
26
|
end
|
29
27
|
|
30
28
|
it "knows about gcd" do
|
31
|
-
Gcd.(12,9).
|
32
|
-
Gcd.(4,8).
|
33
|
-
GcdA.([12,9,6]).
|
34
|
-
GcdA.([4,8,2]).
|
29
|
+
expect(Gcd.(12,9)).to eq(3)
|
30
|
+
expect(Gcd.(4,8)).to eq(4)
|
31
|
+
expect(GcdA.([12,9,6])).to eq(3)
|
32
|
+
expect(GcdA.([4,8,2])).to eq(2)
|
35
33
|
end
|
36
34
|
|
37
35
|
it "knows about lcm" do
|
38
|
-
Lcm.(12,9).
|
39
|
-
Lcm.(6,8).
|
40
|
-
LcmA.([12,9]).
|
41
|
-
LcmA.([12,9,2]).
|
36
|
+
expect(Lcm.(12,9)).to eq(36)
|
37
|
+
expect(Lcm.(6,8)).to eq(24)
|
38
|
+
expect(LcmA.([12,9])).to eq(36)
|
39
|
+
expect(LcmA.([12,9,2])).to eq(36)
|
42
40
|
end
|
43
41
|
|
44
42
|
end
|
data/spec/prelude_lambda_spec.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
include Functions::Prelude
|
1
|
+
require 'spec_helper'
|
4
2
|
|
5
3
|
describe Functions::Prelude, "basic" do
|
6
4
|
|
@@ -10,52 +8,52 @@ describe Functions::Prelude, "basic" do
|
|
10
8
|
|
11
9
|
it "composes" do
|
12
10
|
sum_of_squares = Compose.(Sum).(Squares)
|
13
|
-
sum_of_squares.([2, 3, 4]).
|
11
|
+
expect(sum_of_squares.([2, 3, 4])).to eq(4+9+16)
|
14
12
|
end
|
15
13
|
|
16
14
|
it "has a compose operator" do
|
17
15
|
sum_of_squares = Sum < Squares
|
18
|
-
sum_of_squares.([2, 3, 4]).
|
16
|
+
expect(sum_of_squares.([2, 3, 4])).to eq(4+9+16)
|
19
17
|
end
|
20
18
|
|
21
19
|
it "folds" do
|
22
20
|
inc_with = ->(f, n, m) { n + f.(m) }.curry
|
23
21
|
sum_of = ->(f, arr) { Foldl.(inc_with.(f), 0, arr) }.curry
|
24
|
-
sum_of.(Square).([1,2,3]).
|
22
|
+
expect(sum_of.(Square).([1,2,3])).to eq(1+4+9)
|
25
23
|
end
|
26
24
|
|
27
25
|
it "composes using after with implicit parallel" do
|
28
26
|
average = After.([Sum, Length]).(Divide)
|
29
|
-
average.([2, 3, 8]).
|
27
|
+
expect(average.([2, 3, 8])).to eq((2+3+8)/3)
|
30
28
|
end
|
31
29
|
|
32
30
|
it "mixes parallel with after operator" do
|
33
31
|
average = Parallel.(Sum, Length) > Divide
|
34
|
-
average.([2, 3, 8]).
|
32
|
+
expect(average.([2, 3, 8])).to eq((2+3+8)/3)
|
35
33
|
end
|
36
34
|
|
37
35
|
it "mixes par with after operator" do
|
38
36
|
average = Par.([Sum, Length]) > Divide
|
39
|
-
average.([2, 3, 8]).
|
37
|
+
expect(average.([2, 3, 8])).to eq((2+3+8)/3)
|
40
38
|
end
|
41
39
|
|
42
40
|
it "flattens arrays" do
|
43
|
-
Flatten.([[1, 2, 3], [2, 3]]).
|
41
|
+
expect(Flatten.([[1, 2, 3], [2, 3]])).to eq([1,2,3,2,3])
|
44
42
|
end
|
45
43
|
|
46
44
|
it "sorts arrays" do
|
47
|
-
|
48
|
-
|
49
|
-
|
45
|
+
expect(QuickSort.([3,3,5,6,7,1,2])).to eq([1,2,3,3,5,6,7])
|
46
|
+
expect(QuickSort.([1,1,1,1,1,1,1])).to eq([1,1,1,1,1,1,1])
|
47
|
+
expect(MergeSort.([3,3,5,6,7,1,2])).to eq([1,2,3,3,5,6,7])
|
50
48
|
end
|
51
49
|
|
52
50
|
it "merges hashes" do
|
53
51
|
a = { a: 'a', b: 'b' }
|
54
52
|
b = { a: '1', c: '3' }
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
53
|
+
expect(MergeHash.(a,b)).to eq({ a: ['a','1'], b: 'b', c: '3' })
|
54
|
+
expect(ZipHashLeft.(a,b)).to eq({ a: ['a','1'], b: ['b', nil] })
|
55
|
+
expect(ZipHashRight.(a,b)).to eq({ a: ['a','1'], c: [nil, '3'] })
|
56
|
+
expect(ZipHashInner.(a,b)).to eq({ a: ['a','1'] })
|
59
57
|
end
|
60
58
|
|
61
|
-
end
|
59
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,15 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
1
|
+
require 'coveralls'
|
2
|
+
Coveralls.wear!
|
3
|
+
|
4
|
+
require 'functions'
|
5
|
+
|
7
6
|
RSpec.configure do |config|
|
8
|
-
|
7
|
+
|
9
8
|
config.run_all_when_everything_filtered = true
|
10
9
|
config.filter_run :focus
|
10
|
+
config.raise_errors_for_deprecations!
|
11
|
+
|
12
|
+
include Functions::Prelude
|
11
13
|
|
12
14
|
# Run specs in random order to surface order dependencies. If you find an
|
13
15
|
# order dependency and want to debug it, you can fix the order by providing
|
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: functions
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Koen Handekyn
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-12-01 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description: functional programming in
|
13
|
+
description: A prelude library that belongs to the book "functional programming in
|
14
|
+
ruby".
|
14
15
|
email:
|
15
16
|
- koen.handekyn@up-nxt.com
|
16
17
|
executables: []
|
@@ -19,11 +20,13 @@ extra_rdoc_files: []
|
|
19
20
|
files:
|
20
21
|
- ".gitignore"
|
21
22
|
- ".rspec"
|
23
|
+
- ".travis.yml"
|
22
24
|
- Gemfile
|
23
25
|
- LICENSE.txt
|
24
26
|
- README.md
|
25
27
|
- Rakefile
|
26
|
-
- examples/
|
28
|
+
- examples/Gemfile
|
29
|
+
- examples/Rakefile
|
27
30
|
- examples/prelude_lambda.rb
|
28
31
|
- examples/spec/prelude_lambda_spec.rb
|
29
32
|
- functions.gemspec
|
@@ -47,7 +50,7 @@ files:
|
|
47
50
|
- test/test_prelude_performance.rb
|
48
51
|
homepage: https://github.com/koenhandekyn/functions
|
49
52
|
licenses:
|
50
|
-
-
|
53
|
+
- LGPL-3.0+
|
51
54
|
metadata: {}
|
52
55
|
post_install_message:
|
53
56
|
rdoc_options: []
|
@@ -65,7 +68,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
65
68
|
version: '0'
|
66
69
|
requirements: []
|
67
70
|
rubyforge_project:
|
68
|
-
rubygems_version: 2.
|
71
|
+
rubygems_version: 2.7.3
|
69
72
|
signing_key:
|
70
73
|
specification_version: 4
|
71
74
|
summary: functional programming in ruby
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require_relative '../lib/functions'
|
2
|
-
|
3
|
-
folders = {
|
4
|
-
'main[2]' => { 'child[3]' => 'leaf'}
|
5
|
-
}
|
6
|
-
|
7
|
-
def multiply_folder(k,v)
|
8
|
-
match = k.match(/(.*)\[(\d+)\]/)
|
9
|
-
k, count = match ? [match[1], match[2].to_i] : [k, nil]
|
10
|
-
if count
|
11
|
-
(1..count).map { |i| ["#{k}_#{i}",v] }
|
12
|
-
else
|
13
|
-
[[k,v]]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
multiplied = folders.multi_map_hash_recursive &multiply_folder
|
18
|
-
|
19
|
-
puts multiplied
|