Completed
Branch master (0403bd)
by Doug
01:59
created

PackerTest::testPackFiveItemsTwoLargeOneSmallBox()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 39
Code Lines 29

Duplication

Lines 39
Ratio 100 %

Importance

Changes 3
Bugs 0 Features 2
Metric Value
c 3
b 0
f 2
dl 39
loc 39
rs 8.8571
cc 1
eloc 29
nc 1
nop 0
1
<?php
2
  /**
3
   * Box packing (3D bin packing, knapsack problem)
4
   * @package BoxPacker
5
   * @author Doug Wright
6
   */
7
8
  namespace DVDoug\BoxPacker;
9
10
  class PackerTest extends \PHPUnit_Framework_TestCase {
11
12
13
14
    public function testPackBoxThreeItemsFitEasily() {
15
16
      $box = new TestBox('Le box', 300, 300, 10, 10, 296, 296, 8, 1000);
17
18
      $items = new ItemList;
19
      $items->insert(new TestItem('Item 1', 250, 250, 2, 200));
20
      $items->insert(new TestItem('Item 2', 250, 250, 2, 200));
21
      $items->insert(new TestItem('Item 3', 250, 250, 2, 200));
22
23
      $packer = new Packer();
24
      $packedItems = $packer->packBox($box, $items);
25
26
      self::assertEquals(3, $packedItems->count());
27
    }
28
29
    public function testPackBoxThreeItemsFitExactly() {
30
31
      $box = new TestBox('Le box', 300, 300, 10, 10, 296, 296, 8, 1000);
32
33
      $items = new ItemList;
34
      $items->insert(new TestItem('Item 1', 296, 296, 2, 200));
35
      $items->insert(new TestItem('Item 2', 296, 296, 2, 500));
36
      $items->insert(new TestItem('Item 3', 296, 296, 4, 290));
37
38
      $packer = new Packer();
39
      $packedItems = $packer->packBox($box, $items);
40
41
      self::assertEquals(3, $packedItems->count());
42
    }
43
44
    public function testPackBoxThreeItemsFitExactlyNoRotation() {
45
46
      $box = new TestBox('Le box', 300, 300, 10, 10, 296, 296, 8, 1000);
47
48
      $items = new ItemList;
49
      $items->insert(new TestItem('Item 1', 296, 148, 2, 200));
50
      $items->insert(new TestItem('Item 2', 296, 148, 2, 500));
51
52
      $packer = new Packer();
53
      $packedItems = $packer->packBox($box, $items);
54
55
      self::assertEquals(2, $packedItems->count());
56
    }
57
58
    public function testPackBoxThreeItemsFitSizeButOverweight() {
59
60
      $box = new TestBox('Le box', 300, 300, 10, 10, 296, 296, 8, 1000);
61
62
      $items = new ItemList;
63
      $items->insert(new TestItem('Item 1', 250, 250, 2, 400));
64
      $items->insert(new TestItem('Item 2', 250, 250, 2, 500));
65
      $items->insert(new TestItem('Item 3', 250, 250, 2, 200));
66
67
      $packer = new Packer();
68
      $packedItems = $packer->packBox($box, $items);
69
70
      self::assertEquals(2, $packedItems->count());
71
    }
72
73
    public function testPackBoxThreeItemsFitWeightBut2Oversize() {
74
75
      $box = new TestBox('Le box', 300, 300, 10, 10, 296, 296, 8, 1000);
76
77
      $items = new ItemList;
78
      $items->insert(new TestItem('Item 1', 297, 296, 2, 200));
79
      $items->insert(new TestItem('Item 2', 297, 296, 2, 500));
80
      $items->insert(new TestItem('Item 3', 296, 296, 4, 290));
81
82
      $packer = new Packer();
83
      $packedItems = $packer->packBox($box, $items);
84
85
      self::assertEquals(1, $packedItems->count());
86
    }
87
88
    public function testPackThreeItemsFitEasilyInSmallerOfTwoBoxes() {
89
90
    $box1 = new TestBox('Le petite box', 300, 300, 10, 10, 296, 296, 8, 1000);
91
    $box2 = new TestBox('Le grande box', 3000, 3000, 100, 100, 2960, 2960, 80, 10000);
92
93
    $item1 = new TestItem('Item 1', 250, 250, 2, 200);
94
    $item2 = new TestItem('Item 2', 250, 250, 2, 200);
95
    $item3 = new TestItem('Item 3', 250, 250, 2, 200);
96
97
    $packer = new Packer();
98
    $packer->addBox($box1);
99
    $packer->addBox($box2);
100
    $packer->addItem($item1);
101
    $packer->addItem($item2);
102
    $packer->addItem($item3);
103
    $packedBoxes = $packer->pack();
104
105
    self::assertEquals(1, $packedBoxes->count());
106
    self::assertEquals(3, $packedBoxes->top()->getItems()->count());
107
    self::assertEquals($box1, $packedBoxes->top()->getBox());
108
    self::assertEquals(610, $packedBoxes->top()->getWeight());
109
  }
110
111
    public function testPackThreeItemsFitEasilyInLargerOfTwoBoxes() {
112
113
      $box1 = new TestBox('Le petite box', 300, 300, 10, 10, 296, 296, 8, 1000);
114
      $box2 = new TestBox('Le grande box', 3000, 3000, 100, 100, 2960, 2960, 80, 10000);
115
116
      $item1 = new TestItem('Item 1', 2500, 2500, 20, 2000);
117
      $item2 = new TestItem('Item 2', 2500, 2500, 20, 2000);
118
      $item3 = new TestItem('Item 3', 2500, 2500, 20, 2000);
119
120
      $packer = new Packer();
121
      $packer->addBox($box1);
122
      $packer->addBox($box2);
123
      $packer->addItem($item1);
124
      $packer->addItem($item2);
125
      $packer->addItem($item3);
126
      $packedBoxes = $packer->pack();
127
128
      self::assertEquals(1, $packedBoxes->count());
129
      self::assertEquals(3, $packedBoxes->top()->getItems()->count());
130
      self::assertEquals($box2, $packedBoxes->top()->getBox());
131
      self::assertEquals(6100, $packedBoxes->top()->getWeight());
132
    }
133
134
    public function testPackFiveItemsTwoLargeOneSmallBox() {
135
136
      $box1 = new TestBox('Le petite box', 600, 600, 10, 10, 596, 596, 8, 1000);
137
      $box2 = new TestBox('Le grande box', 3000, 3000, 50, 100, 2960, 2960, 40, 10000);
138
139
      $item1 = new TestItem('Item 1', 2500, 2500, 20, 500);
140
      $item2 = new TestItem('Item 2', 550, 550, 2, 500);
141
      $item3 = new TestItem('Item 3', 2500, 2500, 20, 500);
142
      $item4 = new TestItem('Item 4', 2500, 2500, 20, 500);
143
      $item5 = new TestItem('Item 5', 2500, 2500, 20, 500);
144
145
      $packer = new Packer();
146
      $packer->addBox($box1);
147
      $packer->addBox($box2);
148
      $packer->addItem($item1);
149
      $packer->addItem($item2);
150
      $packer->addItem($item3);
151
      $packer->addItem($item4);
152
      $packer->addItem($item5);
153
      $packedBoxes = $packer->pack();
154
155
      self::assertEquals(3, $packedBoxes->count());
156
157
      self::assertEquals(2, $packedBoxes->top()->getItems()->count());
158
      self::assertEquals($box2, $packedBoxes->top()->getBox());
159
      self::assertEquals(1100, $packedBoxes->top()->getWeight());
160
161
      $packedBoxes->extract();
162
163
      self::assertEquals(2, $packedBoxes->top()->getItems()->count());
164
      self::assertEquals($box2, $packedBoxes->top()->getBox());
165
      self::assertEquals(1100, $packedBoxes->top()->getWeight());
166
167
      $packedBoxes->extract();
168
169
      self::assertEquals(1, $packedBoxes->top()->getItems()->count());
170
      self::assertEquals($box1, $packedBoxes->top()->getBox());
171
      self::assertEquals(510, $packedBoxes->top()->getWeight());
172
    }
173
174
    public function testPackFiveItemsTwoLargeOneSmallBoxButThreeAfterRepack() {
175
176
      $box1 = new TestBox('Le petite box', 600, 600, 10, 10, 596, 596, 8, 1000);
177
      $box2 = new TestBox('Le grande box', 3000, 3000, 50, 100, 2960, 2960, 40, 10000);
178
179
      $item1 = new TestItem('Item 1', 2500, 2500, 20, 2000);
180
      $item2 = new TestItem('Item 2', 550, 550, 2, 200);
181
      $item3 = new TestItem('Item 3', 2500, 2500, 20, 2000);
182
      $item4 = new TestItem('Item 4', 2500, 2500, 20, 2000);
183
      $item5 = new TestItem('Item 5', 2500, 2500, 20, 2000);
184
185
      $packer = new Packer();
186
      $packer->addBox($box1);
187
      $packer->addBox($box2);
188
      $packer->addItem($item1);
189
      $packer->addItem($item2);
190
      $packer->addItem($item3);
191
      $packer->addItem($item4);
192
      $packer->addItem($item5);
193
      $packedBoxes = $packer->pack();
194
195
      self::assertEquals(3, $packedBoxes->count());
196
197
      self::assertEquals(2, $packedBoxes->top()->getItems()->count());
198
      self::assertEquals($box2, $packedBoxes->top()->getBox());
199
      self::assertEquals(4100, $packedBoxes->top()->getWeight());
200
201
      $packedBoxes->extract();
202
203
      self::assertEquals(2, $packedBoxes->top()->getItems()->count());
204
      self::assertEquals($box2, $packedBoxes->top()->getBox());
205
      self::assertEquals(2300, $packedBoxes->top()->getWeight());
206
207
      $packedBoxes->extract();
208
209
      self::assertEquals(1, $packedBoxes->top()->getItems()->count());
210
      self::assertEquals($box2, $packedBoxes->top()->getBox());
211
      self::assertEquals(2100, $packedBoxes->top()->getWeight());
212
    }
213
214
    /**
215
     * @expectedException \RuntimeException
216
     */
217
    public function testPackThreeItemsOneDoesntFitInAnyBox() {
218
219
      $box1 = new TestBox('Le petite box', 300, 300, 10, 10, 296, 296, 8, 1000);
220
      $box2 = new TestBox('Le grande box', 3000, 3000, 100, 100, 2960, 2960, 80, 10000);
221
222
      $item1 = new TestItem('Item 1', 2500, 2500, 20, 2000);
223
      $item2 = new TestItem('Item 2', 25000, 2500, 20, 2000);
224
      $item3 = new TestItem('Item 3', 2500, 2500, 20, 2000);
225
226
      $packer = new Packer();
227
      $packer->addBox($box1);
228
      $packer->addBox($box2);
229
      $packer->addItem($item1);
230
      $packer->addItem($item2);
231
      $packer->addItem($item3);
232
      $packedBoxes = $packer->pack();
233
    }
234
235
    /**
236
     * @expectedException \RuntimeException
237
     */
238
    public function testPackWithoutBox() {
239
240
      $item1 = new TestItem('Item 1', 2500, 2500, 20, 2000);
241
      $item2 = new TestItem('Item 2', 25000, 2500, 20, 2000);
242
      $item3 = new TestItem('Item 3', 2500, 2500, 20, 2000);
243
244
      $packer = new Packer();
245
      $packer->addItem($item1);
246
      $packer->addItem($item2);
247
      $packer->addItem($item3);
248
      $packedBoxes = $packer->pack();
249
    }
250
251
    public function testPackTwoItemsFitExactlySideBySide() {
252
253
      $box = new TestBox('Le box', 300, 400, 10, 10, 296, 496, 8, 1000);
254
255
      $items = new ItemList;
256
      $items->insert(new TestItem('Item 1', 296, 248, 8, 200));
257
      $items->insert(new TestItem('Item 2', 248, 296, 8, 200));
258
259
      $packer = new Packer();
260
      $packedItems = $packer->packBox($box, $items);
261
262
      self::assertEquals(2, $packedItems->count());
263
    }
264
265
    public function testPackThreeItemsBottom2FitSideBySideOneExactlyOnTop() {
266
267
      $box = new TestBox('Le box', 300, 300, 10, 10, 296, 296, 8, 1000);
268
269
      $items = new ItemList;
270
      $items->insert(new TestItem('Item 1', 248, 148, 4, 200));
271
      $items->insert(new TestItem('Item 2', 148, 248, 4, 200));
272
      $items->insert(new TestItem('Item 3', 296, 296, 4, 200));
273
274
      $packer = new Packer();
275
      $packedItems = $packer->packBox($box, $items);
276
277
      self::assertEquals(3, $packedItems->count());
278
    }
279
280
    public function testPackThreeItemsBottom2FitSideBySideWithSpareSpaceOneOverhangSlightlyOnTop() {
281
282
      $box = new TestBox('Le box', 250, 250, 10, 10, 248, 248, 8, 1000);
283
284
      $items = new ItemList;
285
      $items->insert(new TestItem('Item 1', 200, 200, 4, 200));
286
      $items->insert(new TestItem('Item 2', 110, 110, 4, 200));
287
      $items->insert(new TestItem('Item 3', 110, 110, 4, 200));
288
289
      $packer = new Packer();
290
      $packedItems = $packer->packBox($box, $items);
291
292
      self::assertEquals(3, $packedItems->count());
293
    }
294
295
    public function testPackSingleItemFitsBetterRotated() {
296
297
      $box = new TestBox('Le box', 400, 300, 10, 10, 396, 296, 8, 1000);
298
299
      $items = new ItemList;
300
      $items->insert(new TestItem('Item 1', 250, 290, 2, 200));
301
302
      $packer = new Packer();
303
      $packedItems = $packer->packBox($box, $items);
304
305
      self::assertEquals(1, $packedItems->count());
306
    }
307
308
    public function testIssue1() {
309
310
      $packer = new Packer();
311
      $packer->addBox(new TestBox('Le petite box', 292, 336, 60, 10, 292, 336, 60, 9000));
312
      $packer->addBox(new TestBox('Le grande box', 421, 548, 335, 100, 421, 548, 335, 10000));
313
      $packer->addItem(new TestItem('Item 1', 226, 200, 40, 440));
314
      $packer->addItem(new TestItem('Item 2', 200, 200, 155, 1660));
315
      $packedBoxes = $packer->pack();
316
317
      self::assertEquals(1, $packedBoxes->count());
318
    }
319
320
    public function testIssue3() {
321
322
      $packer = new Packer();
323
      $packer->addBox(new TestBox('OW Box 1', 51, 33, 33, 0.6, 51, 33, 33, 0.6));
324
      $packer->addBox(new TestBox('OW Box 2', 50, 40, 40, 0.95, 50, 40, 40, 0.95));
325
      $packer->addItem(new TestItem('Product', 28, 19, 9, 0), 6);
326
      $packedBoxes = $packer->pack();
327
328
      self::assertEquals(1, $packedBoxes->count());
329
    }
330
331
    public function testIssue6() {
332
333
      $packer = new Packer();
334
      $packer->addBox(new TestBox('Package 22', 675, 360, 210, 2, 670, 355, 204, 1000));
335
      $packer->addBox(new TestBox('Package 2', 330, 130, 102, 2, 335, 135, 107, 1000));
336
      $packer->addItem(new TestItem('Item 3', 355.6, 335.28, 127, 1.5));
337
      $packer->addItem(new TestItem('Item 7', 330.2, 127, 101.6, 1));
338
      $packer->addItem(new TestItem('Item 7', 330.2, 127, 101.6, 1));
339
      $packedBoxes = $packer->pack();
340
341
      self::assertEquals(1, $packedBoxes->count());
342
343
    }
344
345
    public function testIssue9() {
346
      $packer = new Packer();
347
      $packer->addBox(new TestBox('24x24x24Box', 24, 24, 24, 24, 24, 24, 24, 100));
348
349
      $packer->addItem(new TestItem('6x6x6Item', 6, 6, 6, 1), 64);
350
      $packedBoxes = $packer->pack();
351
352
      self::assertEquals(1, $packedBoxes->count());
353
    }
354
355
    public function testIssue11() {
356
      $packer = new Packer();
357
      $packer->addBox(new TestBox('4x4x4Box', 4, 4, 4, 4, 4, 4, 4, 100));
358
359
      $packer->addItem(new TestItem('BigItem', 2, 2, 4, 1), 2);
360
      $packer->addItem(new TestItem('SmallItem', 1, 1, 1, 1), 32);
361
      $packedBoxes = $packer->pack();
362
363
      self::assertEquals(1, $packedBoxes->count());
364
    }
365
366
    public function testIssue13() {
367
      $packer = new Packer();
368
      $packer->addBox(new TestBox('Le petite box', 12, 12, 12, 10, 10, 10, 10, 1000));
369
370
      $packer->addItem(new TestItem('Item 1', 5, 3, 2, 2));
371
      $packer->addItem(new TestItem('Item 2', 5, 3, 2, 2));
372
      $packer->addItem(new TestItem('Item 3', 3, 3, 3, 3));
373
      $packedBoxes = $packer->pack();
374
375
      self::assertEquals(1, $packedBoxes->count());
376
    }
377
378
    public function testIssue14() {
379
      $packer = new Packer();
380
      $packer->addBox(new TestBox('29x1x23Box', 29, 1, 23, 0, 29, 1, 23, 100));
381
      $packer->addItem(new TestItem('13x1x10Item', 13, 1, 10, 1));
382
      $packer->addItem(new TestItem('9x1x6Item', 9, 1, 6, 1));
383
      $packer->addItem(new TestItem('9x1x6Item', 9, 1, 6, 1));
384
      $packer->addItem(new TestItem('9x1x6Item', 9, 1, 6, 1));
385
      $packedBoxes = $packer->pack();
386
387
      self::assertEquals(1, $packedBoxes->count());
388
    }
389
390
    public function testPackerPacksRotatedBoxesInNewRow() {
391
      $packer = new Packer();
392
      $packer->addItem(new TestItem('30x10x30item', 30, 10, 30, 0), 9);
393
394
      //Box can hold 7 items in a row and then is completely full, so 9 items won't fit
395
      $packer->addBox(new TestBox('30x70x30InternalBox', 30, 70, 30, 0, 30, 70, 30, 0, 1000));
396
      $packedBoxes = $packer->pack();
397
      self::assertEquals(2, $packedBoxes->count());
398
399
      //Box can hold 7 items in a row, plus two more rotated, making 9 items
400
      // with a 10x10x30 hole in the corner.
401
      //
402
      // Overhead view:
403
      //
404
      // +--+--++
405
      // ++++++++
406
      // ||||||||
407
      // ++++++++
408
      //
409
      $packer = new Packer();
410
      $packer->addItem(new TestItem('30x10x30item', 30, 10, 30, 0), 9);
411
      $packer->addBox(new TestBox('40x70x30InternalBox', 40, 70, 30, 0, 40, 70, 30, 0, 1000));
412
      $packedBoxes = $packer->pack();
413
      self::assertEquals(1, $packedBoxes->count());
414
415
      // Make sure that it doesn't try to fit in a 10th item
416
      $packer = new Packer();
417
      $packer->addItem(new TestItem('30x10x30item', 30, 10, 30, 0), 10);
418
      $packer->addBox(new TestBox('40x70x30InternalBox', 40, 70, 30, 0, 40, 70, 30, 0, 1000));
419
      $packedBoxes = $packer->pack();
420
      self::assertEquals(2, $packedBoxes->count());
421
    }
422
423
424
    //PHP7/HHVM behave(d) differently than PHP5.x
425
    public function testWeightRedistribution() {
426
427
      $box = new TestBox('Box', 370,375,60,140,364,374,40,3000);
428
      $item1 = new TestItem('Item #1', 230, 330, 6, 320);
429
      $item2 = new TestItem('Item #2', 210, 297, 5, 187);
430
      $item3 = new TestItem('Item #3', 210, 297, 11, 674);
431
      $item4 = new TestItem('Item #4', 210, 297, 3, 82);
432
      $item5 = new TestItem('Item #5', 206, 295, 4, 217);
433
434
      $box1Items = new ItemList();
435
      $box1Items->insert(clone $item1);
436
      $box1Items->insert(clone $item1);
437
      $box1Items->insert(clone $item1);
438
      $box1Items->insert(clone $item1);
439
      $box1Items->insert(clone $item1);
440
      $box1Items->insert(clone $item1);
441
      $box1Items->insert(clone $item5);
442
443
      $box2Items = new ItemList();
444
      $box2Items->insert(clone $item3);
445
      $box2Items->insert(clone $item1);
446
      $box2Items->insert(clone $item1);
447
      $box2Items->insert(clone $item1);
448
      $box2Items->insert(clone $item1);
449
      $box2Items->insert(clone $item2);
450
451
      $box3Items = new ItemList();
452
      $box3Items->insert(clone $item5);
453
      $box3Items->insert(clone $item4);
454
455
      $packedBox1 = new PackedBox($box, $box1Items, 0, 0, 0, 0);
456
      $packedBox2 = new PackedBox($box, $box2Items, 0, 0, 0, 0);
457
      $packedBox3 = new PackedBox($box, $box3Items, 0, 0, 0, 0);
458
459
      $packedBoxList = new PackedBoxList();
460
      $packedBoxList->insert($packedBox1);
461
      $packedBoxList->insert($packedBox2);
462
      $packedBoxList->insert($packedBox3);
463
464
      $packer = new Packer();
465
      $packer->addBox($box);
466
      $packedBoxes = $packer->redistributeWeight($packedBoxList);
467
468
      $packedItemCount = 0;
469
      foreach (clone $packedBoxes as $packedBox) {
470
        $packedItemCount += $packedBox->getItems()->count();
471
      }
472
473
      self::assertEquals(3070, (int) $packedBoxes->getWeightVariance());
474
    }
475
476
    /**
477
     * @dataProvider getSamples
478
     */
479
    public function testCanPackRepresentativeLargerSamples($test, $boxes, $items, $expectedBoxes, $expectedWeightVariance) {
480
481
      $expectedItemCount = 0;
482
      $packedItemCount = 0;
483
484
      $packer = new Packer();
485
      foreach($boxes as $box) {
486
        $packer->addBox($box);
487
      }
488
      foreach ($items as $item) {
489
        $packer->addItem(new TestItem($item['name'], $item['width'], $item['length'], $item['depth'], $item['weight']), $item['qty']);
490
        $expectedItemCount += $item['qty'];
491
      }
492
      $packedBoxes = $packer->pack();
493
494
      foreach (clone $packedBoxes as $packedBox) {
495
        $packedItemCount += $packedBox->getItems()->count();
496
      }
497
498
499
      self::assertEquals($expectedBoxes, $packedBoxes->count());
500
      self::assertEquals($expectedItemCount, $packedItemCount);
501
      self::assertEquals($expectedWeightVariance, (int) $packedBoxes->getWeightVariance());
502
503
    }
504
505
    public function getSamples() {
506
507
      $expected = [];
508
      $expectedData = fopen(__DIR__ . '/data/expected.csv', 'r');
509
      while ($data = fgetcsv($expectedData)) {
510
        $expected[$data[0]] = array('boxes' => $data[1], 'weightVariance' => $data[2]);
511
      }
512
      fclose($expectedData);
513
514
      $boxes = [];
515
      $boxData = fopen(__DIR__ . '/data/boxes.csv', 'r');
516
      while ($data = fgetcsv($boxData)) {
517
        $boxes[] = new TestBox($data[0], $data[1], $data[2], $data[3], $data[4], $data[5], $data[6], $data[7], $data[8]);
518
      }
519
      fclose($boxData);
520
521
      $tests = [];
522
      $itemData = fopen(__DIR__ . '/data/items.csv', 'r');
523
      while ($data = fgetcsv($itemData)) {
524
525
        if (isset($tests[$data[0]])) {
526
          $tests[$data[0]]['items'][] = array('qty' => $data[1],
527
                                              'name' => $data[2],
528
                                              'width' => $data[3],
529
                                              'length' => $data[4],
530
                                              'depth' => $data[5],
531
                                              'weight' => $data[6]);
532
        }
533
        else {
534
          $tests[$data[0]] = array('test'  => $data[0],
535
                                   'boxes' => $boxes,
536
                                   'items' => array(array('qty' => $data[1],
537
                                                          'name' => $data[2],
538
                                                          'width' => $data[3],
539
                                                          'length' => $data[4],
540
                                                          'depth' => $data[5],
541
                                                          'weight' => $data[6])),
542
                                   'expected' => $expected[$data[0]]['boxes'],
543
                                   'weightVariance' => $expected[$data[0]]['weightVariance']);
544
        }
545
      }
546
      fclose($itemData);
547
548
      return $tests;
549
    }
550
551
  }
552