functional-ruby 0.7.4 → 0.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,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