chdemko /
php-bitarray
| 1 | <?php |
||
| 2 | |||
| 3 | /** |
||
| 4 | * chdemko\BitArray\BitArray class |
||
| 5 | * |
||
| 6 | * @author Christophe Demko <[email protected]> |
||
| 7 | * @copyright Copyright (C) 2012-2024 Christophe Demko. All rights reserved. |
||
| 8 | * |
||
| 9 | * @license BSD 3-Clause 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 = 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; |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 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): bool |
||
| 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): bool |
||
| 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): void |
||
| 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): void |
||
| 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(): int |
||
| 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(): array |
||
| 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(): Iterator |
||
| 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 | } |
||
| 766 |