functional-ruby 0.7.4 → 0.7.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,132 @@
1
+ module Functional
2
+
3
+ module Search
4
+ extend self
5
+
6
+ # Conduct a linear search against an unsorted collection and
7
+ # return the index where the item was found. Returns nil if
8
+ # the item is not found.
9
+ #
10
+ # The default behavior is to search the entire collections. The
11
+ # options hash can be used to provide optional low and high indexes
12
+ # (:imin and :imax). If either :imin or :imax is out of range the
13
+ # natural collection boundary will be used.
14
+ #
15
+ # When a block is given the block will be applied to both arguments.
16
+ # Using a block in this way allows computation against a specific field
17
+ # in a data set of hashes or objects.
18
+ #
19
+ # @yield iterates over each element in the data set
20
+ # @yieldparam item each element in the data set
21
+ #
22
+ # @param [Enumerable] data the data set to search
23
+ # @param [Hash] opts search options
24
+ # @param [Block] block optional block for per-item processing
25
+ #
26
+ # @option opts [Integer] :imin minimum index to search
27
+ # @option opts [Integer] :imax maximum index to search
28
+ #
29
+ # @return [Array] the index where the item is found or nil when the
30
+ # collection is empty or nil
31
+ def linear_search(data, key, opts={}, &block)
32
+
33
+ imin, imax = check_search_options(data, key, opts, &block)
34
+ return nil if imin.nil? || imax.nil?
35
+ return imin if imin == imax
36
+
37
+ index = nil
38
+ (imin..imax).each do |i|
39
+ if (block_given? && yield(data[i]) == key) || data[i] == key
40
+ index = i
41
+ break
42
+ end
43
+ end
44
+
45
+ return index
46
+ end
47
+
48
+ # Conduct a binary search against the sorted collection and return
49
+ # a pair of indexes indicating the result of the search. The
50
+ # indexes will be returned as a two-element array.
51
+ #
52
+ # The default behavior is to search the entire collections. The
53
+ # options hash can be used to provide optional low and high indexes
54
+ # (:imin and :imax). If either :imin or :imax is out of range the
55
+ # natural collection boundary will be used.
56
+ #
57
+ # When a block is given the block will be applied to both arguments.
58
+ # Using a block in this way allows computation against a specific field
59
+ # in a data set of hashes or objects.
60
+ #
61
+ # When the key is found both returned indexes will be the index of
62
+ # the item. When the key is not found but the value is within the
63
+ # range of value in the data set the returned indexes will be
64
+ # immediately above and below where the key would reside. When
65
+ # the key is below the lowest value in the search range the result
66
+ # will be nil and the lowest index. When the key is higher than the
67
+ # highest value in the search range the result will be the highest
68
+ # index and nil.
69
+ #
70
+ # @yield iterates over each element in the data set
71
+ # @yieldparam item each element in the data set
72
+ #
73
+ # @param [Enumerable] data the data set to search
74
+ # @param [Hash] opts search options
75
+ # @param [Block] block optional block for per-item processing
76
+ #
77
+ # @option opts [Integer] :imin minimum index to search
78
+ # @option opts [Integer] :imax maximum index to search
79
+ #
80
+ # @return [Array] pair of indexes (see above) or nil when the collection
81
+ # is empty or nil
82
+ def binary_search(data, key, opts={}, &block)
83
+
84
+ imin, imax = check_search_options(data, key, opts, &block)
85
+ return nil if imin.nil? && imax.nil?
86
+ return [imin, imax] if imin == imax || imin.nil? || imax.nil?
87
+
88
+ while (imax >= imin)
89
+ imid = (imin + imax) / 2
90
+ current = data[imid]
91
+ current = yield(current) if block_given?
92
+ if current < key
93
+ imin = imid + 1
94
+ elsif current > key
95
+ imax = imid - 1
96
+ else
97
+ imin = imax = imid
98
+ break
99
+ end
100
+ end
101
+
102
+ return imax, imin
103
+ end
104
+
105
+ alias_method :bsearch, :binary_search
106
+ alias_method :half_interval_search, :binary_search
107
+
108
+ private
109
+
110
+ # :nodoc:
111
+ # @private
112
+ def check_search_options(data, key, opts={})
113
+ return [nil, nil] if data.nil? || data.empty?
114
+
115
+ imin = [opts[:imin].to_i, 0].max
116
+ imax = opts[:imax].nil? ? data.size-1 : [opts[:imax], data.size-1].min
117
+ return [nil, nil] if imin > imax
118
+
119
+ if block_given?
120
+ min, max = yield(data[imin]), yield(data[imax])
121
+ else
122
+ min, max = data[imin], data[imax]
123
+ end
124
+ return [nil, imin] if key < min
125
+ return [imin, imin] if key == min
126
+ return [imax, nil] if key > max
127
+ return [imax, imax] if key == max
128
+
129
+ return [imin, imax]
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,41 @@
1
+ module Functional
2
+
3
+ module Sort
4
+ extend self
5
+
6
+ # Sorts the collection using the insertion sort algorithm.
7
+ #
8
+ # When a block is given the block will be applied to both arguments.
9
+ # Using a block in this way allows computation against a specific field
10
+ # in a data set of hashes or objects.
11
+ #
12
+ # @yield iterates over each element in the data set
13
+ # @yieldparam item each element in the data set
14
+ #
15
+ # @param [Enumerable] data the data set to search
16
+ # @param [Hash] opts search options
17
+ #
18
+ # @return [Array] the sorted collection
19
+ def insertion_sort!(data, opts={})
20
+ return data if data.nil? || data.size <= 1
21
+
22
+ (1..(data.size-1)).each do |j|
23
+
24
+ key = block_given? ? yield(data[j]) : data[j]
25
+ value = data[j]
26
+ i = j - 1
27
+ current = block_given? ? yield(data[i]) : data[i]
28
+
29
+ while i >= 0 && current > key
30
+ data[i+1] = data[i]
31
+ i = i - 1
32
+ current = block_given? ? yield(data[i]) : data[i]
33
+ end
34
+
35
+ data[i+1] = value
36
+ end
37
+
38
+ return data
39
+ end
40
+ end
41
+ end
@@ -3,9 +3,6 @@ require 'stringio'
3
3
  require 'erb'
4
4
  require 'rbconfig'
5
5
 
6
- Infinity = 1/0.0 unless defined?(Infinity)
7
- NaN = 0/0.0 unless defined?(NaN)
8
-
9
6
  module Kernel
10
7
 
11
8
  # Compute the difference (delta) between two values.
@@ -1,3 +1,3 @@
1
1
  module Functional
2
- VERSION = '0.7.4'
2
+ VERSION = '0.7.5'
3
3
  end
@@ -0,0 +1,32 @@
1
+ # Catalog
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Collection
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Inflect
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
@@ -36,13 +36,13 @@ This gem may not make much sense if you don't understand how Erlang dispatches f
36
36
  In the Ruby class file where you want to use pattern matching, require the *functional-ruby* gem:
37
37
 
38
38
  ```ruby
39
- require 'functional/pattern_matching'
39
+ require 'functional'
40
40
  ```
41
41
 
42
42
  Then include `PatternMatching` in your class:
43
43
 
44
44
  ```ruby
45
- require 'functional/pattern_matching'
45
+ require 'functional'
46
46
 
47
47
  class Foo
48
48
  include PatternMatching
@@ -0,0 +1,32 @@
1
+ # Platform
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Search
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ # Sort
2
+
3
+ TBD...
4
+
5
+ ## Copyright
6
+
7
+ *Functional Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
8
+ It is free software and may be redistributed under the terms specified in the LICENSE file.
9
+
10
+ ## License
11
+
12
+ Released under the MIT license.
13
+
14
+ http://www.opensource.org/licenses/mit-license.php
15
+
16
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
17
+ > of this software and associated documentation files (the "Software"), to deal
18
+ > in the Software without restriction, including without limitation the rights
19
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
20
+ > copies of the Software, and to permit persons to whom the Software is
21
+ > furnished to do so, subject to the following conditions:
22
+ >
23
+ > The above copyright notice and this permission notice shall be included in
24
+ > all copies or substantial portions of the Software.
25
+ >
26
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
29
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
30
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
31
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
+ > THE SOFTWARE.
@@ -1,9 +1,9 @@
1
1
  # Utility Functions
2
2
 
3
- Convenience functions are not imported by default. They need a separate `require` statement:
3
+ Start by requiring the Functional Ruby gem:
4
4
 
5
5
  ```ruby
6
- require 'functional/utilities'
6
+ require 'functional'
7
7
  ```
8
8
 
9
9
  This gives you access to a few constants and functions:
@@ -0,0 +1,1206 @@
1
+ require 'spec_helper'
2
+
3
+ module Functional
4
+
5
+ describe Catalog do
6
+
7
+ let(:hash_sample) {
8
+ {
9
+ 7 => 8,
10
+ 17 => 14,
11
+ 27 => 6,
12
+ 32 => 12,
13
+ 37 => 8,
14
+ 22 => 4,
15
+ 42 => 3,
16
+ 12 => 8,
17
+ 47 => 2
18
+ }.freeze
19
+ }
20
+
21
+ let(:hash_sample_for_block) {
22
+ {
23
+ 7 => {:count => 8},
24
+ 17 => {:count => 14},
25
+ 27 => {:count => 6},
26
+ 32 => {:count => 12},
27
+ 37 => {:count => 8},
28
+ 22 => {:count => 4},
29
+ 42 => {:count => 3},
30
+ 12 => {:count => 8},
31
+ 47 => {:count => 2}
32
+ }.freeze
33
+ }
34
+
35
+ let(:catalog_sample) {
36
+ [
37
+ [7, 8],
38
+ [17, 14],
39
+ [27, 6],
40
+ [32, 12],
41
+ [37, 8],
42
+ [22, 4],
43
+ [42, 3],
44
+ [12, 8],
45
+ [47, 2]
46
+ ].freeze
47
+ }
48
+
49
+ let(:catalog_sample_for_block) {
50
+ [
51
+ [7, {:count => 8}],
52
+ [17, {:count => 14}],
53
+ [27, {:count => 6}],
54
+ [32, {:count => 12}],
55
+ [37, {:count => 8}],
56
+ [22, {:count => 4}],
57
+ [42, {:count => 3}],
58
+ [12, {:count => 8}],
59
+ [47, {:count => 2}]
60
+ ].freeze
61
+ }
62
+
63
+ context 'creation' do
64
+
65
+ context '#initialize' do
66
+
67
+ it 'creates an empty Catalog when no arguments are given' do
68
+ catalog = Catalog.new
69
+ catalog.should be_empty
70
+ end
71
+
72
+ it 'creates a Catalog from a hash' do
73
+ catalog = Catalog.new(hash_sample, :from => :hash)
74
+ catalog.size.should eq 9
75
+ end
76
+
77
+ it 'creates a Catalog from an array' do
78
+ catalog = Catalog.new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], :from => :array)
79
+ catalog.size.should eq 5
80
+ catalog.first.should eq [1, 2]
81
+ catalog.last.should eq [9, 10]
82
+ end
83
+
84
+ it 'creates a Catalog from a catalog' do
85
+ catalog = Catalog.new(catalog_sample, :from => :catalog)
86
+ catalog.size.should eq 9
87
+ catalog.first.should eq catalog_sample.first
88
+ catalog.last.should eq catalog_sample.last
89
+
90
+ catalog = Catalog.new(catalog_sample, :from => :catalogue)
91
+ catalog.size.should eq 9
92
+ catalog.first.should eq catalog_sample.first
93
+ catalog.last.should eq catalog_sample.last
94
+ end
95
+
96
+ it 'assumes the arguments are a catalog when no :from is given' do
97
+ catalog = Catalog.new(catalog_sample)
98
+ catalog.size.should eq 9
99
+ catalog.first.should eq catalog_sample.first
100
+ catalog.last.should eq catalog_sample.last
101
+ end
102
+
103
+ it 'creates an empty Catalog when :from is unrecognized' do
104
+ catalog = Catalog.new(hash_sample, :from => :bogus)
105
+ catalog.should be_empty
106
+ end
107
+
108
+ it 'uses the given block to create key/value pairs' do
109
+ sample = [
110
+ {:x => 1, :y => 2},
111
+ {:x => 3, :y => 4},
112
+ {:x => 5, :y => 6}
113
+ ]
114
+
115
+ expected = [ [1, 2], [3, 4], [5, 6] ]
116
+
117
+ catalog = Catalog.new(sample){|item| [ item[:x], item[:y] ] }
118
+ catalog.should eq expected
119
+ end
120
+ end
121
+
122
+ context '#from_array' do
123
+
124
+ it 'creates a Catalog from an Array' do
125
+ catalog = Catalog.from_array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
126
+ catalog.size.should eq 5
127
+ catalog.first.should eq [1, 2]
128
+ catalog.last.should eq [9, 10]
129
+
130
+ catalog = Catalog.from_array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
131
+ catalog.size.should eq 5
132
+ catalog.first.should eq [1, 2]
133
+ catalog.last.should eq [9, 10]
134
+ end
135
+
136
+ it 'creates an empty Catalog from an empty Array' do
137
+ catalog = Catalog.from_array([])
138
+ catalog.should be_empty
139
+ end
140
+
141
+ it 'throws out the last element when given an odd-size array' do
142
+ catalog = Catalog.from_array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
143
+ catalog.size.should eq 5
144
+ catalog.first.should eq [1, 2]
145
+ catalog.last.should eq [9, 10]
146
+ end
147
+
148
+ it 'creates a Catalog when given an array and a block' do
149
+ sample = [
150
+ {:count => 13},
151
+ {:count => 18},
152
+ {:count => 13},
153
+ {:count => 14},
154
+ {:count => 13},
155
+ {:count => 16},
156
+ {:count => 14},
157
+ {:count => 13}
158
+ ].freeze
159
+
160
+ catalog = Catalog.from_array(sample){|item| item[:count]}
161
+ catalog.size.should eq 4
162
+ catalog.first.should eq [13, 18]
163
+ catalog.last.should eq [14, 13]
164
+ end
165
+ end
166
+
167
+ context '#from_hash' do
168
+
169
+ it 'creates a Catalog from a hash' do
170
+ catalog = Catalog.from_hash(hash_sample)
171
+ catalog.size.should eq 9
172
+
173
+ catalog = Catalog.from_hash(:one => 1, :two => 2, :three => 3)
174
+ catalog.size.should eq 3
175
+ end
176
+
177
+ it 'creates an empty Catalog from an empty hash' do
178
+ catalog = Catalog.from_hash({})
179
+ catalog.should be_empty
180
+ end
181
+
182
+ it 'creates a Catalog when given a hash and a block' do
183
+ catalog = Catalog.from_hash(hash_sample_for_block){|item| item[:count]}
184
+ catalog.size.should eq 9
185
+ end
186
+ end
187
+
188
+ context '#from_catalog' do
189
+
190
+ context 'creates a Catalog from a catalog' do
191
+
192
+ specify do
193
+ catalog = Catalog.from_catalog(catalog_sample)
194
+ catalog.size.should eq 9
195
+ catalog.first.should eq catalog_sample.first
196
+ catalog.last.should eq catalog_sample.last
197
+ end
198
+
199
+ specify do
200
+ catalog = Catalog.from_catalog([:one, 1], [:two, 2], [:three, 3])
201
+ catalog.size.should eq 3
202
+ catalog.first.should eq [:one, 1]
203
+ catalog.last.should eq [:three, 3]
204
+ end
205
+
206
+ specify do
207
+ catalog = Catalog.from_catalog([[:one, 1], [:two, 2], [:three, 3]])
208
+ catalog.size.should eq 3
209
+ catalog.first.should eq [:one, 1]
210
+ catalog.last.should eq [:three, 3]
211
+ end
212
+
213
+ specify do
214
+ catalog = Catalog.from_catalog([:one, 1], [:two, 2])
215
+ catalog.size.should eq 2
216
+ catalog.first.should eq [:one, 1]
217
+ catalog.last.should eq [:two, 2]
218
+ end
219
+
220
+ specify do
221
+ catalog = Catalog.from_catalog([[:one, 1], [:two, 2]])
222
+ catalog.size.should eq 2
223
+ catalog.first.should eq [:one, 1]
224
+ catalog.last.should eq [:two, 2]
225
+ end
226
+
227
+ specify do
228
+ catalog = Catalog.from_catalog([:one, 1])
229
+ catalog.size.should eq 1
230
+ catalog.first.should eq [:one, 1]
231
+ catalog.last.should eq [:one, 1]
232
+ end
233
+
234
+ specify do
235
+ catalog = Catalog.from_catalog([[:one, 1]])
236
+ catalog.size.should eq 1
237
+ catalog.first.should eq [:one, 1]
238
+ catalog.last.should eq [:one, 1]
239
+ end
240
+ end
241
+
242
+ it 'creates an empty Catalog from an empty catalog' do
243
+ catalog = Catalog.from_catalog({})
244
+ catalog.should be_empty
245
+ end
246
+
247
+ it 'creates a Catalog when given a catalog and a block' do
248
+ catalog = Catalog.from_catalog(catalog_sample_for_block){|item| item[:count]}
249
+ catalog.size.should eq 9
250
+ catalog.first.should eq [catalog_sample.first[0], catalog_sample.first[1]]
251
+ catalog.last.should eq [catalog_sample.last[0], catalog_sample.last[1]]
252
+ end
253
+ end
254
+ end
255
+
256
+ context '#==' do
257
+
258
+ it 'returns true for equal catalogs' do
259
+ catalog_1 = Catalog.from_hash(hash_sample)
260
+ catalog_2 = Catalog.from_hash(hash_sample)
261
+ catalog_1.should eq catalog_2
262
+ end
263
+
264
+
265
+ it 'returns false for unequal catalogs' do
266
+ catalog_1 = Catalog.new
267
+ catalog_2 = Catalog.from_hash(hash_sample)
268
+ catalog_1.should_not eq catalog_2
269
+ end
270
+
271
+ it 'compares with an equal Catalog object' do
272
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
273
+ catalog_2 = Catalog.new([[1, 1], [2, 2], [3, 3]])
274
+ catalog_1.should == catalog_2
275
+ end
276
+
277
+ it 'compares with an equal catalog array' do
278
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
279
+ catalog_2 = [[1, 1], [2, 2], [3, 3]]
280
+ catalog_1.should == catalog_2
281
+ end
282
+
283
+ it 'compares with a non-equal Catalog objects' do
284
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
285
+ catalog_2 = Catalog.new([[1, 1], [2, 2], [3, 3], [4, 4]])
286
+ catalog_1.should_not == catalog_2
287
+ end
288
+
289
+ it 'compares with a non-equal catalog array' do
290
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
291
+ catalog_2 = [[1, 1], [2, 2], [3, 3], [4, 4]]
292
+ catalog_1.should_not == catalog_2
293
+ end
294
+
295
+ it 'returns false when compared with any other object' do
296
+ Catalog.new.should_not == :foo
297
+ end
298
+ end
299
+
300
+ context '#!=' do
301
+
302
+ it 'returns false for equal catalogs' do
303
+ catalog_1 = Catalog.from_hash(hash_sample)
304
+ catalog_2 = Catalog.from_hash(hash_sample)
305
+ (catalog_1 != catalog_2).should be_false
306
+ end
307
+
308
+
309
+ it 'returns true for unequal catalogs' do
310
+ catalog_1 = Catalog.from_hash(hash_sample)
311
+ catalog_2 = Catalog.new
312
+ (catalog_1 != catalog_2).should be_true
313
+ end
314
+
315
+ it 'compares with an equal Catalog object' do
316
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
317
+ catalog_2 = Catalog.new([[1, 1], [2, 2], [3, 3]])
318
+ (catalog_1 != catalog_2).should be_false
319
+ end
320
+
321
+ it 'compares with an equal catalog array' do
322
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
323
+ catalog_2 = [[1, 1], [2, 2], [3, 3]]
324
+ (catalog_1 != catalog_2).should be_false
325
+ end
326
+
327
+ it 'compares with a non-equal Catalog objects' do
328
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
329
+ catalog_2 = Catalog.new([[1, 1], [2, 2], [3, 3], [4, 4]])
330
+ (catalog_1 != catalog_2).should be_true
331
+ end
332
+
333
+ it 'compares with a non-equal catalog array' do
334
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
335
+ catalog_2 = [[1, 1], [2, 2], [3, 3], [4, 4]]
336
+ (catalog_1 != catalog_2).should be_true
337
+ end
338
+
339
+ it 'returns true when compared with any other object' do
340
+ (Catalog.new != :foo).should be_true
341
+ end
342
+ end
343
+
344
+ context '<=>' do
345
+
346
+ let(:low) { [[1, 1], [2, 2]] }
347
+ let(:high) { [[3, 3], [4, 4]] }
348
+
349
+ it 'returns a negative number when less than another Catalog' do
350
+ small = Catalog.new(low)
351
+ big = Catalog.new(high)
352
+ (small <=> big).should < 0
353
+ end
354
+
355
+ it 'returns a negative number when less than a catalog' do
356
+ small = Catalog.new(low)
357
+ (small <=> high).should < 0
358
+ end
359
+
360
+ it 'returns zero when equal to another Catalog' do
361
+ small = Catalog.new(low)
362
+ big = Catalog.new(low)
363
+ (small <=> big).should eq 0
364
+ end
365
+
366
+ it 'returns zero when equal to a catalog' do
367
+ small = Catalog.new(low)
368
+ (small <=> low).should eq 0
369
+ end
370
+
371
+ it 'returns a positive number when greater than another Catalog' do
372
+ small = Catalog.new(low)
373
+ big = Catalog.new(high)
374
+ (big <=> small).should > 0
375
+ end
376
+
377
+ it 'returns a positive number when greater than a catalog' do
378
+ big = Catalog.new(high)
379
+ (big <=> low).should > 0
380
+ end
381
+
382
+ it 'raises an error when compated to an invalid object' do
383
+ lambda {
384
+ Catalog.new <=> :foo
385
+ }.should raise_error(TypeError)
386
+ end
387
+ end
388
+
389
+ context '#[]' do
390
+
391
+ it 'returns nil when empty' do
392
+ catalog = Catalog.new
393
+ catalog[0].should be_nil
394
+ end
395
+
396
+ it 'returns the element at a valid positive index' do
397
+ catalog = Catalog.from_catalog(catalog_sample)
398
+ catalog[0].should eq catalog_sample[0]
399
+ end
400
+
401
+ it 'returns the element at a valid negative index' do
402
+ catalog = Catalog.from_catalog(catalog_sample)
403
+ catalog[-1].should eq catalog_sample[-1]
404
+ end
405
+
406
+ it 'returns nil for an invalid positive index' do
407
+ catalog = Catalog.from_catalog(catalog_sample)
408
+ catalog[100].should be_nil
409
+ end
410
+
411
+ it 'returns nil for an invalid negative index' do
412
+ catalog = Catalog.from_catalog(catalog_sample)
413
+ catalog[-100].should be_nil
414
+ end
415
+ end
416
+
417
+ context '#[]=' do
418
+
419
+ let(:catalog) { Catalog.from_hash(:one => 1, :two => 2, :three => 3) }
420
+
421
+ it 'accepts a one-element hash as a value' do
422
+ catalog[0] = {:foo => :bar}
423
+ catalog[0].should eq [:foo, :bar]
424
+ end
425
+
426
+ it 'accepts a two-element array as a value' do
427
+ catalog[0] = [:foo, :bar]
428
+ catalog[0].should eq [:foo, :bar]
429
+ end
430
+
431
+ it 'raises an exception when given in invalid value' do
432
+ lambda {
433
+ catalog[0] = :foo
434
+ }.should raise_error(ArgumentError)
435
+ end
436
+
437
+ it 'updates the index when given a valid positive index' do
438
+ catalog[1] = [:foo, :bar]
439
+ catalog.should eq [[:one, 1], [:foo, :bar], [:three, 3]]
440
+ end
441
+
442
+ it 'updates the index when given an invalid negative index' do
443
+ catalog[-2] = [:foo, :bar]
444
+ catalog.should eq [[:one, 1], [:foo, :bar], [:three, 3]]
445
+ end
446
+
447
+ it 'raises an exception when given an invalid positive index' do
448
+ lambda {
449
+ catalog[100] = [:foo, :bar]
450
+ }.should raise_error(ArgumentError)
451
+ end
452
+
453
+ it 'raises an exception when given an invalid negative index' do
454
+ lambda {
455
+ catalog[-100] = [:foo, :bar]
456
+ }.should raise_error(ArgumentError)
457
+ end
458
+
459
+ end
460
+
461
+ context '#&' do
462
+
463
+ it 'returns a new Catalog object' do
464
+ catalog_1 = Catalog.new
465
+ catalog_2 = Catalog.new
466
+ intersection = catalog_1 & catalog_2
467
+ intersection.object_id.should_not eq catalog_1.object_id
468
+ intersection.object_id.should_not eq catalog_2.object_id
469
+ end
470
+
471
+ it 'intersects two empty Catalogs' do
472
+ catalog_1 = Catalog.new
473
+ catalog_2 = Catalog.new
474
+ intersection = catalog_1 & catalog_2
475
+ intersection.should be_empty
476
+ end
477
+
478
+ it 'intersects an empty Catalog with a non-empty Catalog' do
479
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
480
+ catalog_2 = Catalog.new
481
+ intersection = catalog_1 & catalog_2
482
+ intersection.should be_empty
483
+ end
484
+
485
+ it 'intersects two non-empty Catalogs' do
486
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
487
+ catalog_2 = Catalog.new([[2, 2], [3, 3], [4, 4], [5, 5]])
488
+ intersection = catalog_1 & catalog_2
489
+ intersection.should eq [[2, 2], [3, 3]]
490
+ end
491
+
492
+ it 'intersects a Catalog object with a catalog' do
493
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
494
+ catalog_2 = [[2, 2], [3, 3], [4, 4], [5, 5]]
495
+ intersection = catalog_1 & catalog_2
496
+ intersection.should eq [[2, 2], [3, 3]]
497
+ end
498
+
499
+ it 'removes duplicates when intersecting two non-empty Catalogs' do
500
+ catalog_1 = Catalog.new([[1, 1], [1, 1], [1, 1], [2, 2], [3, 3]])
501
+ catalog_2 = Catalog.new([[1, 1], [3, 3], [4, 4], [5, 5]])
502
+ intersection = catalog_1 & catalog_2
503
+ intersection.should eq [[1, 1], [3, 3]]
504
+ end
505
+
506
+ it 'raises an error when given a non-Catalog object' do
507
+ lambda {
508
+ catalog_1 = Catalog.new([[1, 1], [1, 1], [1, 1], [2, 2], [3, 3]])
509
+ intersection = catalog_1 & :foo
510
+ }.should raise_error(TypeError)
511
+ end
512
+ end
513
+
514
+ context '#+' do
515
+
516
+ it 'returns a new Catalog object' do
517
+ catalog_1 = Catalog.new
518
+ catalog_2 = Catalog.new
519
+ sum = catalog_1 + catalog_2
520
+ sum.object_id.should_not eq catalog_1.object_id
521
+ sum.object_id.should_not eq catalog_2.object_id
522
+ end
523
+
524
+ it 'adds two empty Catalogs' do
525
+ catalog_1 = Catalog.new
526
+ catalog_2 = Catalog.new
527
+ sum = catalog_1 + catalog_2
528
+ sum.should be_empty
529
+ end
530
+
531
+ it 'adds an empty Catalog with a non-empty Catalog' do
532
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
533
+ catalog_2 = Catalog.new
534
+ sum = catalog_1 + catalog_2
535
+ sum.should eq [[1, 1], [2, 2], [3, 3]]
536
+ end
537
+
538
+ it 'adds two non-empty Catalogs' do
539
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
540
+ catalog_2 = Catalog.new([[2, 2], [3, 3], [4, 4], [5, 5]])
541
+ sum = catalog_1 + catalog_2
542
+ sum.should eq [[1, 1], [2, 2], [3, 3], [2, 2], [3, 3], [4, 4], [5, 5]]
543
+ end
544
+
545
+ it 'adds a Catalog object with a catalog' do
546
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
547
+ catalog_2 = [[2, 2], [3, 3], [4, 4], [5, 5]]
548
+ sum = catalog_1 + catalog_2
549
+ sum.should eq [[1, 1], [2, 2], [3, 3],[2, 2], [3, 3], [4, 4], [5, 5] ]
550
+ end
551
+
552
+ it 'raises an error when given a non-Catalog object' do
553
+ lambda {
554
+ catalog_1 = Catalog.new([[1, 1], [1, 1], [1, 1], [2, 2], [3, 3]])
555
+ sum = catalog_1 + :foo
556
+ }.should raise_error(TypeError)
557
+ end
558
+ end
559
+
560
+ context '#|' do
561
+
562
+ it 'returns a new Catalog object' do
563
+ catalog_1 = Catalog.new
564
+ catalog_2 = Catalog.new
565
+ union = catalog_1 | catalog_2
566
+ union.object_id.should_not eq catalog_1.object_id
567
+ union.object_id.should_not eq catalog_2.object_id
568
+ end
569
+
570
+ it 'unions two empty Catalogs' do
571
+ catalog_1 = Catalog.new
572
+ catalog_2 = Catalog.new
573
+ union = catalog_1 | catalog_2
574
+ union.should be_empty
575
+ end
576
+
577
+ it 'unions an empty Catalog with a non-empty Catalog' do
578
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
579
+ catalog_2 = Catalog.new
580
+ union = catalog_1 | catalog_2
581
+ union.should eq [[1, 1], [2, 2], [3, 3]]
582
+ end
583
+
584
+ it 'unions two non-empty Catalogs' do
585
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
586
+ catalog_2 = Catalog.new([[4, 4], [5, 5]])
587
+ union = catalog_1 | catalog_2
588
+ union.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
589
+ end
590
+
591
+ it 'unions a Catalog object with a catalog' do
592
+ catalog_1 = Catalog.new([[1, 1], [2, 2], [3, 3]])
593
+ catalog_2 = [[4, 4], [5, 5]]
594
+ union = catalog_1 | catalog_2
595
+ union.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
596
+ end
597
+
598
+ it 'removes duplicates when intersecting two non-empty Catalogs' do
599
+ catalog_1 = Catalog.new([[1, 1], [1, 1], [1, 1], [2, 2], [3, 3]])
600
+ catalog_2 = Catalog.new([[1, 1], [3, 3], [4, 4], [5, 5]])
601
+ union = catalog_1 | catalog_2
602
+ union.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
603
+ end
604
+
605
+ it 'raises an error when given a non-Catalog object' do
606
+ lambda {
607
+ catalog_1 = Catalog.new([[1, 1], [1, 1], [1, 1], [2, 2], [3, 3]])
608
+ union = catalog_1 | :foo
609
+ }.should raise_error(TypeError)
610
+ end
611
+ end
612
+
613
+ context '#push' do
614
+
615
+ it 'returns the Catalog' do
616
+ catalog_1 = Catalog.new
617
+ catalog_2 = catalog_1.push([1, 2])
618
+ catalog_1.object_id.should eq catalog_2.object_id
619
+ end
620
+
621
+ it 'appends a two-element array onto the catalog' do
622
+ catalog = Catalog.new([[1, 1], [2, 2]])
623
+ catalog = catalog.push([3, 3])
624
+ catalog.should eq [[1, 1], [2, 2], [3, 3]]
625
+ end
626
+
627
+ it 'appends a one-element hash onto the catalog' do
628
+ catalog = Catalog.new([[1, 1], [2, 2]])
629
+ catalog = catalog.push({3 => 3})
630
+ catalog.should eq [[1, 1], [2, 2], [3, 3]]
631
+ end
632
+
633
+ it 'raises an error for an invalid datatype' do
634
+ lambda {
635
+ Catalog.new.push(:foo)
636
+ }.should raise_error(TypeError)
637
+ end
638
+ end
639
+
640
+ context '#pop' do
641
+
642
+ it 'returns nil when empty' do
643
+ Catalog.new.pop.should be_nil
644
+ end
645
+
646
+ it 'returns the last element' do
647
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
648
+ pop = catalog.pop
649
+ pop.should eq [3, 3]
650
+ end
651
+
652
+ it 'removes the last element' do
653
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
654
+ catalog.pop
655
+ catalog.should eq [[1, 1], [2, 2]]
656
+ end
657
+ end
658
+
659
+ context '#peek' do
660
+
661
+ it 'returns nil when empty' do
662
+ Catalog.new.peek.should be_nil
663
+ end
664
+
665
+ it 'returns the last element' do
666
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
667
+ peek = catalog.pop
668
+ peek.should eq [3, 3]
669
+ end
670
+
671
+ it 'does not remove the last element' do
672
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
673
+ catalog.peek
674
+ catalog.should eq [[1, 1], [2, 2], [3, 3]]
675
+ end
676
+ end
677
+
678
+ context '#keys' do
679
+
680
+ it 'returns an empty array when empty' do
681
+ Catalog.new.keys.should be_empty
682
+ end
683
+
684
+ it 'returns an array with all first elements in the catalog' do
685
+ catalog = Catalog.new([[0, 0], [0, 1], [1, 0], [1, 1], [2, 2], [3, 3]])
686
+ keys = catalog.keys
687
+ keys.should eq [0, 0, 1, 1, 2, 3]
688
+ end
689
+ end
690
+
691
+ context '#values' do
692
+
693
+ it 'returns an empty array when empty' do
694
+ Catalog.new.values.should be_empty
695
+ end
696
+
697
+ it 'returns an array with all last elements in the catalog' do
698
+ catalog = Catalog.new([[0, 0], [0, 1], [1, 0], [1, 1], [2, 2], [3, 3]])
699
+ values = catalog.values
700
+ values.should eq [0, 1, 0, 1, 2, 3]
701
+ end
702
+ end
703
+
704
+ context '#first' do
705
+
706
+ it 'returns nil when empty' do
707
+ Catalog.new.first.should be_nil
708
+ end
709
+
710
+ it 'returns the first element when not empty' do
711
+ catalog = Catalog.from_catalog(catalog_sample)
712
+ catalog.first.should == catalog_sample.first
713
+ end
714
+ end
715
+
716
+ context '#last' do
717
+
718
+ it 'returns nil when empty' do
719
+ Catalog.new.last.should be_nil
720
+ end
721
+
722
+ it 'returns the last element when not empty' do
723
+ catalog = Catalog.from_catalog(catalog_sample)
724
+ catalog.last.should == catalog_sample.last
725
+ end
726
+ end
727
+
728
+ context '#iterators' do
729
+
730
+ let(:sample) {
731
+ [
732
+ [7, 8],
733
+ [17, 14],
734
+ [27, 6],
735
+ [32, 12],
736
+ [37, 8],
737
+ [22, 4],
738
+ [42, 3],
739
+ [12, 8],
740
+ [47, 2]
741
+ ].freeze
742
+ }
743
+
744
+ let(:catalog) { Catalog.new(sample) }
745
+
746
+ specify '#each' do
747
+
748
+ index = 0
749
+ catalog.each do |item|
750
+ item.should eq sample[index]
751
+ index = index + 1
752
+ end
753
+ end
754
+
755
+ specify '#each_pair' do
756
+
757
+ index = 0
758
+ catalog.each_pair do |key, value|
759
+ key.should eq sample[index].first
760
+ value.should eq sample[index].last
761
+ index = index + 1
762
+ end
763
+ end
764
+
765
+ specify '#each_key' do
766
+
767
+ index = 0
768
+ catalog.each_key do |key|
769
+ key.should eq sample[index].first
770
+ index = index + 1
771
+ end
772
+ end
773
+
774
+ specify '#each_value' do
775
+
776
+ index = 0
777
+ catalog.each_value do |value|
778
+ value.should eq sample[index].last
779
+ index = index + 1
780
+ end
781
+ end
782
+ end
783
+
784
+ context '#empty?' do
785
+
786
+ it 'returns true when empty' do
787
+ catalog = Catalog.new
788
+ catalog.should be_empty
789
+ end
790
+
791
+ it 'returns false when not empty' do
792
+ catalog = Catalog.from_hash(:one => 1, :two => 2, :three => 3)
793
+ catalog.should_not be_empty
794
+ end
795
+ end
796
+
797
+ context '#include?' do
798
+
799
+ it 'returns false when empty' do
800
+ Catalog.new.include?([1, 1]).should be_false
801
+ end
802
+
803
+ it 'returns true when the key/value array is found' do
804
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
805
+ catalog.include?([2, 2]).should be_true
806
+ end
807
+
808
+ it 'returns true when the key/value pair is found' do
809
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
810
+ catalog.include?(2, 2).should be_true
811
+ end
812
+
813
+ it 'returns true for given a one-element hash that matches' do
814
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
815
+ catalog.include?({2 => 2}).should be_true
816
+ end
817
+
818
+ it 'returns true for an implicit one-element hash that matches' do
819
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
820
+ catalog.include?(2 => 2).should be_true
821
+ end
822
+
823
+ it 'returns false when not found' do
824
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
825
+ catalog.include?([4, 4]).should be_false
826
+ end
827
+
828
+ it 'returns false when given an invalid lookup item' do
829
+ catalog = Catalog.new([[1, 1], [2, 2], [3, 3]])
830
+ catalog.include?(:foo).should be_false
831
+ end
832
+ end
833
+
834
+ context '#size' do
835
+
836
+ it 'returns zero when is empty' do
837
+ catalog = Catalog.new
838
+ catalog.size.should eq 0
839
+ end
840
+
841
+ it 'returns the correct positive integer when not empty' do
842
+ catalog = Catalog.from_hash(:one => 1, :two => 2, :three => 3)
843
+ catalog.size.should eq 3
844
+ end
845
+ end
846
+
847
+ context '#slice' do
848
+
849
+ let(:catalog) {
850
+ Catalog.new([[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]])
851
+ }
852
+
853
+ it 'returns the element at index' do
854
+ slice = catalog.slice(2)
855
+ slice.should eq [[3, 3]]
856
+ slice.should be_a Catalog
857
+ catalog.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
858
+ end
859
+
860
+ it 'returns the elements from index through length' do
861
+ slice = catalog.slice(1, 2)
862
+ slice.should eq [[2, 2], [3, 3]]
863
+ slice.should be_a Catalog
864
+ catalog.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
865
+ end
866
+
867
+ it 'returns a catalog specified by range' do
868
+ slice = catalog.slice(1..2)
869
+ slice.should eq [[2, 2], [3, 3]]
870
+ slice.should be_a Catalog
871
+ catalog.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
872
+ end
873
+
874
+ it 'returns the element at the negative index' do
875
+ slice = catalog.slice(-3)
876
+ slice.should eq [[3, 3]]
877
+ slice.should be_a Catalog
878
+ catalog.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
879
+ end
880
+
881
+ it 'returns an empty Catalog if the index is out of range' do
882
+ slice = catalog.slice(10, 2)
883
+ slice.should be_empty
884
+ slice.should be_a Catalog
885
+ catalog.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
886
+ end
887
+ end
888
+
889
+ context '#slice!' do
890
+
891
+ let(:catalog) {
892
+ Catalog.new([[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]])
893
+ }
894
+
895
+ it 'returns the element at index' do
896
+ slice = catalog.slice!(2)
897
+ slice.should eq [[3, 3]]
898
+ slice.should be_a Catalog
899
+ catalog.should eq [[1, 1], [2, 2], [4, 4], [5, 5]]
900
+ end
901
+
902
+ it 'returns the elements from index through length' do
903
+ slice = catalog.slice!(1, 2)
904
+ slice.should eq [[2, 2], [3, 3]]
905
+ slice.should be_a Catalog
906
+ catalog.should eq [[1, 1], [4, 4], [5, 5]]
907
+ end
908
+
909
+ it 'returns a catalog specified by range' do
910
+ slice = catalog.slice!(1..2)
911
+ slice.should eq [[2, 2], [3, 3]]
912
+ slice.should be_a Catalog
913
+ catalog.should eq [[1, 1], [4, 4], [5, 5]]
914
+ end
915
+
916
+ it 'returns the element at the negative index' do
917
+ slice = catalog.slice!(-3)
918
+ slice.should eq [[3, 3]]
919
+ slice.should be_a Catalog
920
+ catalog.should eq [[1, 1], [2, 2], [4, 4], [5, 5]]
921
+ end
922
+
923
+ it 'returns an empty Catalog if the index is out of range' do
924
+ slice = catalog.slice!(10, 2)
925
+ slice.should be_empty
926
+ slice.should be_a Catalog
927
+ catalog.should eq [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5]]
928
+ end
929
+ end
930
+
931
+ context 'sorting' do
932
+
933
+ let(:unsorted_catalog) {
934
+ [
935
+ [7, 8],
936
+ [17, 14],
937
+ [27, 6],
938
+ [32, 12],
939
+ [37, 9],
940
+ [22, 4],
941
+ [42, 3],
942
+ [12, 10],
943
+ [47, 2]
944
+ ].freeze
945
+ }
946
+
947
+ let(:catalog_sorted_by_key) {
948
+ [
949
+ [7, 8],
950
+ [12, 10],
951
+ [17, 14],
952
+ [22, 4],
953
+ [27, 6],
954
+ [32, 12],
955
+ [37, 9],
956
+ [42, 3],
957
+ [47, 2]
958
+ ].freeze
959
+ }
960
+
961
+ let(:catalog_sorted_by_value) {
962
+ [
963
+ [47, 2],
964
+ [42, 3],
965
+ [22, 4],
966
+ [27, 6],
967
+ [7, 8],
968
+ [37, 9],
969
+ [12, 10],
970
+ [32, 12],
971
+ [17, 14]
972
+ ].freeze
973
+ }
974
+
975
+ let(:catalog_reversed) {
976
+ [
977
+ [47, 2],
978
+ [42, 3],
979
+ [37, 9],
980
+ [32, 12],
981
+ [27, 6],
982
+ [22, 4],
983
+ [17, 14],
984
+ [12, 10],
985
+ [7, 8]
986
+ ].freeze
987
+ }
988
+
989
+ let(:catalog) { Catalog.new(unsorted_catalog) }
990
+
991
+ specify '#sort_by_key' do
992
+ sorted = catalog.sort_by_key
993
+ sorted.should eq catalog_sorted_by_key
994
+ catalog.should eq unsorted_catalog
995
+ sorted.should be_a Catalog
996
+ end
997
+
998
+ specify '#sort_by_key!' do
999
+ sorted = catalog.sort_by_key!
1000
+ sorted.should eq catalog_sorted_by_key
1001
+ catalog.should eq catalog_sorted_by_key
1002
+ sorted.object_id.should eq catalog.object_id
1003
+ end
1004
+
1005
+ specify '#sort_by_value' do
1006
+ sorted = catalog.sort_by_value
1007
+ sorted.should eq catalog_sorted_by_value
1008
+ catalog.should eq unsorted_catalog
1009
+ sorted.should be_a Catalog
1010
+ end
1011
+
1012
+ specify '#sort_by_value!' do
1013
+ sorted = catalog.sort_by_value!
1014
+ sorted.should eq catalog_sorted_by_value
1015
+ catalog.should eq catalog_sorted_by_value
1016
+ sorted.object_id.should eq catalog.object_id
1017
+ end
1018
+
1019
+ specify '#sort' do
1020
+ sorted = catalog.sort
1021
+ sorted.should eq catalog_sorted_by_key
1022
+ catalog.should eq unsorted_catalog
1023
+ sorted.should be_a Catalog
1024
+ end
1025
+
1026
+ specify '#sort!' do
1027
+ sorted = catalog.sort!
1028
+ sorted.should eq catalog_sorted_by_key
1029
+ catalog.should eq catalog_sorted_by_key
1030
+ sorted.object_id.should eq catalog.object_id
1031
+ end
1032
+
1033
+ specify '#sort with block' do
1034
+ sorted = catalog.sort{|a, b| b <=> a}
1035
+ sorted.should eq catalog_reversed
1036
+ catalog.should eq unsorted_catalog
1037
+ sorted.should be_a Catalog
1038
+ end
1039
+
1040
+ specify '#sort! with block' do
1041
+ sorted = catalog.sort!{|a, b| b <=> a}
1042
+ sorted.should eq catalog_reversed
1043
+ catalog.should eq catalog_reversed
1044
+ sorted.should be_a Catalog
1045
+ end
1046
+ end
1047
+
1048
+ context 'conversion' do
1049
+
1050
+ let(:sample) {
1051
+ [
1052
+ [7, 8],
1053
+ [17, 14],
1054
+ [47, 2]
1055
+ ].freeze
1056
+ }
1057
+
1058
+ let(:sample_as_hash) {
1059
+ {
1060
+ 7 => 8,
1061
+ 17 => 14,
1062
+ 47 => 2
1063
+ }.freeze
1064
+ }
1065
+
1066
+ let(:sample_as_array) {
1067
+ [7, 8, 17, 14, 47, 2]
1068
+ }
1069
+
1070
+ let(:catalog) { Catalog.new(sample) }
1071
+
1072
+ context '#to_a' do
1073
+
1074
+ specify { Catalog.new.to_a.should eq [] }
1075
+
1076
+ specify { catalog.to_a.should eq sample_as_array }
1077
+ end
1078
+
1079
+ context '#to_hash' do
1080
+
1081
+ specify { Catalog.new.to_hash.should == {} }
1082
+
1083
+ specify { catalog.to_hash.should eq sample_as_hash }
1084
+ end
1085
+
1086
+ context '#to_catalog' do
1087
+ specify { Catalog.new.to_catalog.should eq [] }
1088
+
1089
+ specify do
1090
+ cat = catalog.to_catalog
1091
+ cat.should eq sample
1092
+ cat.should be_a Array
1093
+ end
1094
+ end
1095
+
1096
+ context '#to_s' do
1097
+
1098
+ specify { Catalog.new.to_s.should eq '[]' }
1099
+ specify { Catalog.from_hash(:one => 1, :two => 2).to_s.should eq '[[:one, 1], [:two, 2]]' }
1100
+ end
1101
+ end
1102
+
1103
+ context 'deletion' do
1104
+
1105
+ let(:sample) {
1106
+ [
1107
+ [7, 8],
1108
+ [17, 14],
1109
+ [47, 2]
1110
+ ].freeze
1111
+ }
1112
+
1113
+ let(:catalog) { Catalog.new(sample) }
1114
+
1115
+ context 'delete' do
1116
+
1117
+ it 'deletes the specified item' do
1118
+ item = catalog.delete([17, 14])
1119
+ item.should eq [17, 14]
1120
+ catalog.should eq [[7, 8], [47, 2]]
1121
+ end
1122
+
1123
+ it 'deletes the item matching the given key/value pair' do
1124
+ item = catalog.delete(17, 14)
1125
+ item.should eq [17, 14]
1126
+ catalog.should eq [[7, 8], [47, 2]]
1127
+ end
1128
+
1129
+ it 'deletes the item matching the given one-item hash' do
1130
+ item = catalog.delete({17 => 14})
1131
+ item.should eq [17, 14]
1132
+ catalog.should eq [[7, 8], [47, 2]]
1133
+ end
1134
+
1135
+ it 'deletes the item matching the implied one-item hash' do
1136
+ item = catalog.delete(17 => 14)
1137
+ item.should eq [17, 14]
1138
+ catalog.should eq [[7, 8], [47, 2]]
1139
+ end
1140
+
1141
+ it 'returns nil if the item is not found' do
1142
+ item = catalog.delete([1, 2])
1143
+ item.should be_nil
1144
+ end
1145
+
1146
+ it 'returns the result of the given block if the item is not found' do
1147
+ item = catalog.delete([1, 2]){ 'not found' }
1148
+ item.should eq 'not found'
1149
+ end
1150
+ end
1151
+
1152
+ context 'delete_at' do
1153
+
1154
+ it 'deletes the item at the specified index' do
1155
+ item = catalog.delete_at(1)
1156
+ item.should eq [17, 14]
1157
+ catalog.should eq [[7, 8], [47, 2]]
1158
+ end
1159
+
1160
+ it 'returns nil if the index is out of range' do
1161
+ item = catalog.delete_at(100)
1162
+ item.should be_nil
1163
+ end
1164
+
1165
+ it 'returns the result of the given block if the index is out of range' do
1166
+ item = catalog.delete_at(100){ 'not found' }
1167
+ item.should eq 'not found'
1168
+ end
1169
+ end
1170
+
1171
+ context 'delete_if' do
1172
+
1173
+ it 'can yield the key/value pair on iteration' do
1174
+ catalog.delete_if {|item| item.last > 5}
1175
+ catalog.should eq [[47, 2]]
1176
+ end
1177
+
1178
+ it 'can yield the key and value separately on iteration' do
1179
+ catalog.delete_if {|key, value| value > 5}
1180
+ catalog.should eq [[47, 2]]
1181
+ end
1182
+
1183
+ it 'removes the matched items' do
1184
+ catalog.delete_if {|key, value| value > 5}
1185
+ catalog.should eq [[47, 2]]
1186
+ end
1187
+
1188
+ it 'does nothing on no matches' do
1189
+ result = catalog.delete_if {|item| false }
1190
+ result.should eq catalog
1191
+ end
1192
+
1193
+ it 'returns self' do
1194
+ result = catalog.delete_if {|key, value| value > 5}
1195
+ result.object_id.should eq catalog.object_id
1196
+ end
1197
+
1198
+ it 'raises an exception if a block is not given' do
1199
+ lambda {
1200
+ catalog.delete_if
1201
+ }.should raise_error(ArgumentError)
1202
+ end
1203
+ end
1204
+ end
1205
+ end
1206
+ end