daru 0.1.3.1 → 0.1.4

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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.rspec +2 -1
  4. data/.rspec_formatter.rb +33 -0
  5. data/.rubocop.yml +26 -2
  6. data/History.md +38 -0
  7. data/README.md +22 -13
  8. data/Rakefile +50 -2
  9. data/benchmarks/csv_reading.rb +22 -0
  10. data/daru.gemspec +9 -2
  11. data/lib/daru.rb +36 -4
  12. data/lib/daru/accessors/array_wrapper.rb +6 -1
  13. data/lib/daru/accessors/dataframe_by_row.rb +10 -2
  14. data/lib/daru/accessors/gsl_wrapper.rb +1 -3
  15. data/lib/daru/accessors/nmatrix_wrapper.rb +9 -0
  16. data/lib/daru/category.rb +935 -0
  17. data/lib/daru/core/group_by.rb +29 -38
  18. data/lib/daru/core/merge.rb +186 -145
  19. data/lib/daru/core/query.rb +22 -11
  20. data/lib/daru/dataframe.rb +976 -885
  21. data/lib/daru/date_time/index.rb +166 -166
  22. data/lib/daru/date_time/offsets.rb +66 -77
  23. data/lib/daru/formatters/table.rb +54 -0
  24. data/lib/daru/helpers/array.rb +40 -0
  25. data/lib/daru/index.rb +476 -73
  26. data/lib/daru/io/io.rb +66 -45
  27. data/lib/daru/io/sql_data_source.rb +33 -62
  28. data/lib/daru/iruby/helpers.rb +38 -0
  29. data/lib/daru/iruby/templates/dataframe.html.erb +52 -0
  30. data/lib/daru/iruby/templates/dataframe_mi.html.erb +58 -0
  31. data/lib/daru/iruby/templates/multi_index.html.erb +12 -0
  32. data/lib/daru/iruby/templates/vector.html.erb +27 -0
  33. data/lib/daru/iruby/templates/vector_mi.html.erb +36 -0
  34. data/lib/daru/maths/arithmetic/dataframe.rb +16 -18
  35. data/lib/daru/maths/arithmetic/vector.rb +4 -6
  36. data/lib/daru/maths/statistics/dataframe.rb +8 -15
  37. data/lib/daru/maths/statistics/vector.rb +120 -98
  38. data/lib/daru/monkeys.rb +12 -40
  39. data/lib/daru/plotting/gruff.rb +3 -0
  40. data/lib/daru/plotting/gruff/category.rb +49 -0
  41. data/lib/daru/plotting/gruff/dataframe.rb +91 -0
  42. data/lib/daru/plotting/gruff/vector.rb +57 -0
  43. data/lib/daru/plotting/nyaplot.rb +3 -0
  44. data/lib/daru/plotting/nyaplot/category.rb +34 -0
  45. data/lib/daru/plotting/nyaplot/dataframe.rb +187 -0
  46. data/lib/daru/plotting/nyaplot/vector.rb +46 -0
  47. data/lib/daru/vector.rb +694 -421
  48. data/lib/daru/version.rb +1 -1
  49. data/profile/_base.rb +23 -0
  50. data/profile/df_to_a.rb +10 -0
  51. data/profile/filter.rb +13 -0
  52. data/profile/joining.rb +13 -0
  53. data/profile/sorting.rb +12 -0
  54. data/profile/vector_each_with_index.rb +9 -0
  55. data/spec/accessors/wrappers_spec.rb +2 -4
  56. data/spec/categorical_spec.rb +1734 -0
  57. data/spec/core/group_by_spec.rb +52 -2
  58. data/spec/core/merge_spec.rb +63 -2
  59. data/spec/core/query_spec.rb +236 -80
  60. data/spec/dataframe_spec.rb +1373 -79
  61. data/spec/date_time/data_spec.rb +3 -5
  62. data/spec/date_time/index_spec.rb +154 -17
  63. data/spec/date_time/offsets_spec.rb +3 -4
  64. data/spec/fixtures/empties.dat +2 -0
  65. data/spec/fixtures/strings.dat +2 -0
  66. data/spec/formatters/table_formatter_spec.rb +99 -0
  67. data/spec/helpers_spec.rb +8 -0
  68. data/spec/index/categorical_index_spec.rb +168 -0
  69. data/spec/index/index_spec.rb +283 -0
  70. data/spec/index/multi_index_spec.rb +570 -0
  71. data/spec/io/io_spec.rb +31 -4
  72. data/spec/io/sql_data_source_spec.rb +0 -1
  73. data/spec/iruby/dataframe_spec.rb +172 -0
  74. data/spec/iruby/helpers_spec.rb +49 -0
  75. data/spec/iruby/multi_index_spec.rb +37 -0
  76. data/spec/iruby/vector_spec.rb +107 -0
  77. data/spec/math/arithmetic/dataframe_spec.rb +71 -13
  78. data/spec/math/arithmetic/vector_spec.rb +8 -10
  79. data/spec/math/statistics/dataframe_spec.rb +3 -5
  80. data/spec/math/statistics/vector_spec.rb +45 -55
  81. data/spec/monkeys_spec.rb +32 -9
  82. data/spec/plotting/dataframe_spec.rb +386 -0
  83. data/spec/plotting/vector_spec.rb +230 -0
  84. data/spec/shared/vector_display_spec.rb +215 -0
  85. data/spec/spec_helper.rb +23 -0
  86. data/spec/vector_spec.rb +905 -138
  87. metadata +143 -11
  88. data/.rubocop_todo.yml +0 -44
  89. data/lib/daru/plotting/dataframe.rb +0 -104
  90. data/lib/daru/plotting/vector.rb +0 -38
  91. data/spec/daru_spec.rb +0 -58
  92. data/spec/index_spec.rb +0 -375
@@ -0,0 +1,570 @@
1
+ require 'spec_helper.rb'
2
+
3
+ describe Daru::MultiIndex do
4
+ before(:each) do
5
+ @index_tuples = [
6
+ [:a,:one,:bar],
7
+ [:a,:one,:baz],
8
+ [:a,:two,:bar],
9
+ [:a,:two,:baz],
10
+ [:b,:one,:bar],
11
+ [:b,:two,:bar],
12
+ [:b,:two,:baz],
13
+ [:b,:one,:foo],
14
+ [:c,:one,:bar],
15
+ [:c,:one,:baz],
16
+ [:c,:two,:foo],
17
+ [:c,:two,:bar]
18
+ ]
19
+ @multi_mi = Daru::MultiIndex.from_tuples(@index_tuples)
20
+ end
21
+
22
+ context ".initialize" do
23
+ it "accepts labels and levels as arguments" do
24
+ mi = Daru::MultiIndex.new(
25
+ levels: [[:a,:b,:c], [:one, :two]],
26
+ labels: [[0,0,1,1,2,2], [0,1,0,1,0,1]])
27
+
28
+ expect(mi[:a, :two]).to eq(1)
29
+ end
30
+
31
+ it "raises error for wrong number of labels or levels" do
32
+ expect {
33
+ Daru::MultiIndex.new(
34
+ levels: [[:a,:a,:b,:b,:c,:c], [:one, :two]],
35
+ labels: [[0,0,1,1,2,2]])
36
+ }.to raise_error
37
+ end
38
+ end
39
+
40
+ context ".from_tuples" do
41
+ it "creates 2 layer MultiIndex from tuples" do
42
+ tuples = [
43
+ [:a, :one],
44
+ [:a, :two],
45
+ [:b, :one],
46
+ [:b, :two],
47
+ [:c, :one],
48
+ [:c, :two]
49
+ ]
50
+ mi = Daru::MultiIndex.from_tuples(tuples)
51
+ expect(mi.levels).to eq([[:a, :b, :c], [:one,:two]])
52
+ expect(mi.labels).to eq([[0,0,1,1,2,2], [0,1,0,1,0,1]])
53
+ end
54
+
55
+ it "creates a triple layer MultiIndex from tuples" do
56
+ expect(@multi_mi.levels).to eq([[:a,:b,:c], [:one, :two],[:bar,:baz,:foo]])
57
+ expect(@multi_mi.labels).to eq([
58
+ [0,0,0,0,1,1,1,1,2,2,2,2],
59
+ [0,0,1,1,0,1,1,0,0,0,1,1],
60
+ [0,1,0,1,0,0,1,2,0,1,2,0]
61
+ ])
62
+ end
63
+ end
64
+
65
+ context '.try_from_tuples' do
66
+ it 'creates MultiIndex, if there are tuples' do
67
+ tuples = [
68
+ [:a, :one],
69
+ [:a, :two],
70
+ [:b, :one],
71
+ [:b, :two],
72
+ [:c, :one],
73
+ [:c, :two]
74
+ ]
75
+ mi = Daru::MultiIndex.try_from_tuples(tuples)
76
+ expect(mi).to be_a Daru::MultiIndex
77
+ end
78
+
79
+ it 'returns nil, if MultiIndex can not be created' do
80
+ mi = Daru::MultiIndex.try_from_tuples([:a, :b, :c])
81
+ expect(mi).to be_nil
82
+ end
83
+ end
84
+
85
+ context "#size" do
86
+ it "returns size of MultiIndex" do
87
+ expect(@multi_mi.size).to eq(12)
88
+ end
89
+ end
90
+
91
+ context "#[]" do
92
+ it "returns the row number when specifying the complete tuple" do
93
+ expect(@multi_mi[:a, :one, :baz]).to eq(1)
94
+ end
95
+
96
+ it "returns MultiIndex when specifying incomplete tuple" do
97
+ expect(@multi_mi[:b]).to eq(Daru::MultiIndex.from_tuples([
98
+ [:b,:one,:bar],
99
+ [:b,:two,:bar],
100
+ [:b,:two,:baz],
101
+ [:b,:one,:foo]
102
+ ]))
103
+ expect(@multi_mi[:b, :one]).to eq(Daru::MultiIndex.from_tuples([
104
+ [:b,:one,:bar],
105
+ [:b,:one,:foo]
106
+ ]))
107
+ # TODO: Return Daru::Index if a single layer of indexes is present.
108
+ end
109
+
110
+ it "returns MultiIndex when specifying wholly numeric ranges" do
111
+ expect(@multi_mi[3..6]).to eq(Daru::MultiIndex.from_tuples([
112
+ [:a,:two,:baz],
113
+ [:b,:one,:bar],
114
+ [:b,:two,:bar],
115
+ [:b,:two,:baz]
116
+ ]))
117
+ end
118
+
119
+ it "raises error when specifying invalid index" do
120
+ expect { @multi_mi[:a, :three] }.to raise_error IndexError
121
+ expect { @multi_mi[:a, :one, :xyz] }.to raise_error IndexError
122
+ expect { @multi_mi[:x] }.to raise_error IndexError
123
+ expect { @multi_mi[:x, :one] }.to raise_error IndexError
124
+ expect { @multi_mi[:x, :one, :bar] }.to raise_error IndexError
125
+ end
126
+
127
+ it "works with numerical first levels" do
128
+ mi = Daru::MultiIndex.from_tuples([
129
+ [2000, 'M'],
130
+ [2000, 'F'],
131
+ [2001, 'M'],
132
+ [2001, 'F']
133
+ ])
134
+
135
+ expect(mi[2000]).to eq(Daru::MultiIndex.from_tuples([
136
+ [2000, 'M'],
137
+ [2000, 'F']
138
+ ]))
139
+
140
+ expect(mi[2000,'M']).to eq(0)
141
+ end
142
+ end
143
+
144
+ context "#include?" do
145
+ it "checks if a completely specified tuple exists" do
146
+ expect(@multi_mi.include?([:a,:one,:bar])).to eq(true)
147
+ end
148
+
149
+ it "checks if a top layer incomplete tuple exists" do
150
+ expect(@multi_mi.include?([:a])).to eq(true)
151
+ end
152
+
153
+ it "checks if a middle layer incomplete tuple exists" do
154
+ expect(@multi_mi.include?([:a, :one])).to eq(true)
155
+ end
156
+
157
+ it "checks for non-existence of a tuple" do
158
+ expect(@multi_mi.include?([:boo])).to eq(false)
159
+ end
160
+ end
161
+
162
+ context "#key" do
163
+ it "returns the tuple of the specified number" do
164
+ expect(@multi_mi.key(3)).to eq([:a,:two,:baz])
165
+ end
166
+
167
+ it "returns nil for non-existent pointer number" do
168
+ expect {
169
+ @multi_mi.key(100)
170
+ }.to raise_error ArgumentError
171
+ end
172
+ end
173
+
174
+ context "#to_a" do
175
+ it "returns tuples as an Array" do
176
+ expect(@multi_mi.to_a).to eq(@index_tuples)
177
+ end
178
+ end
179
+
180
+ context "#dup" do
181
+ it "completely duplicates the object" do
182
+ duplicate = @multi_mi.dup
183
+ expect(duplicate) .to eq(@multi_mi)
184
+ expect(duplicate.object_id).to_not eq(@multi_mi.object_id)
185
+ end
186
+ end
187
+
188
+ context "#inspect" do
189
+ context 'small index' do
190
+ subject {
191
+ Daru::MultiIndex.from_tuples [
192
+ [:a,:one,:bar],
193
+ [:a,:one,:baz],
194
+ [:a,:two,:bar],
195
+ [:a,:two,:baz],
196
+ [:b,:one,:bar],
197
+ [:b,:two,:bar],
198
+ [:b,:two,:baz],
199
+ [:b,:one,:foo],
200
+ [:c,:one,:bar],
201
+ [:c,:one,:baz],
202
+ [:c,:two,:foo],
203
+ [:c,:two,:bar]
204
+ ]
205
+ }
206
+
207
+ its(:inspect) { is_expected.to eq %Q{
208
+ |#<Daru::MultiIndex(12x3)>
209
+ | a one bar
210
+ | baz
211
+ | two bar
212
+ | baz
213
+ | b one bar
214
+ | two bar
215
+ | baz
216
+ | one foo
217
+ | c one bar
218
+ | baz
219
+ | two foo
220
+ | bar
221
+ }.unindent
222
+ }
223
+ end
224
+
225
+ context 'large index' do
226
+ subject {
227
+ Daru::MultiIndex.from_tuples(
228
+ (1..100).map { |i| %w[a b c].map { |c| [i, c] } }.flatten(1)
229
+ )
230
+ }
231
+
232
+ its(:inspect) { is_expected.to eq %Q{
233
+ |#<Daru::MultiIndex(300x2)>
234
+ | 1 a
235
+ | b
236
+ | c
237
+ | 2 a
238
+ | b
239
+ | c
240
+ | 3 a
241
+ | b
242
+ | c
243
+ | 4 a
244
+ | b
245
+ | c
246
+ | 5 a
247
+ | b
248
+ | c
249
+ | 6 a
250
+ | b
251
+ | c
252
+ | 7 a
253
+ | b
254
+ | ... ...
255
+ }.unindent
256
+ }
257
+ end
258
+ end
259
+
260
+ context "#==" do
261
+ it "returns false for unequal MultiIndex comparisons" do
262
+ mi1 = Daru::MultiIndex.from_tuples([
263
+ [:a, :one, :bar],
264
+ [:a, :two, :baz],
265
+ [:b, :one, :foo],
266
+ [:b, :two, :bar]
267
+ ])
268
+ mi2 = Daru::MultiIndex.from_tuples([
269
+ [:a, :two, :bar],
270
+ [:b, :one, :foo],
271
+ [:a, :one, :baz],
272
+ [:b, :two, :baz]
273
+ ])
274
+
275
+ expect(mi1 == mi2).to eq(false)
276
+ end
277
+ end
278
+
279
+ context "#values" do
280
+ it "returns an array of indices in order" do
281
+ mi = Daru::MultiIndex.from_tuples([
282
+ [:a, :one, :bar],
283
+ [:a, :two, :baz],
284
+ [:b, :one, :foo],
285
+ [:b, :two, :bar]
286
+ ])
287
+
288
+ expect(mi.values).to eq([0,1,2,3])
289
+ end
290
+ end
291
+
292
+ context "#|" do
293
+ before do
294
+ @mi1 = Daru::MultiIndex.from_tuples([
295
+ [:a, :one, :bar],
296
+ [:a, :two, :baz],
297
+ [:b, :one, :foo],
298
+ [:b, :two, :bar]
299
+ ])
300
+ @mi2 = Daru::MultiIndex.from_tuples([
301
+ [:a, :two, :bar],
302
+ [:b, :one, :foo],
303
+ [:a, :one, :baz],
304
+ [:b, :two, :baz]
305
+ ])
306
+ end
307
+
308
+ it "returns a union of two MultiIndex objects" do
309
+ expect(@mi1 | @mi2).to eq(Daru::MultiIndex.new(
310
+ levels: [[:a, :b], [:one, :two], [:bar, :baz, :foo]],
311
+ labels: [
312
+ [0, 0, 1, 1, 0, 0, 1],
313
+ [0, 1, 0, 1, 1, 0, 1],
314
+ [0, 1, 2, 0, 0, 1, 1]
315
+ ])
316
+ )
317
+ end
318
+ end
319
+
320
+ context "#&" do
321
+ before do
322
+ @mi1 = Daru::MultiIndex.from_tuples([
323
+ [:a, :one],
324
+ [:a, :two],
325
+ [:b, :two]
326
+ ])
327
+ @mi2 = Daru::MultiIndex.from_tuples([
328
+ [:a, :two],
329
+ [:b, :one],
330
+ [:b, :three]
331
+ ])
332
+ end
333
+
334
+ it "returns the intersection of two MI objects" do
335
+ expect(@mi1 & @mi2).to eq(Daru::MultiIndex.from_tuples([
336
+ [:a, :two],
337
+ ]))
338
+ end
339
+ end
340
+
341
+ context "#empty?" do
342
+ it "returns true if nothing present in MultiIndex" do
343
+ expect(Daru::MultiIndex.new(labels: [[]], levels: [[]]).empty?).to eq(true)
344
+ end
345
+ end
346
+
347
+ context "#drop_left_level" do
348
+ it "drops the leftmost level" do
349
+ expect(
350
+ Daru::MultiIndex.from_tuples([
351
+ [:c,:one,:bar],
352
+ [:c,:one,:baz],
353
+ [:c,:two,:foo],
354
+ [:c,:two,:bar]
355
+ ]).drop_left_level).to eq(
356
+ Daru::MultiIndex.from_tuples([
357
+ [:one,:bar],
358
+ [:one,:baz],
359
+ [:two,:foo],
360
+ [:two,:bar]
361
+ ])
362
+ )
363
+ end
364
+ end
365
+
366
+ context 'other forms of tuple list representation' do
367
+ let(:index) {
368
+ Daru::MultiIndex.from_tuples [
369
+ [:a,:one,:bar],
370
+ [:a,:one,:baz],
371
+ [:a,:two,:bar],
372
+ [:a,:two,:baz],
373
+ [:b,:one,:bar],
374
+ [:b,:two,:bar],
375
+ [:b,:two,:baz],
376
+ [:b,:one,:foo],
377
+ [:c,:one,:bar],
378
+ [:c,:one,:baz],
379
+ [:c,:two,:foo],
380
+ [:c,:two,:bar]
381
+ ]
382
+ }
383
+
384
+ context '#sparse_tuples' do
385
+ subject { index.sparse_tuples }
386
+
387
+ it { is_expected.to eq [
388
+ [:a ,:one,:bar],
389
+ [nil, nil,:baz],
390
+ [nil,:two,:bar],
391
+ [nil, nil,:baz],
392
+ [:b ,:one,:bar],
393
+ [nil,:two,:bar],
394
+ [nil, nil,:baz],
395
+ [nil,:one,:foo],
396
+ [:c ,:one,:bar],
397
+ [nil, nil,:baz],
398
+ [nil,:two,:foo],
399
+ [nil, nil,:bar]
400
+ ]}
401
+ end
402
+ end
403
+
404
+ context "#pos" do
405
+ let(:idx) do
406
+ described_class.from_tuples([
407
+ [:b,:one,:bar],
408
+ [:b,:two,:bar],
409
+ [:b,:two,:baz],
410
+ [:b,:one,:foo]
411
+ ])
412
+ end
413
+
414
+ context "single index" do
415
+ it { expect(idx.pos :b, :one, :bar).to eq 0 }
416
+ end
417
+
418
+ context "multiple indexes" do
419
+ subject { idx.pos :b, :one }
420
+
421
+ it { is_expected.to be_a Array }
422
+ its(:size) { is_expected.to eq 2 }
423
+ it { is_expected.to eq [0, 3] }
424
+ end
425
+
426
+ context "single positional index" do
427
+ it { expect(idx.pos 0).to eq 0 }
428
+ end
429
+
430
+ context "multiple positional indexes" do
431
+ subject { idx.pos 0, 1 }
432
+
433
+ it { is_expected.to be_a Array }
434
+ its(:size) { is_expected.to eq 2 }
435
+ it { is_expected.to eq [0, 1] }
436
+ end
437
+
438
+ # TODO: Add specs for IndexError
439
+ end
440
+
441
+ context "#subset" do
442
+ let(:idx) do
443
+ described_class.from_tuples([
444
+ [:b, :one, :bar],
445
+ [:b, :two, :bar],
446
+ [:b, :two, :baz],
447
+ [:b, :one, :foo]
448
+ ])
449
+ end
450
+
451
+ context "multiple indexes" do
452
+ subject { idx.subset :b, :one }
453
+
454
+ it { is_expected.to be_a described_class }
455
+ its(:size) { is_expected.to eq 2 }
456
+ its(:to_a) { is_expected.to eq [[:bar], [:foo]] }
457
+ end
458
+
459
+ context "multiple positional indexes" do
460
+ subject { idx.subset 0, 1 }
461
+
462
+ it { is_expected.to be_a described_class }
463
+ its(:size) { is_expected.to eq 2 }
464
+ its(:to_a) { is_expected.to eq [[:b, :one, :bar], [:b, :two, :bar]] }
465
+ end
466
+
467
+ # TODO: Checks for invalid indexes
468
+ end
469
+
470
+ context "at" do
471
+ let(:idx) do
472
+ described_class.from_tuples([
473
+ [:b, :one, :bar],
474
+ [:b, :two, :bar],
475
+ [:b, :two, :baz],
476
+ [:b, :one, :foo]
477
+ ])
478
+ end
479
+
480
+ context "single position" do
481
+ it { expect(idx.at 2).to eq [:b, :two, :baz] }
482
+ end
483
+
484
+ context "multiple positions" do
485
+ subject { idx.at 1, 2 }
486
+
487
+ it { is_expected.to be_a described_class }
488
+ its(:size) { is_expected.to eq 2 }
489
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
490
+ [:b, :two, :baz]] }
491
+ end
492
+
493
+ context "range" do
494
+ subject { idx.at 1..2 }
495
+
496
+ it { is_expected.to be_a described_class }
497
+ its(:size) { is_expected.to eq 2 }
498
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
499
+ [:b, :two, :baz]] }
500
+ end
501
+
502
+ context "range with negative integers" do
503
+ subject { idx.at 1..-2 }
504
+
505
+ it { is_expected.to be_a described_class }
506
+ its(:size) { is_expected.to eq 2 }
507
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar],
508
+ [:b, :two, :baz]] }
509
+ end
510
+
511
+ context "rangle with single element" do
512
+ subject { idx.at 1..1 }
513
+
514
+ it { is_expected.to be_a described_class }
515
+ its(:size) { is_expected.to eq 1 }
516
+ its(:to_a) { is_expected.to eq [[:b, :two, :bar]] }
517
+ end
518
+
519
+ context "invalid position" do
520
+ it { expect { idx.at 4 }.to raise_error IndexError }
521
+ end
522
+
523
+ context "invalid positions" do
524
+ it { expect { idx.at 2, 4 }.to raise_error IndexError }
525
+ end
526
+ end
527
+
528
+ context "#add" do
529
+ let(:idx) do
530
+ described_class.from_tuples [
531
+ [:a, :one, :bar],
532
+ [:a, :two, :bar],
533
+ [:b, :two, :baz],
534
+ [:b, :one, :foo]
535
+ ]
536
+ end
537
+
538
+ context "single index" do
539
+ subject { idx.add :b, :two, :baz }
540
+
541
+ its(:to_a) { is_expected.to eq [
542
+ [:a, :one, :bar],
543
+ [:a, :two, :bar],
544
+ [:b, :two, :baz],
545
+ [:b, :one, :foo],
546
+ [:b, :two, :baz]] }
547
+ end
548
+ end
549
+
550
+ context "#valid?" do
551
+ let(:idx) do
552
+ described_class.from_tuples [
553
+ [:a, :one, :bar],
554
+ [:a, :two, :bar],
555
+ [:b, :two, :baz],
556
+ [:b, :one, :foo]
557
+ ]
558
+ end
559
+
560
+ context "single index" do
561
+ it { expect(idx.valid? :a, :one, :bar).to eq true }
562
+ it { expect(idx.valid? :b, :two, :three).to eq false }
563
+ end
564
+
565
+ context "multiple indexes" do
566
+ it { expect(idx.valid? :a, :one).to eq true }
567
+ it { expect(idx.valid? :a, :three).to eq false }
568
+ end
569
+ end
570
+ end