Completed
Push — master ( 266b79...1485a5 )
by Christophe
07:44
created

BitArray::getRealOffset()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 21
rs 9.0534
cc 4
eloc 9
nc 4
nop 1
1
<?php
2
3
/**
4
 * chdemko\BitArray\BitArray class
5
 *
6
 * @author     Christophe Demko <[email protected]>
7
 * @copyright  Copyright (C) 2012-2016 Christophe Demko. All rights reserved.
8
 *
9
 * @license    http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html The CeCILL B license
10
 *
11
 * This file is part of the php-bitarray package https://github.com/chdemko/php-bitarray
12
 */
13
14
// Declare chdemko\BitArray namespace
15
namespace chdemko\BitArray;
16
17
/**
18
 * Array of bits
19
 *
20
 * @package  BitArray
21
 *
22
 * @property-read  integer    $count  The number of bits set to true
23
 * @property-read  integer    $size   The number of bits
24
 * 
25
 * @since    1.0.0
26
 */
27
class BitArray implements \ArrayAccess, \Countable, \IteratorAggregate, \JsonSerializable
28
{
29
	/**
30
	 * @var  integer[]  Number of bits for each value between 0 and 255
31
	 */
32
	private static $count = [
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 = [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
	 *
75
	 * @since   1.0.0
76
	 */
77
	protected function __construct($size)
78
	{
79
		$this->size = (int) $size;
80
		$this->data = str_repeat("\0", ceil($this->size / 8));
81
	}
82
83
	/**
84
	 * Clone a BitArray
85
	 *
86
	 * @return  void
87
	 *
88
	 * @since   1.0.0
89
	 */
90
	public function __clone()
91
	{
92
		$this->data = str_repeat($this->data, 1);
93
	}
94
95
	/**
96
	 * Convert the object to a string
97
	 *
98
	 * @return  string  String representation of this object
99
	 *
100
	 * @since   1.0.0
101
	 */
102
	public function __toString()
103
	{
104
		$string = str_repeat('0', $this->size);
105
106
		for ($offset = 0; $offset < $this->size; $offset++)
107
		{
108
			if (ord($this->data[(int) ($offset / 8)]) & (1 << $offset % 8))
109
			{
110
				$string[$offset] = '1';
111
			}
112
		}
113
114
		return $string;
115
	}
116
117
	/**
118
	 * Magic get method
119
	 *
120
	 * @param   string  $property  The property
121
	 *
122
	 * @throws  \RuntimeException  If the property does not exist
123
	 *
124
	 * @return  mixed  The value associated to the property
125
	 *
126
	 * @since   1.0.0
127
	 */
128
	public function __get($property)
129
	{
130
		switch ($property)
131
		{
132
			case 'size':
133
				return $this->size;
134
			case 'count':
135
				return $this->count();
136
			default:
137
				throw new \RuntimeException('Undefined property');
138
		}
139
	}
140
141
	/**
142
	 * Test the existence of an index
143
	 *
144
	 * @param   integer  $offset  The offset
145
	 *
146
	 * @return  boolean  The truth value
147
	 *
148
	 * @since   1.0.0
149
	 */
150
	public function offsetExists($offset)
151
	{
152
		return is_int($offset) && $offset >= 0 && $offset < $this->size;
153
	}
154
155
	/**
156
	 * Get the truth value for an index
157
	 *
158
	 * @param   integer  $offset  The offset
159
	 *
160
	 * @return  boolean  The truth value
161
	 *
162
	 * @throw   \OutOfRangeException  Argument index must be an positive integer lesser than the size
163
	 *
164
	 * @since   1.0.0
165
	 */
166
	public function offsetGet($offset)
167
	{
168
		if ($this->offsetExists($offset))
169
		{
170
			return (bool) (ord($this->data[(int) ($offset / 8)]) & (1 << $offset % 8));
171
		}
172
		else
173
		{
174
			throw new \OutOfRangeException('Argument offset must be a positive integer lesser than the size');
175
		}
176
	}
177
178
	/**
179
	 * Set the truth value for an index
180
	 *
181
	 * @param   integer  $offset  The offset
182
	 * @param   boolean  $value   The truth value
183
	 *
184
	 * @return  void
185
	 *
186
	 * @throw   \OutOfRangeException  Argument index must be an positive integer lesser than the size
187
	 *
188
	 * @since   1.0.0
189
	 */
190
	public function offsetSet($offset, $value)
191
	{
192
		if ($this->offsetExists($offset))
193
		{
194
			$index = (int) ($offset / 8);
195
196
			if ($value)
197
			{
198
				$this->data[$index] = chr(ord($this->data[$index]) | (1 << $offset % 8));
199
			}
200
			else
201
			{
202
				$this->data[$index] = chr(ord($this->data[$index]) & ~(1 << $offset % 8));
203
			}
204
		}
205
		else
206
		{
207
			throw new \OutOfRangeException('Argument index must be a positive integer lesser than the size');
208
		}
209
	}
210
211
	/**
212
	 * Unset the existence of an index
213
	 *
214
	 * @param   integer  $offset  The index
215
	 *
216
	 * @return  void
217
	 *
218
	 * @throw   \RuntimeException  Values cannot be unset
219
	 *
220
	 * @since   1.0.0
221
	 */
222
	public function offsetUnset($offset)
223
	{
224
		throw new \RuntimeException('Values cannot be unset');
225
	}
226
227
	/**
228
	 * Return the number of true bits
229
	 *
230
	 * @return  integer  The number of true bits
231
	 *
232
	 * @since   1.0.0
233
	 */
234
	public function count()
235
	{
236
		$count = 0;
237
238
		for ($index = 0, $length = strlen($this->data); $index < $length; $index++)
239
		{
240
			$count += self::$count[ord($this->data[$index])];
241
		}
242
243
		return $count;
244
	}
245
246
	/**
247
	 * Transform the object to an array
248
	 *
249
	 * @return  array  Array of values
250
	 *
251
	 * @since   1.1.0
252
	 */
253
	public function toArray()
254
	{
255
		$array = [];
256
257
		for ($index = 0; $index < $this->size; $index++)
258
		{
259
			$array[] = (bool) (ord($this->data[(int) ($index / 8)]) & (1 << $index % 8));
260
		}
261
262
		return $array;
263
	}
264
265
	/**
266
	 * Serialize the object
267
	 *
268
	 * @return  array  Array of values
269
	 *
270
	 * @since   1.0.0
271
	 */
272
	public function jsonSerialize()
273
	{
274
		return $this->toArray();
275
	}
276
277
	/**
278
	 * Get an iterator
279
	 *
280
	 * @return  Iterator  Iterator
281
	 *
282
	 * @since   1.0.0
283
	 */
284
	public function getIterator()
285
	{
286
		return new Iterator($this);
287
	}
288
289
	/**
290
	 * Return the size
291
	 *
292
	 * @return  integer  The size
293
	 *
294
	 * @since   1.0.0
295
	 */
296
	public function size()
297
	{
298
		return $this->size;
299
	}
300
301
	/**
302
	 * Copy bits directly from a BitArray
303
	 *
304
	 * @param   BitArray  $bits    A BitArray to copy
305
	 * @param   int       $index   Starting index for destination
306
	 * @param   int       $offset  Starting index for copying
307
	 * @param   int       $size    Copy size
308
	 *
309
	 * @return  BitArray  This object for chaining
310
	 *
311
	 * @throw   \OutOfRangeException  Argument index must be an positive integer lesser than the size
312
	 *
313
	 * @since   1.1.0
314
	 */
315
	public function directCopy(BitArray $bits, $index = 0, $offset = 0, $size = 0)
316
	{
317
		if ($offset > $index)
318
		{
319 View Code Duplication
			for ($i = 0; $i < $size; $i++)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
320
			{
321
				$this[$i + $index] = $bits[$i + $offset];
322
			}
323
		}
324
		else
325
		{
326 View Code Duplication
			for ($i = $size - 1; $i >= 0; $i--)
1 ignored issue
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
327
			{
328
				$this[$i + $index] = $bits[$i + $offset];
329
			}
330
		}
331
332
		return $this;
333
	}
334
335
	/**
336
	 * Copy bits from a BitArray
337
	 *
338
	 * @param   BitArray  $bits    A BitArray to copy
339
	 * @param   int       $index   Starting index for destination.
340
	 *                             If index is non-negative, the index parameter is used as it is, keeping its real value between 0 and size-1.
341
	 *                             If index is negative, the index parameter starts from the end, keeping its real value between 0 and size-1.
342
	 * @param   int       $offset  Starting index for copying.
343
	 *                             If offset is non-negative, the offset parameter is used as it is, keeping its positive value between 0 and size-1.
344
	 *                             If offset is negative, the offset parameter starts from the end, keeping its real value between 0 and size-1.
345
	 * @param   mixed     $size    Copy size.
346
	 *                             If size is given and is positive, then the copy will copy size elements.
347
	 *                             If the bits argument is shorter than the size, then only the available elements will be copied.
348
	 *                             If size is given and is negative then the copy starts from the end.
349
	 *                             If it is omitted, then the copy will have everything from offset up until the end of the bits argument.
350
	 *
351
	 * @return  BitArray  This object for chaining
352
	 *
353
	 * @since   1.1.0
354
	 */
355
	public function copy(BitArray $bits, $index = 0, $offset = 0, $size = null)
356
	{
357
		$index = $this->getRealOffset($index);
358
		$offset = $bits->getRealOffset($offset);
359
		$size = $bits->getRealSize($offset, $size);
360
361
		if ($size > $this->size - $index)
362
		{
363
			$size = $this->size - $index;
364
		}
365
366
		return $this->directCopy($bits, $index, $offset, $size);
367
	}
368
369
	/**
370
	 * Get the real offset using a positive or negative offset
371
	 *
372
	 * @param   int  $offset  If offset is non-negative, the offset parameter is used as it is, keeping its real value between 0 and size-1.
373
	 *                        If offset is negative, the offset parameter starts from the end, keeping its real value between 0 and size-1.
374
	 *
375
	 * @return  int  The real offset
376
	 *
377
	 * @since   1.1.0
378
	 */
379
	protected function getRealOffset($offset)
380
	{
381
		$offset = (int) $offset;
382
383
		if ($offset < 0)
384
		{
385
			// Start from the end
386
			$offset = $this->size + $offset;
387
388
			if ($offset < 0)
389
			{
390
				$offset = 0;
391
			}
392
		}
393
		elseif ($offset > $this->size)
394
		{
395
			$offset = $this->size;
396
		}
397
398
		return $offset;
399
	}
400
401
	/**
402
	 * Get the real offset using a positive or negative offset
403
	 *
404
	 * @param   int    $offset  The real offset.
405
	 * @param   mixed  $size    If size is given and is positive, then the real size will be between 0 and the current size-1.
406
	 *                          If size is given and is negative then the real size starts from the end.
407
	 *                          If it is omitted, then the size goes until the end of the BitArray.
408
	 *
409
	 * @return  int  The real size
410
	 *
411
	 * @since   1.1.0
412
	 */
413
	protected function getRealSize($offset, $size)
414
	{
415
		if ($size === null)
416
		{
417
			$size = $this->size - $offset;
418
		}
419
		else
420
		{
421
			$size = (int) $size;
422
423
			if ($size < 0)
424
			{
425
				$size = $this->size + $size - $offset;
426
427
				if ($size < 0)
428
				{
429
					$size = 0;
430
				}
431
			}
432
			elseif ($size > $this->size - $offset)
433
			{
434
				$size = $this->size - $offset;
435
			}
436
		}
437
438
		return $size;
439
	}
440
441
	/**
442
	 * Create a new BitArray from an integer
443
	 *
444
	 * @param   integer  $size  Size of the BitArray
445
	 *
446
	 * @return  BitArray  A new BitArray
447
	 *
448
	 * @since   1.0.0
449
	 */
450
	public static function fromInteger($size)
451
	{
452
		return new BitArray($size);
453
	}
454
455
	/**
456
	 * Create a new BitArray from a traversable
457
	 *
458
	 * @param   \Traversable  $traversable  A traversable and countable
459
	 *
460
	 * @return  BitArray  A new BitArray
461
	 *
462
	 * @since   1.0.0
463
	 */
464 View Code Duplication
	public static function fromTraversable($traversable)
465
	{
466
		$bits = new BitArray(count($traversable));
467
		$offset = 0;
468
		$ord = 0;
469
470
		foreach ($traversable as $value)
471
		{
472
			if ($value)
473
			{
474
				$ord |= 1 << $offset % 8;
475
			}
476
477
			if ($offset % 8 === 7)
478
			{
479
				$bits->data[(int) ($offset / 8)] = chr($ord);
480
				$ord = 0;
481
			}
482
483
			$offset++;
484
		}
485
486
		if ($offset % 8 !== 0)
487
		{
488
			$bits->data[(int) ($offset / 8)] = chr($ord);
489
		}
490
491
		return $bits;
492
	}
493
494
	/**
495
	 * Create a new BitArray from a bit string
496
	 *
497
	 * @param   string  $string  A bit string
498
	 *
499
	 * @return  BitArray  A new BitArray
500
	 *
501
	 * @since   1.0.0
502
	 */
503 View Code Duplication
	public static function fromString($string)
504
	{
505
		$bits = new BitArray(strlen($string));
506
		$ord = 0;
507
508
		for ($offset = 0; $offset < $bits->size; $offset++)
509
		{
510
			if ($string[$offset] !== '0')
511
			{
512
				$ord |= 1 << $offset % 8;
513
			}
514
515
			if ($offset % 8 === 7)
516
			{
517
				$bits->data[(int) ($offset / 8)] = chr($ord);
518
				$ord = 0;
519
			}
520
		}
521
522
		if ($offset % 8 !== 0)
523
		{
524
			$bits->data[(int) ($offset / 8)] = chr($ord);
525
		}
526
527
		return $bits;
528
	}
529
530
	/**
531
	 * Create a new BitArray from json
532
	 *
533
	 * @param   string  $json  A json encoded value
534
	 *
535
	 * @return  BitArray  A new BitArray
536
	 *
537
	 * @since   1.0.0
538
	 */
539
	public static function fromJson($json)
540
	{
541
		return self::fromTraversable(json_decode($json));
542
	}
543
544
	/**
545
	 * Create a new BitArray using a slice
546
	 *
547
	 * @param   BitArray  $bits    A BitArray to get the slice
548
	 * @param   int       $offset  If offset is non-negative, the slice will start at that offset in the bits argument.
549
	 *                             If offset is negative, the slice will start from the end of the bits argument.
550
	 * @param   mixed     $size    If size is given and is positive, then the slice will have up to that many elements in it.
551
	 *                             If the bits argument is shorter than the size, then only the available elements will be present.
552
	 *                             If size is given and is negative then the slice will stop that many elements from the end of the bits argument.
553
	 *                             If it is omitted, then the slice will have everything from offset up until the end of the bits argument.
554
	 *
555
	 * @return  BitArray  A new BitArray
556
	 *
557
	 * @since   1.1.0
558
	 */
559
	public static function fromSlice(BitArray $bits, $offset = 0, $size = null)
560
	{
561
		$offset = $bits->getRealOffset($offset);
562
		$size = $bits->getRealSize($offset, $size);
563
		$slice = new BitArray($size);
564
565
		return $slice->directCopy($bits, 0, $offset, $size);
566
	}
567
568
	/**
569
	 * Create a new BitArray using the concat operation
570
	 *
571
	 * @param   BitArray  $bits1  A BitArray
572
	 * @param   BitArray  $bits2  A BitArray
573
	 *
574
	 * @return  BitArray  A new BitArray
575
	 *
576
	 * @since   1.1.0
577
	 */
578
	public static function fromConcat(BitArray $bits1, BitArray $bits2)
579
	{
580
		$size = $bits1->size + $bits2->size;
581
		$concat = new BitArray($size);
582
		$concat->directCopy($bits1, 0, 0, $bits1->size);
1 ignored issue
show
Unused Code introduced by
The call to the method chdemko\BitArray\BitArray::directCopy() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
583
		$concat->directCopy($bits2, $bits1->size, 0, $bits2->size);
1 ignored issue
show
Unused Code introduced by
The call to the method chdemko\BitArray\BitArray::directCopy() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
584
585
		return $concat;
586
	}
587
588
	/**
589
	 * Complement the bit array
590
	 *
591
	 * @return  BitArray  This object for chaining
592
	 *
593
	 * @since   1.0.0
594
	 */
595
	public function applyComplement()
596
	{
597
		$length = strlen($this->data);
598
599
		for ($index = 0; $index < $length; $index++)
600
		{
601
			$this->data[$index] = chr(~ ord($this->data[$index]));
602
		}
603
604
		// Remove useless bits
605
		if ($length > 0)
606
		{
607
			$this->data[$length - 1] = chr(ord($this->data[$length - 1]) & self::$restrict[$this->size % 8]);
608
		}
609
610
		return $this;
611
	}
612
613
	/**
614
	 * Or with an another bit array
615
	 *
616
	 * @param   BitArray  $bits  A bit array
617
	 *
618
	 * @return  BitArray  This object for chaining
619
	 *
620
	 * @throw   \InvalidArgumentException  Argument must be of equal size
621
	 *
622
	 * @since   1.0.0
623
	 */
624 View Code Duplication
	public function applyOr(BitArray $bits)
625
	{
626
		if ($this->size == $bits->size)
627
		{
628
			$length = strlen($this->data);
629
630
			for ($index = 0; $index < $length; $index++)
631
			{
632
				$this->data[$index] = chr(ord($this->data[$index]) | ord($bits->data[$index]));
633
			}
634
635
			return $this;
636
		}
637
		else
638
		{
639
			throw new \InvalidArgumentException('Argument must be of equal size');
640
		}
641
	}
642
643
	/**
644
	 * And with an another bit array
645
	 *
646
	 * @param   BitArray  $bits  A bit array
647
	 *
648
	 * @return  BitArray  This object for chaining
649
	 *
650
	 * @throw   \InvalidArgumentException  Argument must be of equal size
651
	 *
652
	 * @since   1.0.0
653
	 */
654 View Code Duplication
	public function applyAnd(BitArray $bits)
655
	{
656
		if ($this->size == $bits->size)
657
		{
658
			$length = strlen($this->data);
659
660
			for ($index = 0; $index < $length; $index++)
661
			{
662
				$this->data[$index] = chr(ord($this->data[$index]) & ord($bits->data[$index]));
663
			}
664
665
			return $this;
666
		}
667
		else
668
		{
669
			throw new \InvalidArgumentException('Argument must be of equal size');
670
		}
671
	}
672
673
	/**
674
	 * Xor with an another bit array
675
	 *
676
	 * @param   BitArray  $bits  A bit array
677
	 *
678
	 * @return  BitArray  This object for chaining
679
	 *
680
	 * @throw   \InvalidArgumentException  Argument must be of equal size
681
	 *
682
	 * @since   1.0.0
683
	 */
684 View Code Duplication
	public function applyXor(BitArray $bits)
685
	{
686
		if ($this->size == $bits->size)
687
		{
688
			$length = strlen($this->data);
689
690
			for ($index = 0; $index < $length; $index++)
691
			{
692
				$this->data[$index] = chr(ord($this->data[$index]) ^ ord($bits->data[$index]));
693
			}
694
695
			return $this;
696
		}
697
		else
698
		{
699
			throw new \InvalidArgumentException('Argument must be of equal size');
700
		}
701
	}
702
}
703