Passed
Push — develop ( 237739...d404a2 )
by Christophe
01:48
created
src/BitArray/BitArray.php 1 patch
Indentation   +736 added lines, -736 removed lines patch added patch discarded remove patch
@@ -26,740 +26,740 @@
 block discarded – undo
26 26
  */
27 27
 class BitArray implements \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable
28 28
 {
29
-    /**
30
-     * @var integer[]  Number of bits for each value between 0 and 255
31
-     */
32
-    private static $count = array(
33
-        0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
34
-        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
35
-        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
36
-        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
37
-        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
38
-        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
39
-        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
40
-        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
41
-        1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
42
-        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
43
-        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
44
-        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
45
-        2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
46
-        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
47
-        3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
48
-        4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
49
-    );
50
-
51
-    /**
52
-     * @var integer[]  Mask for restricting complements
53
-     */
54
-    private static $restrict = array(255, 1, 3, 7, 15, 31, 63, 127);
55
-
56
-    /**
57
-     * @var string  Underlying data
58
-     *
59
-     * @since 1.0.0
60
-     */
61
-    private $data;
62
-
63
-    /**
64
-     * @var integer  Size of the bit array
65
-     *
66
-     * @since 1.0.0
67
-     */
68
-    private $size;
69
-
70
-    /**
71
-     * Create a new bit array of the given size
72
-     *
73
-     * @param integer $size    The BitArray size
74
-     * @param boolean $default The default value for bits
75
-     *
76
-     * @since 1.0.0
77
-     */
78
-    protected function __construct($size = 0, $default = false)
79
-    {
80
-        $this->size = (int) $size;
81
-
82
-        if ($default) {
83
-            $this->data = str_repeat(chr(255), (int) ceil($this->size / 8));
84
-            $this->restrict();
85
-        } else {
86
-            $this->data = str_repeat(chr(0), (int) ceil($this->size / 8));
87
-        }
88
-    }
89
-
90
-    /**
91
-     * Remove useless bits for simplifying count operation.
92
-     *
93
-     * @return BitArray  $this for chaining
94
-     *
95
-     * @since 1.2.0
96
-     */
97
-    protected function restrict()
98
-    {
99
-        $length = strlen($this->data);
100
-
101
-        if ($length > 0) {
102
-            $this->data[$length - 1] = chr(ord($this->data[$length - 1]) & self::$restrict[$this->size % 8]);
103
-        }
104
-
105
-        return $this;
106
-    }
107
-
108
-    /**
109
-     * Clone a BitArray
110
-     *
111
-     * @return void
112
-     *
113
-     * @since 1.0.0
114
-     */
115
-    public function __clone()
116
-    {
117
-        $this->data = str_repeat($this->data, 1);
118
-    }
119
-
120
-    /**
121
-     * Convert the object to a string
122
-     *
123
-     * @return string  String representation of this object
124
-     *
125
-     * @since 1.0.0
126
-     */
127
-    public function __toString()
128
-    {
129
-        $string = str_repeat('0', $this->size);
130
-
131
-        for ($offset = 0; $offset < $this->size; $offset++) {
132
-            if (ord($this->data[(int) ($offset / 8)]) & (1 << $offset % 8)) {
133
-                $string[$offset] = '1';
134
-            }
135
-        }
136
-
137
-        return $string;
138
-    }
139
-
140
-    /**
141
-     * Magic get method
142
-     *
143
-     * @param string $property The property
144
-     *
145
-     * @throws RuntimeException  If the property does not exist
146
-     *
147
-     * @return mixed  The value associated to the property
148
-     *
149
-     * @since 1.0.0
150
-     */
151
-    public function __get($property)
152
-    {
153
-        switch ($property) {
154
-            case 'size':
155
-                return $this->size;
156
-            case 'count':
157
-                return $this->count();
158
-            default:
159
-                throw new \RuntimeException('Undefined property');
160
-        }
161
-    }
162
-
163
-    /**
164
-     * Test the existence of an index
165
-     *
166
-     * @param integer $offset The offset
167
-     *
168
-     * @return boolean  The truth value
169
-     *
170
-     * @since 1.0.0
171
-     */
172
-    public function offsetExists($offset)
173
-    {
174
-        return is_int($offset) && $offset >= 0 && $offset < $this->size;
175
-    }
176
-
177
-    /**
178
-     * Get the truth value for an index
179
-     *
180
-     * @param integer $offset The offset
181
-     *
182
-     * @return boolean  The truth value
183
-     *
184
-     * @throws OutOfRangeException  Argument index must be an positive integer lesser than the size
185
-     *
186
-     * @since 1.0.0
187
-     */
188
-    public function offsetGet($offset)
189
-    {
190
-        if ($this->offsetExists($offset)) {
191
-            return (bool) (ord($this->data[(int) ($offset / 8)]) & (1 << $offset % 8));
192
-        } else {
193
-            throw new \OutOfRangeException('Argument offset must be a positive integer lesser than the size');
194
-        }
195
-    }
196
-
197
-    /**
198
-     * Set the truth value for an index
199
-     *
200
-     * @param integer $offset The offset
201
-     * @param boolean $value  The truth value
202
-     *
203
-     * @return void
204
-     *
205
-     * @throws OutOfRangeException  Argument index must be an positive integer lesser than the size
206
-     *
207
-     * @since 1.0.0
208
-     */
209
-    public function offsetSet($offset, $value)
210
-    {
211
-        if ($this->offsetExists($offset)) {
212
-            $index = (int) ($offset / 8);
213
-
214
-            if ($value) {
215
-                $this->data[$index] = chr(ord($this->data[$index]) | (1 << $offset % 8));
216
-            } else {
217
-                $this->data[$index] = chr(ord($this->data[$index]) & ~(1 << $offset % 8));
218
-            }
219
-        } else {
220
-            throw new \OutOfRangeException('Argument index must be a positive integer lesser than the size');
221
-        }
222
-    }
223
-
224
-    /**
225
-     * Unset the existence of an index
226
-     *
227
-     * @param integer $offset The index
228
-     *
229
-     * @return void
230
-     *
231
-     * @throws RuntimeException  Values cannot be unset
232
-     *
233
-     * @since 1.0.0
234
-     */
235
-    public function offsetUnset($offset)
236
-    {
237
-        throw new \RuntimeException('Values cannot be unset');
238
-    }
239
-
240
-    /**
241
-     * Return the number of true bits
242
-     *
243
-     * @return integer  The number of true bits
244
-     *
245
-     * @since 1.0.0
246
-     */
247
-    public function count()
248
-    {
249
-        $count = 0;
250
-
251
-        for ($index = 0, $length = strlen($this->data); $index < $length; $index++) {
252
-            $count += self::$count[ord($this->data[$index])];
253
-        }
254
-
255
-        return $count;
256
-    }
257
-
258
-    /**
259
-     * Transform the object to an array
260
-     *
261
-     * @return array  Array of values
262
-     *
263
-     * @since 1.1.0
264
-     */
265
-    public function toArray()
266
-    {
267
-        $array = array();
268
-
269
-        for ($index = 0; $index < $this->size; $index++) {
270
-            $array[] = (bool) (ord($this->data[(int) ($index / 8)]) & (1 << $index % 8));
271
-        }
272
-
273
-        return $array;
274
-    }
275
-
276
-    /**
277
-     * Serialize the object
278
-     *
279
-     * @return array  Array of values
280
-     *
281
-     * @since 1.0.0
282
-     */
283
-    public function jsonSerialize()
284
-    {
285
-        return $this->toArray();
286
-    }
287
-
288
-    /**
289
-     * Get an iterator
290
-     *
291
-     * @return Iterator  Iterator
292
-     *
293
-     * @since 1.0.0
294
-     */
295
-    public function getIterator()
296
-    {
297
-        return new Iterator($this);
298
-    }
299
-
300
-    /**
301
-     * Return the size
302
-     *
303
-     * @return integer  The size
304
-     *
305
-     * @since 1.0.0
306
-     */
307
-    public function size()
308
-    {
309
-        return $this->size;
310
-    }
311
-
312
-    /**
313
-     * Copy bits directly from a BitArray
314
-     *
315
-     * @param BitArray $bits   A BitArray to copy
316
-     * @param int      $index  Starting index for destination
317
-     * @param int      $offset Starting index for copying
318
-     * @param int      $size   Copy size
319
-     *
320
-     * @return BitArray  This object for chaining
321
-     *
322
-     * @throws OutOfRangeException  Argument index must be an positive integer lesser than the size
323
-     *
324
-     * @since 1.1.0
325
-     */
326
-    public function directCopy(BitArray $bits, $index = 0, $offset = 0, $size = 0)
327
-    {
328
-        if ($offset > $index) {
329
-            for ($i = 0; $i < $size; $i++) {
330
-                $this[$i + $index] = $bits[$i + $offset];
331
-            }
332
-        } else {
333
-            for ($i = $size - 1; $i >= 0; $i--) {
334
-                $this[$i + $index] = $bits[$i + $offset];
335
-            }
336
-        }
337
-
338
-        return $this;
339
-    }
340
-
341
-    /**
342
-     * Copy bits from a BitArray
343
-     *
344
-     * * if index is non-negative, the index parameter is used as it is,
345
-     *   keeping its real value between 0 and size-1;
346
-     * * if index is negative, the index parameter starts from the end,
347
-     *   keeping its real value between 0 and size-1.
348
-     *
349
-     * * if offset is non-negative, the offset parameter is used as it is,
350
-     *   keeping its positive value between 0 and size-1;
351
-     * * if offset is negative, the offset parameter starts from the end,
352
-     *   keeping its real value between 0 and size-1.
353
-     *
354
-     * * if size is given and is positive, then the copy will copy size elements.
355
-     * * if the bits argument is shorter than the size, then only the available elements will be copied.
356
-     * * if size is given and is negative, then the copy starts from the end.
357
-     * * if size is omitted, then the copy will have everything from offset up
358
-     *   until the end of the bits argument.
359
-     *
360
-     * @param BitArray $bits   A BitArray to copy
361
-     * @param int      $index  Starting index for destination.
362
-     * @param int      $offset Starting index for copying.
363
-     * @param mixed    $size   Copy size.
364
-     *
365
-     * @return BitArray  This object for chaining
366
-     *
367
-     * @since 1.1.0
368
-     */
369
-    public function copy(BitArray $bits, $index = 0, $offset = 0, $size = null)
370
-    {
371
-        $index = $this->getRealOffset($index);
372
-        $offset = $bits->getRealOffset($offset);
373
-        $size = $bits->getRealSize($offset, $size);
374
-
375
-        if ($size > $this->size - $index) {
376
-            $size = $this->size - $index;
377
-        }
378
-
379
-        return $this->directCopy($bits, $index, $offset, $size);
380
-    }
381
-
382
-    /**
383
-     * Get the real offset using a positive or negative offset
384
-     *
385
-     * * If offset is non-negative, the offset parameter is used as it is,
386
-     *   keeping its real value between 0 and size-1.
387
-     * * if offset is negative, the offset parameter starts from the end,
388
-     *   keeping its real value between 0 and size-1.
389
-     *
390
-     * @param int $offset The offset
391
-     *
392
-     * @return integer  The real offset
393
-     *
394
-     * @since 1.1.0
395
-     */
396
-    protected function getRealOffset($offset)
397
-    {
398
-        $offset = (int) $offset;
399
-
400
-        if ($offset < 0) {
401
-            // Start from the end
402
-            $offset = $this->size + $offset;
403
-
404
-            if ($offset < 0) {
405
-                $offset = 0;
406
-            }
407
-        } elseif ($offset > $this->size) {
408
-            $offset = $this->size;
409
-        }
410
-
411
-        return $offset;
412
-    }
413
-
414
-    /**
415
-     * Get the real offset using a positive or negative offset
416
-     *
417
-     * * if size is given and is positive, then the real size will be between 0 and the current size-1.
418
-     * * if size is given and is negative, then the real size starts from the end.
419
-     * * if size is omitted, then the size goes until the end of the BitArray.
420
-     *
421
-     * @param int   $offset The real offset.
422
-     * @param mixed $size   The size
423
-     *
424
-     * @return integer  The real size
425
-     *
426
-     * @since 1.1.0
427
-     */
428
-    protected function getRealSize($offset, $size)
429
-    {
430
-        if ($size === null) {
431
-            $size = $this->size - $offset;
432
-        } else {
433
-            $size = (int) $size;
434
-
435
-            if ($size < 0) {
436
-                $size = $this->size + $size - $offset;
437
-
438
-                if ($size < 0) {
439
-                    $size = 0;
440
-                }
441
-            } elseif ($size > $this->size - $offset) {
442
-                $size = $this->size - $offset;
443
-            }
444
-        }
445
-
446
-        return $size;
447
-    }
448
-
449
-    /**
450
-     * Create a new BitArray from an integer
451
-     *
452
-     * @param integer $size    Size of the BitArray
453
-     * @param boolean $default The default value for bits
454
-     *
455
-     * @return BitArray  A new BitArray
456
-     *
457
-     * @since 1.0.0
458
-     */
459
-    public static function fromInteger($size, $default = false)
460
-    {
461
-        return new BitArray($size, (bool) $default);
462
-    }
463
-
464
-    /**
465
-     * Create a new BitArray from a sequence of bits.
466
-     *
467
-     * @param integer $size   Size of the BitArray
468
-     * @param integer $values The values for the bits
469
-     *
470
-     * @return BitArray  A new BitArray
471
-     *
472
-     * @since 1.2.0
473
-     */
474
-    public static function fromDecimal($size, $values = 0)
475
-    {
476
-        $php_bit_size = PHP_INT_SIZE * 8;
477
-        $size = min((int) $size, $php_bit_size);
478
-        $values <<= ($php_bit_size - $size);
479
-        $bits = new BitArray($size);
480
-
481
-        for ($i = 0; $i < ceil($size / 8); $i++) {
482
-            $value = ($values & (0xff << ($php_bit_size - 8))) >> ($php_bit_size - 8);
483
-            $reverse = 0;
484
-            for ($j = 0; $j < 8; $j++) {
485
-                if ($value & (1 << $j)) {
486
-                    $reverse |= 1 << (7 - $j);
487
-                }
488
-            }
489
-            $bits->data[$i] = chr($reverse);
490
-            $values <<= 8;
491
-        }
492
-
493
-        return $bits;
494
-    }
495
-
496
-    /**
497
-     * Create a new BitArray from a traversable
498
-     *
499
-     * @param mixed $traversable A traversable and countable
500
-     *
501
-     * @return BitArray  A new BitArray
502
-     *
503
-     * @since 1.0.0
504
-     */
505
-    public static function fromTraversable($traversable)
506
-    {
507
-        $bits = new BitArray(count($traversable));
508
-        $offset = 0;
509
-        $ord = 0;
510
-
511
-        foreach ($traversable as $value) {
512
-            if ($value) {
513
-                $ord |= 1 << $offset % 8;
514
-            }
515
-
516
-            if ($offset % 8 === 7) {
517
-                $bits->data[(int) ($offset / 8)] = chr($ord);
518
-                $ord = 0;
519
-            }
520
-
521
-            $offset++;
522
-        }
523
-
524
-        if ($offset % 8 !== 0) {
525
-            $bits->data[(int) ($offset / 8)] = chr($ord);
526
-        }
527
-
528
-        return $bits;
529
-    }
530
-
531
-    /**
532
-     * Create a new BitArray from a bit string
533
-     *
534
-     * @param string $string A bit string
535
-     *
536
-     * @return BitArray  A new BitArray
537
-     *
538
-     * @since 1.0.0
539
-     */
540
-    public static function fromString($string)
541
-    {
542
-        $bits = new BitArray(strlen($string));
543
-        $ord = 0;
544
-
545
-        for ($offset = 0; $offset < $bits->size; $offset++) {
546
-            if ($string[$offset] !== '0') {
547
-                $ord |= 1 << $offset % 8;
548
-            }
549
-
550
-            if ($offset % 8 === 7) {
551
-                $bits->data[(int) ($offset / 8)] = chr($ord);
552
-                $ord = 0;
553
-            }
554
-        }
555
-
556
-        if ($offset % 8 !== 0) {
557
-            $bits->data[(int) ($offset / 8)] = chr($ord);
558
-        }
559
-
560
-        return $bits;
561
-    }
562
-
563
-    /**
564
-     * Create a new BitArray from json
565
-     *
566
-     * @param string $json A json encoded value
567
-     *
568
-     * @return BitArray  A new BitArray
569
-     *
570
-     * @since 1.0.0
571
-     */
572
-    public static function fromJson($json)
573
-    {
574
-        return self::fromTraversable(json_decode($json));
575
-    }
576
-
577
-    /**
578
-     * Create a new BitArray using a slice
579
-     *
580
-     * * if offset is non-negative, the slice will start at that offset in the bits argument.
581
-     * * if offset is negative, the slice will start from the end of the bits argument.
582
-     *
583
-     * * if size is given and is positive, then the slice will have up to that many elements in it.
584
-     * * if the bits argument is shorter than the size, then only the available elements will be present.
585
-     * * if size is given and is negative,
586
-     *   then the slice will stop that many elements from the end of the bits argument.
587
-     * * if size is omitted,
588
-     *   then the slice will have everything from offset up until the end of the bits argument.
589
-     *
590
-     * @param BitArray $bits   A BitArray to get the slice
591
-     * @param int      $offset The offset
592
-     * @param mixed    $size   The size
593
-     *
594
-     * @return BitArray  A new BitArray
595
-     *
596
-     * @since 1.1.0
597
-     */
598
-    public static function fromSlice(BitArray $bits, $offset = 0, $size = null)
599
-    {
600
-        $offset = $bits->getRealOffset($offset);
601
-        $size = $bits->getRealSize($offset, $size);
602
-        $slice = new BitArray($size);
603
-
604
-        return $slice->directCopy($bits, 0, $offset, $size);
605
-    }
606
-
607
-    /**
608
-     * Create a new BitArray using the concat operation
609
-     *
610
-     * @param BitArray $bits1 A BitArray
611
-     * @param BitArray $bits2 A BitArray
612
-     *
613
-     * @return BitArray  A new BitArray
614
-     *
615
-     * @since 1.1.0
616
-     */
617
-    public static function fromConcat(BitArray $bits1, BitArray $bits2)
618
-    {
619
-        $size = $bits1->size + $bits2->size;
620
-        $concat = new BitArray($size);
621
-        $concat->directCopy($bits1, 0, 0, $bits1->size);
622
-        $concat->directCopy($bits2, $bits1->size, 0, $bits2->size);
623
-
624
-        return $concat;
625
-    }
626
-
627
-    /**
628
-     * Complement the bit array
629
-     *
630
-     * @return BitArray  This object for chaining
631
-     *
632
-     * @since 1.0.0
633
-     */
634
-    public function applyComplement()
635
-    {
636
-        $length = strlen($this->data);
637
-
638
-        for ($index = 0; $index < $length; $index++) {
639
-            $this->data[$index] = chr(~ ord($this->data[$index]));
640
-        }
641
-
642
-        return $this->restrict();
643
-    }
644
-
645
-    /**
646
-     * Or with an another bit array
647
-     *
648
-     * @param BitArray $bits A bit array
649
-     *
650
-     * @return BitArray  This object for chaining
651
-     *
652
-     * @throws InvalidArgumentException  Argument must be of equal size
653
-     *
654
-     * @since 1.0.0
655
-     */
656
-    public function applyOr(BitArray $bits)
657
-    {
658
-        if ($this->size == $bits->size) {
659
-            $length = strlen($this->data);
660
-
661
-            for ($index = 0; $index < $length; $index++) {
662
-                $this->data[$index] = chr(ord($this->data[$index]) | ord($bits->data[$index]));
663
-            }
664
-
665
-            return $this;
666
-        } else {
667
-            throw new \InvalidArgumentException('Argument must be of equal size');
668
-        }
669
-    }
670
-
671
-    /**
672
-     * And with an another bit array
673
-     *
674
-     * @param BitArray $bits A bit array
675
-     *
676
-     * @return BitArray  This object for chaining
677
-     *
678
-     * @throws InvalidArgumentException  Argument must be of equal size
679
-     *
680
-     * @since 1.0.0
681
-     */
682
-    public function applyAnd(BitArray $bits)
683
-    {
684
-        if ($this->size == $bits->size) {
685
-            $length = strlen($this->data);
686
-
687
-            for ($index = 0; $index < $length; $index++) {
688
-                $this->data[$index] = chr(ord($this->data[$index]) & ord($bits->data[$index]));
689
-            }
690
-
691
-            return $this;
692
-        } else {
693
-            throw new \InvalidArgumentException('Argument must be of equal size');
694
-        }
695
-    }
696
-
697
-    /**
698
-     * Xor with an another bit array
699
-     *
700
-     * @param BitArray $bits A bit array
701
-     *
702
-     * @return BitArray  This object for chaining
703
-     *
704
-     * @throws InvalidArgumentException  Argument must be of equal size
705
-     *
706
-     * @since 1.0.0
707
-     */
708
-    public function applyXor(BitArray $bits)
709
-    {
710
-        if ($this->size == $bits->size) {
711
-            $length = strlen($this->data);
712
-
713
-            for ($index = 0; $index < $length; $index++) {
714
-                $this->data[$index] = chr(ord($this->data[$index]) ^ ord($bits->data[$index]));
715
-            }
716
-
717
-            return $this;
718
-        } else {
719
-            throw new \InvalidArgumentException('Argument must be of equal size');
720
-        }
721
-    }
722
-
723
-    /**
724
-     * Shift a bit array.
725
-     *
726
-     * Negative value means the shifting is done right to left
727
-     * while positive value means the shifting is done left to right.
728
-     *
729
-     * @param int     $size  Size to shift.
730
-     *
731
-     * @param boolean $value Value to shift
732
-     *
733
-     * @return BitArray  $this for chaining
734
-     *
735
-     * @since 1.2.0
736
-     */
737
-    public function shift($size = 1, $value = false)
738
-    {
739
-        $size = (int) $size;
740
-
741
-        if ($size > 0) {
742
-            $min = min($this->size, $size);
743
-
744
-            for ($i = $this->size - 1; $i >= $min; $i--) {
745
-                $this[$i] = $this[$i - $min];
746
-            }
747
-
748
-            for ($i = 0; $i < $min; $i++) {
749
-                $this[$i] = $value;
750
-            }
751
-        } else {
752
-            $min = min($this->size, -$size);
753
-
754
-            for ($i = 0; $i < $this->size - $min; $i++) {
755
-                $this[$i] = $this[$i + $min];
756
-            }
757
-
758
-            for ($i = $this->size - $min; $i < $this->size; $i++) {
759
-                $this[$i] = $value;
760
-            }
761
-        }
762
-
763
-        return $this;
764
-    }
29
+	/**
30
+	 * @var integer[]  Number of bits for each value between 0 and 255
31
+	 */
32
+	private static $count = array(
33
+		0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4,
34
+		1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
35
+		1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
36
+		2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
37
+		1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
38
+		2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
39
+		2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
40
+		3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
41
+		1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5,
42
+		2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
43
+		2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
44
+		3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
45
+		2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6,
46
+		3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
47
+		3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7,
48
+		4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8
49
+	);
50
+
51
+	/**
52
+	 * @var integer[]  Mask for restricting complements
53
+	 */
54
+	private static $restrict = array(255, 1, 3, 7, 15, 31, 63, 127);
55
+
56
+	/**
57
+	 * @var string  Underlying data
58
+	 *
59
+	 * @since 1.0.0
60
+	 */
61
+	private $data;
62
+
63
+	/**
64
+	 * @var integer  Size of the bit array
65
+	 *
66
+	 * @since 1.0.0
67
+	 */
68
+	private $size;
69
+
70
+	/**
71
+	 * Create a new bit array of the given size
72
+	 *
73
+	 * @param integer $size    The BitArray size
74
+	 * @param boolean $default The default value for bits
75
+	 *
76
+	 * @since 1.0.0
77
+	 */
78
+	protected function __construct($size = 0, $default = false)
79
+	{
80
+		$this->size = (int) $size;
81
+
82
+		if ($default) {
83
+			$this->data = str_repeat(chr(255), (int) ceil($this->size / 8));
84
+			$this->restrict();
85
+		} else {
86
+			$this->data = str_repeat(chr(0), (int) ceil($this->size / 8));
87
+		}
88
+	}
89
+
90
+	/**
91
+	 * Remove useless bits for simplifying count operation.
92
+	 *
93
+	 * @return BitArray  $this for chaining
94
+	 *
95
+	 * @since 1.2.0
96
+	 */
97
+	protected function restrict()
98
+	{
99
+		$length = strlen($this->data);
100
+
101
+		if ($length > 0) {
102
+			$this->data[$length - 1] = chr(ord($this->data[$length - 1]) & self::$restrict[$this->size % 8]);
103
+		}
104
+
105
+		return $this;
106
+	}
107
+
108
+	/**
109
+	 * Clone a BitArray
110
+	 *
111
+	 * @return void
112
+	 *
113
+	 * @since 1.0.0
114
+	 */
115
+	public function __clone()
116
+	{
117
+		$this->data = str_repeat($this->data, 1);
118
+	}
119
+
120
+	/**
121
+	 * Convert the object to a string
122
+	 *
123
+	 * @return string  String representation of this object
124
+	 *
125
+	 * @since 1.0.0
126
+	 */
127
+	public function __toString()
128
+	{
129
+		$string = str_repeat('0', $this->size);
130
+
131
+		for ($offset = 0; $offset < $this->size; $offset++) {
132
+			if (ord($this->data[(int) ($offset / 8)]) & (1 << $offset % 8)) {
133
+				$string[$offset] = '1';
134
+			}
135
+		}
136
+
137
+		return $string;
138
+	}
139
+
140
+	/**
141
+	 * Magic get method
142
+	 *
143
+	 * @param string $property The property
144
+	 *
145
+	 * @throws RuntimeException  If the property does not exist
146
+	 *
147
+	 * @return mixed  The value associated to the property
148
+	 *
149
+	 * @since 1.0.0
150
+	 */
151
+	public function __get($property)
152
+	{
153
+		switch ($property) {
154
+			case 'size':
155
+				return $this->size;
156
+			case 'count':
157
+				return $this->count();
158
+			default:
159
+				throw new \RuntimeException('Undefined property');
160
+		}
161
+	}
162
+
163
+	/**
164
+	 * Test the existence of an index
165
+	 *
166
+	 * @param integer $offset The offset
167
+	 *
168
+	 * @return boolean  The truth value
169
+	 *
170
+	 * @since 1.0.0
171
+	 */
172
+	public function offsetExists($offset)
173
+	{
174
+		return is_int($offset) && $offset >= 0 && $offset < $this->size;
175
+	}
176
+
177
+	/**
178
+	 * Get the truth value for an index
179
+	 *
180
+	 * @param integer $offset The offset
181
+	 *
182
+	 * @return boolean  The truth value
183
+	 *
184
+	 * @throws OutOfRangeException  Argument index must be an positive integer lesser than the size
185
+	 *
186
+	 * @since 1.0.0
187
+	 */
188
+	public function offsetGet($offset)
189
+	{
190
+		if ($this->offsetExists($offset)) {
191
+			return (bool) (ord($this->data[(int) ($offset / 8)]) & (1 << $offset % 8));
192
+		} else {
193
+			throw new \OutOfRangeException('Argument offset must be a positive integer lesser than the size');
194
+		}
195
+	}
196
+
197
+	/**
198
+	 * Set the truth value for an index
199
+	 *
200
+	 * @param integer $offset The offset
201
+	 * @param boolean $value  The truth value
202
+	 *
203
+	 * @return void
204
+	 *
205
+	 * @throws OutOfRangeException  Argument index must be an positive integer lesser than the size
206
+	 *
207
+	 * @since 1.0.0
208
+	 */
209
+	public function offsetSet($offset, $value)
210
+	{
211
+		if ($this->offsetExists($offset)) {
212
+			$index = (int) ($offset / 8);
213
+
214
+			if ($value) {
215
+				$this->data[$index] = chr(ord($this->data[$index]) | (1 << $offset % 8));
216
+			} else {
217
+				$this->data[$index] = chr(ord($this->data[$index]) & ~(1 << $offset % 8));
218
+			}
219
+		} else {
220
+			throw new \OutOfRangeException('Argument index must be a positive integer lesser than the size');
221
+		}
222
+	}
223
+
224
+	/**
225
+	 * Unset the existence of an index
226
+	 *
227
+	 * @param integer $offset The index
228
+	 *
229
+	 * @return void
230
+	 *
231
+	 * @throws RuntimeException  Values cannot be unset
232
+	 *
233
+	 * @since 1.0.0
234
+	 */
235
+	public function offsetUnset($offset)
236
+	{
237
+		throw new \RuntimeException('Values cannot be unset');
238
+	}
239
+
240
+	/**
241
+	 * Return the number of true bits
242
+	 *
243
+	 * @return integer  The number of true bits
244
+	 *
245
+	 * @since 1.0.0
246
+	 */
247
+	public function count()
248
+	{
249
+		$count = 0;
250
+
251
+		for ($index = 0, $length = strlen($this->data); $index < $length; $index++) {
252
+			$count += self::$count[ord($this->data[$index])];
253
+		}
254
+
255
+		return $count;
256
+	}
257
+
258
+	/**
259
+	 * Transform the object to an array
260
+	 *
261
+	 * @return array  Array of values
262
+	 *
263
+	 * @since 1.1.0
264
+	 */
265
+	public function toArray()
266
+	{
267
+		$array = array();
268
+
269
+		for ($index = 0; $index < $this->size; $index++) {
270
+			$array[] = (bool) (ord($this->data[(int) ($index / 8)]) & (1 << $index % 8));
271
+		}
272
+
273
+		return $array;
274
+	}
275
+
276
+	/**
277
+	 * Serialize the object
278
+	 *
279
+	 * @return array  Array of values
280
+	 *
281
+	 * @since 1.0.0
282
+	 */
283
+	public function jsonSerialize()
284
+	{
285
+		return $this->toArray();
286
+	}
287
+
288
+	/**
289
+	 * Get an iterator
290
+	 *
291
+	 * @return Iterator  Iterator
292
+	 *
293
+	 * @since 1.0.0
294
+	 */
295
+	public function getIterator()
296
+	{
297
+		return new Iterator($this);
298
+	}
299
+
300
+	/**
301
+	 * Return the size
302
+	 *
303
+	 * @return integer  The size
304
+	 *
305
+	 * @since 1.0.0
306
+	 */
307
+	public function size()
308
+	{
309
+		return $this->size;
310
+	}
311
+
312
+	/**
313
+	 * Copy bits directly from a BitArray
314
+	 *
315
+	 * @param BitArray $bits   A BitArray to copy
316
+	 * @param int      $index  Starting index for destination
317
+	 * @param int      $offset Starting index for copying
318
+	 * @param int      $size   Copy size
319
+	 *
320
+	 * @return BitArray  This object for chaining
321
+	 *
322
+	 * @throws OutOfRangeException  Argument index must be an positive integer lesser than the size
323
+	 *
324
+	 * @since 1.1.0
325
+	 */
326
+	public function directCopy(BitArray $bits, $index = 0, $offset = 0, $size = 0)
327
+	{
328
+		if ($offset > $index) {
329
+			for ($i = 0; $i < $size; $i++) {
330
+				$this[$i + $index] = $bits[$i + $offset];
331
+			}
332
+		} else {
333
+			for ($i = $size - 1; $i >= 0; $i--) {
334
+				$this[$i + $index] = $bits[$i + $offset];
335
+			}
336
+		}
337
+
338
+		return $this;
339
+	}
340
+
341
+	/**
342
+	 * Copy bits from a BitArray
343
+	 *
344
+	 * * if index is non-negative, the index parameter is used as it is,
345
+	 *   keeping its real value between 0 and size-1;
346
+	 * * if index is negative, the index parameter starts from the end,
347
+	 *   keeping its real value between 0 and size-1.
348
+	 *
349
+	 * * if offset is non-negative, the offset parameter is used as it is,
350
+	 *   keeping its positive value between 0 and size-1;
351
+	 * * if offset is negative, the offset parameter starts from the end,
352
+	 *   keeping its real value between 0 and size-1.
353
+	 *
354
+	 * * if size is given and is positive, then the copy will copy size elements.
355
+	 * * if the bits argument is shorter than the size, then only the available elements will be copied.
356
+	 * * if size is given and is negative, then the copy starts from the end.
357
+	 * * if size is omitted, then the copy will have everything from offset up
358
+	 *   until the end of the bits argument.
359
+	 *
360
+	 * @param BitArray $bits   A BitArray to copy
361
+	 * @param int      $index  Starting index for destination.
362
+	 * @param int      $offset Starting index for copying.
363
+	 * @param mixed    $size   Copy size.
364
+	 *
365
+	 * @return BitArray  This object for chaining
366
+	 *
367
+	 * @since 1.1.0
368
+	 */
369
+	public function copy(BitArray $bits, $index = 0, $offset = 0, $size = null)
370
+	{
371
+		$index = $this->getRealOffset($index);
372
+		$offset = $bits->getRealOffset($offset);
373
+		$size = $bits->getRealSize($offset, $size);
374
+
375
+		if ($size > $this->size - $index) {
376
+			$size = $this->size - $index;
377
+		}
378
+
379
+		return $this->directCopy($bits, $index, $offset, $size);
380
+	}
381
+
382
+	/**
383
+	 * Get the real offset using a positive or negative offset
384
+	 *
385
+	 * * If offset is non-negative, the offset parameter is used as it is,
386
+	 *   keeping its real value between 0 and size-1.
387
+	 * * if offset is negative, the offset parameter starts from the end,
388
+	 *   keeping its real value between 0 and size-1.
389
+	 *
390
+	 * @param int $offset The offset
391
+	 *
392
+	 * @return integer  The real offset
393
+	 *
394
+	 * @since 1.1.0
395
+	 */
396
+	protected function getRealOffset($offset)
397
+	{
398
+		$offset = (int) $offset;
399
+
400
+		if ($offset < 0) {
401
+			// Start from the end
402
+			$offset = $this->size + $offset;
403
+
404
+			if ($offset < 0) {
405
+				$offset = 0;
406
+			}
407
+		} elseif ($offset > $this->size) {
408
+			$offset = $this->size;
409
+		}
410
+
411
+		return $offset;
412
+	}
413
+
414
+	/**
415
+	 * Get the real offset using a positive or negative offset
416
+	 *
417
+	 * * if size is given and is positive, then the real size will be between 0 and the current size-1.
418
+	 * * if size is given and is negative, then the real size starts from the end.
419
+	 * * if size is omitted, then the size goes until the end of the BitArray.
420
+	 *
421
+	 * @param int   $offset The real offset.
422
+	 * @param mixed $size   The size
423
+	 *
424
+	 * @return integer  The real size
425
+	 *
426
+	 * @since 1.1.0
427
+	 */
428
+	protected function getRealSize($offset, $size)
429
+	{
430
+		if ($size === null) {
431
+			$size = $this->size - $offset;
432
+		} else {
433
+			$size = (int) $size;
434
+
435
+			if ($size < 0) {
436
+				$size = $this->size + $size - $offset;
437
+
438
+				if ($size < 0) {
439
+					$size = 0;
440
+				}
441
+			} elseif ($size > $this->size - $offset) {
442
+				$size = $this->size - $offset;
443
+			}
444
+		}
445
+
446
+		return $size;
447
+	}
448
+
449
+	/**
450
+	 * Create a new BitArray from an integer
451
+	 *
452
+	 * @param integer $size    Size of the BitArray
453
+	 * @param boolean $default The default value for bits
454
+	 *
455
+	 * @return BitArray  A new BitArray
456
+	 *
457
+	 * @since 1.0.0
458
+	 */
459
+	public static function fromInteger($size, $default = false)
460
+	{
461
+		return new BitArray($size, (bool) $default);
462
+	}
463
+
464
+	/**
465
+	 * Create a new BitArray from a sequence of bits.
466
+	 *
467
+	 * @param integer $size   Size of the BitArray
468
+	 * @param integer $values The values for the bits
469
+	 *
470
+	 * @return BitArray  A new BitArray
471
+	 *
472
+	 * @since 1.2.0
473
+	 */
474
+	public static function fromDecimal($size, $values = 0)
475
+	{
476
+		$php_bit_size = PHP_INT_SIZE * 8;
477
+		$size = min((int) $size, $php_bit_size);
478
+		$values <<= ($php_bit_size - $size);
479
+		$bits = new BitArray($size);
480
+
481
+		for ($i = 0; $i < ceil($size / 8); $i++) {
482
+			$value = ($values & (0xff << ($php_bit_size - 8))) >> ($php_bit_size - 8);
483
+			$reverse = 0;
484
+			for ($j = 0; $j < 8; $j++) {
485
+				if ($value & (1 << $j)) {
486
+					$reverse |= 1 << (7 - $j);
487
+				}
488
+			}
489
+			$bits->data[$i] = chr($reverse);
490
+			$values <<= 8;
491
+		}
492
+
493
+		return $bits;
494
+	}
495
+
496
+	/**
497
+	 * Create a new BitArray from a traversable
498
+	 *
499
+	 * @param mixed $traversable A traversable and countable
500
+	 *
501
+	 * @return BitArray  A new BitArray
502
+	 *
503
+	 * @since 1.0.0
504
+	 */
505
+	public static function fromTraversable($traversable)
506
+	{
507
+		$bits = new BitArray(count($traversable));
508
+		$offset = 0;
509
+		$ord = 0;
510
+
511
+		foreach ($traversable as $value) {
512
+			if ($value) {
513
+				$ord |= 1 << $offset % 8;
514
+			}
515
+
516
+			if ($offset % 8 === 7) {
517
+				$bits->data[(int) ($offset / 8)] = chr($ord);
518
+				$ord = 0;
519
+			}
520
+
521
+			$offset++;
522
+		}
523
+
524
+		if ($offset % 8 !== 0) {
525
+			$bits->data[(int) ($offset / 8)] = chr($ord);
526
+		}
527
+
528
+		return $bits;
529
+	}
530
+
531
+	/**
532
+	 * Create a new BitArray from a bit string
533
+	 *
534
+	 * @param string $string A bit string
535
+	 *
536
+	 * @return BitArray  A new BitArray
537
+	 *
538
+	 * @since 1.0.0
539
+	 */
540
+	public static function fromString($string)
541
+	{
542
+		$bits = new BitArray(strlen($string));
543
+		$ord = 0;
544
+
545
+		for ($offset = 0; $offset < $bits->size; $offset++) {
546
+			if ($string[$offset] !== '0') {
547
+				$ord |= 1 << $offset % 8;
548
+			}
549
+
550
+			if ($offset % 8 === 7) {
551
+				$bits->data[(int) ($offset / 8)] = chr($ord);
552
+				$ord = 0;
553
+			}
554
+		}
555
+
556
+		if ($offset % 8 !== 0) {
557
+			$bits->data[(int) ($offset / 8)] = chr($ord);
558
+		}
559
+
560
+		return $bits;
561
+	}
562
+
563
+	/**
564
+	 * Create a new BitArray from json
565
+	 *
566
+	 * @param string $json A json encoded value
567
+	 *
568
+	 * @return BitArray  A new BitArray
569
+	 *
570
+	 * @since 1.0.0
571
+	 */
572
+	public static function fromJson($json)
573
+	{
574
+		return self::fromTraversable(json_decode($json));
575
+	}
576
+
577
+	/**
578
+	 * Create a new BitArray using a slice
579
+	 *
580
+	 * * if offset is non-negative, the slice will start at that offset in the bits argument.
581
+	 * * if offset is negative, the slice will start from the end of the bits argument.
582
+	 *
583
+	 * * if size is given and is positive, then the slice will have up to that many elements in it.
584
+	 * * if the bits argument is shorter than the size, then only the available elements will be present.
585
+	 * * if size is given and is negative,
586
+	 *   then the slice will stop that many elements from the end of the bits argument.
587
+	 * * if size is omitted,
588
+	 *   then the slice will have everything from offset up until the end of the bits argument.
589
+	 *
590
+	 * @param BitArray $bits   A BitArray to get the slice
591
+	 * @param int      $offset The offset
592
+	 * @param mixed    $size   The size
593
+	 *
594
+	 * @return BitArray  A new BitArray
595
+	 *
596
+	 * @since 1.1.0
597
+	 */
598
+	public static function fromSlice(BitArray $bits, $offset = 0, $size = null)
599
+	{
600
+		$offset = $bits->getRealOffset($offset);
601
+		$size = $bits->getRealSize($offset, $size);
602
+		$slice = new BitArray($size);
603
+
604
+		return $slice->directCopy($bits, 0, $offset, $size);
605
+	}
606
+
607
+	/**
608
+	 * Create a new BitArray using the concat operation
609
+	 *
610
+	 * @param BitArray $bits1 A BitArray
611
+	 * @param BitArray $bits2 A BitArray
612
+	 *
613
+	 * @return BitArray  A new BitArray
614
+	 *
615
+	 * @since 1.1.0
616
+	 */
617
+	public static function fromConcat(BitArray $bits1, BitArray $bits2)
618
+	{
619
+		$size = $bits1->size + $bits2->size;
620
+		$concat = new BitArray($size);
621
+		$concat->directCopy($bits1, 0, 0, $bits1->size);
622
+		$concat->directCopy($bits2, $bits1->size, 0, $bits2->size);
623
+
624
+		return $concat;
625
+	}
626
+
627
+	/**
628
+	 * Complement the bit array
629
+	 *
630
+	 * @return BitArray  This object for chaining
631
+	 *
632
+	 * @since 1.0.0
633
+	 */
634
+	public function applyComplement()
635
+	{
636
+		$length = strlen($this->data);
637
+
638
+		for ($index = 0; $index < $length; $index++) {
639
+			$this->data[$index] = chr(~ ord($this->data[$index]));
640
+		}
641
+
642
+		return $this->restrict();
643
+	}
644
+
645
+	/**
646
+	 * Or with an another bit array
647
+	 *
648
+	 * @param BitArray $bits A bit array
649
+	 *
650
+	 * @return BitArray  This object for chaining
651
+	 *
652
+	 * @throws InvalidArgumentException  Argument must be of equal size
653
+	 *
654
+	 * @since 1.0.0
655
+	 */
656
+	public function applyOr(BitArray $bits)
657
+	{
658
+		if ($this->size == $bits->size) {
659
+			$length = strlen($this->data);
660
+
661
+			for ($index = 0; $index < $length; $index++) {
662
+				$this->data[$index] = chr(ord($this->data[$index]) | ord($bits->data[$index]));
663
+			}
664
+
665
+			return $this;
666
+		} else {
667
+			throw new \InvalidArgumentException('Argument must be of equal size');
668
+		}
669
+	}
670
+
671
+	/**
672
+	 * And with an another bit array
673
+	 *
674
+	 * @param BitArray $bits A bit array
675
+	 *
676
+	 * @return BitArray  This object for chaining
677
+	 *
678
+	 * @throws InvalidArgumentException  Argument must be of equal size
679
+	 *
680
+	 * @since 1.0.0
681
+	 */
682
+	public function applyAnd(BitArray $bits)
683
+	{
684
+		if ($this->size == $bits->size) {
685
+			$length = strlen($this->data);
686
+
687
+			for ($index = 0; $index < $length; $index++) {
688
+				$this->data[$index] = chr(ord($this->data[$index]) & ord($bits->data[$index]));
689
+			}
690
+
691
+			return $this;
692
+		} else {
693
+			throw new \InvalidArgumentException('Argument must be of equal size');
694
+		}
695
+	}
696
+
697
+	/**
698
+	 * Xor with an another bit array
699
+	 *
700
+	 * @param BitArray $bits A bit array
701
+	 *
702
+	 * @return BitArray  This object for chaining
703
+	 *
704
+	 * @throws InvalidArgumentException  Argument must be of equal size
705
+	 *
706
+	 * @since 1.0.0
707
+	 */
708
+	public function applyXor(BitArray $bits)
709
+	{
710
+		if ($this->size == $bits->size) {
711
+			$length = strlen($this->data);
712
+
713
+			for ($index = 0; $index < $length; $index++) {
714
+				$this->data[$index] = chr(ord($this->data[$index]) ^ ord($bits->data[$index]));
715
+			}
716
+
717
+			return $this;
718
+		} else {
719
+			throw new \InvalidArgumentException('Argument must be of equal size');
720
+		}
721
+	}
722
+
723
+	/**
724
+	 * Shift a bit array.
725
+	 *
726
+	 * Negative value means the shifting is done right to left
727
+	 * while positive value means the shifting is done left to right.
728
+	 *
729
+	 * @param int     $size  Size to shift.
730
+	 *
731
+	 * @param boolean $value Value to shift
732
+	 *
733
+	 * @return BitArray  $this for chaining
734
+	 *
735
+	 * @since 1.2.0
736
+	 */
737
+	public function shift($size = 1, $value = false)
738
+	{
739
+		$size = (int) $size;
740
+
741
+		if ($size > 0) {
742
+			$min = min($this->size, $size);
743
+
744
+			for ($i = $this->size - 1; $i >= $min; $i--) {
745
+				$this[$i] = $this[$i - $min];
746
+			}
747
+
748
+			for ($i = 0; $i < $min; $i++) {
749
+				$this[$i] = $value;
750
+			}
751
+		} else {
752
+			$min = min($this->size, -$size);
753
+
754
+			for ($i = 0; $i < $this->size - $min; $i++) {
755
+				$this[$i] = $this[$i + $min];
756
+			}
757
+
758
+			for ($i = $this->size - $min; $i < $this->size; $i++) {
759
+				$this[$i] = $value;
760
+			}
761
+		}
762
+
763
+		return $this;
764
+	}
765 765
 }
Please login to merge, or discard this patch.