Completed
Push — master ( db6402...0324e8 )
by Christophe
01:52
created

BitArray::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
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 ($i = 0, $length = strlen($this->data); $i < $length; $i++)
239
		{
240
			$count += self::$count[ord($this->data[$i])];
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 ($offset = 0; $offset < $this->size; $offset++)
258
		{
259
			$array[] = (bool) (ord($this->data[(int) ($offset / 8)]) & (1 << $offset % 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
	 * Create a new BitArray from an integer
303
	 *
304
	 * @param   integer  $size  Size of the bitarray
305
	 *
306
	 * @return  BitArray  A new BitArray
307
	 *
308
	 * @since   1.0.0
309
	 */
310
	public static function fromInteger($size)
311
	{
312
		return new BitArray($size);
313
	}
314
315
	/**
316
	 * Create a new BitArray from a traversable
317
	 *
318
	 * @param   \Traversable  $traversable  A traversable and countable
319
	 *
320
	 * @return  BitArray  A new BitArray
321
	 *
322
	 * @since   1.0.0
323
	 */
324 View Code Duplication
	public static function fromTraversable($traversable)
325
	{
326
		$bits = new BitArray(count($traversable));
327
		$offset = 0;
328
		$ord = 0;
329
330
		foreach ($traversable as $value)
331
		{
332
			if ($value)
333
			{
334
				$ord |= 1 << $offset % 8;
335
			}
336
337
			if ($offset % 8 === 7)
338
			{
339
				$bits->data[(int) ($offset / 8)] = chr($ord);
340
				$ord = 0;
341
			}
342
343
			$offset++;
344
		}
345
346
		if ($offset % 8 !== 0)
347
		{
348
			$bits->data[(int) ($offset / 8)] = chr($ord);
349
		}
350
351
		return $bits;
352
	}
353
354
	/**
355
	 * Create a new BitArray from a bit string
356
	 *
357
	 * @param   string  $string  A bit string
358
	 *
359
	 * @return  BitArray  A new BitArray
360
	 *
361
	 * @since   1.0.0
362
	 */
363 View Code Duplication
	public static function fromString($string)
364
	{
365
		$bits = new BitArray(strlen($string));
366
		$ord = 0;
367
368
		for ($offset = 0; $offset < $bits->size; $offset++)
369
		{
370
			if ($string[$offset] !== '0')
371
			{
372
				$ord |= 1 << $offset % 8;
373
			}
374
375
			if ($offset % 8 === 7)
376
			{
377
				$bits->data[(int) ($offset / 8)] = chr($ord);
378
				$ord = 0;
379
			}
380
		}
381
382
		if ($offset % 8 !== 0)
383
		{
384
			$bits->data[(int) ($offset / 8)] = chr($ord);
385
		}
386
387
		return $bits;
388
	}
389
390
	/**
391
	 * Create a new BitArray from json
392
	 *
393
	 * @param   string  $json  A json encoded value
394
	 *
395
	 * @return  BitArray  A new BitArray
396
	 *
397
	 * @since   1.0.0
398
	 */
399
	public static function fromJson($json)
400
	{
401
		return self::fromTraversable(json_decode($json));
402
	}
403
404
	/**
405
	 * Create a new BitArray using a slice
406
	 *
407
	 * @param   BitArray  $bits    A bitarray to get the slice
408
	 * @param   int       $offset  If offset is non-negative, the slice will start at that offset in the bits argument.
409
	 *                             If offset is negative, the slice will start that far from the end of the bits argument.
410
	 * @param   mixed     $size    If size is given and is positive, then the slice will have up to that many elements in it.
411
	 *                             If the bits argument is shorter than the size, then only the available elements will be present.
412
	 *                             If size is given and is negative then the slice will stop that many elements from the end of the bits argument.
413
	 *                             If it is omitted, then the slice will have everything from offset up until the end of the bits argument.
414
	 *
415
	 * @return  BitArray  A new BitArray
416
	 *
417
	 * @since   1.1.0
418
	 */
419
	public static function fromSlice(BitArray $bits, $offset = 0, $size = null)
420
	{
421
		$offset = (int) $offset;
422
423
		if ($offset < 0)
424
		{
425
			// Start from the end
426
			$offset = $bits->size + $offset;
427
428
			if ($offset < 0)
429
			{
430
				$offset = 0;
431
			}
432
		}
433
		elseif ($offset > $bits->size)
434
		{
435
			$offset = $bits->size;
436
		}
437
438
		if ($size === null)
439
		{
440
			$size = $bits->size - $offset;
441
		}
442
		else
443
		{
444
			$size = (int) $size;
445
446
			if ($size < 0)
447
			{
448
				$size = $bits->size + $size - $offset;
449
450
				if ($size < 0)
451
				{
452
					$size = 0;
453
				}
454
			}
455
			elseif ($size > $bits->size - $offset)
456
			{
457
				$size = $bits->size - $offset;
458
			}
459
		}
460
461
		$slice = new BitArray($size);
462
463
		for ($i = 0; $i < $size; $i++)
464
		{
465
			$slice[$i] = $bits[$i + $offset];
466
		}
467
468
		return $slice;
469
	}
470
471
	/**
472
	 * Create a new BitArray using the concat operation
473
	 *
474
	 * @param   BitArray  $bits1  A bitarray
475
	 * @param   BitArray  $bits2  A bitarray
476
	 *
477
	 * @return  BitArray  A new BitArray
478
	 *
479
	 * @since   1.1.0
480
	 */
481
	public static function fromConcat(BitArray $bits1, BitArray $bits2)
482
	{
483
		$size = $bits1->size + $bits2->size;
484
		$concat = new BitArray($size);
485
486 View Code Duplication
		for ($i = 0; $i < $bits1->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...
487
		{
488
			$concat[$i] = $bits1[$i];
489
		}
490
491 View Code Duplication
		for ($i = 0; $i < $bits2->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...
492
		{
493
			$concat[$i + $bits1->size] = $bits2[$i];
494
		}
495
496
		return $concat;
497
	}
498
499
	/**
500
	 * Complement the bit array
501
	 *
502
	 * @return  BitArray  This object for chaining
503
	 *
504
	 * @since   1.0.0
505
	 */
506
	public function applyComplement()
507
	{
508
		$length = strlen($this->data);
509
510
		for ($i = 0; $i < $length; $i++)
511
		{
512
			$this->data[$i] = chr(~ ord($this->data[$i]));
513
		}
514
515
		// Remove useless bits
516
		if ($length > 0)
517
		{
518
			$this->data[$length - 1] = chr(ord($this->data[$length - 1]) & self::$restrict[$this->size % 8]);
519
		}
520
521
		return $this;
522
	}
523
524
	/**
525
	 * Or with an another bit array
526
	 *
527
	 * @param   BitArray  $bits  A bit array
528
	 *
529
	 * @return  BitArray  This object for chaining
530
	 *
531
	 * @throw   \InvalidArgumentException  Argument must be of equal size
532
	 *
533
	 * @since   1.0.0
534
	 */
535 View Code Duplication
	public function applyOr(BitArray $bits)
536
	{
537
		if ($this->size == $bits->size)
538
		{
539
			$length = strlen($this->data);
540
541
			for ($i = 0; $i < $length; $i++)
542
			{
543
				$this->data[$i] = chr(ord($this->data[$i]) | ord($bits->data[$i]));
544
			}
545
546
			return $this;
547
		}
548
		else
549
		{
550
			throw new \InvalidArgumentException('Argument must be of equal size');
551
		}
552
	}
553
554
	/**
555
	 * And with an another bit array
556
	 *
557
	 * @param   BitArray  $bits  A bit array
558
	 *
559
	 * @return  BitArray  This object for chaining
560
	 *
561
	 * @throw   \InvalidArgumentException  Argument must be of equal size
562
	 *
563
	 * @since   1.0.0
564
	 */
565 View Code Duplication
	public function applyAnd(BitArray $bits)
566
	{
567
		if ($this->size == $bits->size)
568
		{
569
			$length = strlen($this->data);
570
571
			for ($i = 0; $i < $length; $i++)
572
			{
573
				$this->data[$i] = chr(ord($this->data[$i]) & ord($bits->data[$i]));
574
			}
575
576
			return $this;
577
		}
578
		else
579
		{
580
			throw new \InvalidArgumentException('Argument must be of equal size');
581
		}
582
	}
583
584
	/**
585
	 * Xor with an another bit array
586
	 *
587
	 * @param   BitArray  $bits  A bit array
588
	 *
589
	 * @return  BitArray  This object for chaining
590
	 *
591
	 * @throw   \InvalidArgumentException  Argument must be of equal size
592
	 *
593
	 * @since   1.0.0
594
	 */
595 View Code Duplication
	public function applyXor(BitArray $bits)
596
	{
597
		if ($this->size == $bits->size)
598
		{
599
			$length = strlen($this->data);
600
601
			for ($i = 0; $i < $length; $i++)
602
			{
603
				$this->data[$i] = chr(ord($this->data[$i]) ^ ord($bits->data[$i]));
604
			}
605
606
			return $this;
607
		}
608
		else
609
		{
610
			throw new \InvalidArgumentException('Argument must be of equal size');
611
		}
612
	}
613
}
614