functions 0.0.16 → 0.0.17
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://img.shields.io/gem/v/functions.svg)](https://rubygems.org/gems/functions)
|
3
|
+
[![Dependency Status](http://img.shields.io/gemnasium/koenhandekyn/functions.svg)](https://gemnasium.com/koenhandekyn/functions)
|
4
|
+
[![Build Status](http://img.shields.io/travis/koenhandekyn/functions.svg)](https://travis-ci.org/koenhandekyn/functions)
|
5
|
+
[![Code Climate](http://img.shields.io/codeclimate/github/koenhandekyn/functions.svg)](https://codeclimate.com/github/koenhandekyn/functions)
|
6
|
+
[![Coverage Status](https://img.shields.io/coveralls/koenhandekyn/functions.svg)](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
|