1

Old question name: How to effectively split a binary string in a groups of 10, 0, 11?

I have some strings as an input, which are binary representation of a number. For example:

10011
100111
0111111
11111011101

I need to split these strings (or arrays) into groups of 10, 0, and 11 in order to replace them.

10 => 11
0 => 0
11 => 10

How to do it? I have tried these options but don't work.

preg_match('/([10]{2})(0{1})([11]{2})/', $S, $matches);

It should be [10] [0], [11] for 10011 input. And it should be 11010 when replaced.

UPD1.

Actually, I'm trying to do a negation algorithm for converting a positive number in a base -2 to a negative one in a base -2. It could be done with an algorithm from Wikipedia with a loop. But byte groups replacing is a much faster. I have implemented it already and just trying to optimize it.

For this case 0111111 it's possible to add 0 in the end. Then rules will be applied. And we could remove leading zeros in a result. The output will be 101010.

UPD2.

@Wiktor Stribiżew proposed an idea how to do a replace immediately, without splitting bytes into groups first. But I have a faster solution already.

$S = strtr($S, $rules);

The meaning of this question isn't do a replacement, but get an array of desired groups [11] [0] [10].

UPD3.

This is a solution which I reached with an idea of converting binary groups. It's faster than one with a loop.

function solution2($A)
{
    $S = implode('', $A);

    //we could add leading 0
    if (substr($S, strlen($S) - 1, 1) == 1) {
        $S .= '0';
    }

    $rules = [
        '10' => '11',
        '0'  => '0',
        '11' => '10',
    ];

    $S = strtr($S, $rules);

    $arr = str_split($S);

    //remove leading 0
    while ($arr[count($arr) - 1] == 0) {
        array_pop($arr);
    }

    return $arr;
}

But the solution in @Alex Blex answer is faster.

Oleg Abrazhaev
  • 2,751
  • 2
  • 28
  • 41
  • 1
    how do you expect to handle this `0, 1, 1, 1, 1, 1, 1` ? the output may be like ?? – hassan Mar 22 '17 at 12:18
  • 1
    out of curiosity, what's the practical use? @hassan it should be 0101010. I remember similar questions from my uni 20 years ago, still don't see why I was asked for a such meaningless things. – Alex Blex Mar 22 '17 at 12:20
  • @hassan, I have answered in question update. – Oleg Abrazhaev Mar 22 '17 at 12:32
  • @Alex, I have answered in question update. – Oleg Abrazhaev Mar 22 '17 at 12:33
  • 1
    @OlegAbrazhaev: what is the point to write `array_combine(array_keys($rules), array_values($rules))` (that exactly returns the original array `$rules`)? Writing `$S = strtr($S, $rules);` does exactly the same. – Casimir et Hippolyte Mar 22 '17 at 15:12
  • 1
    Fair enough, practical use of negabinary is still beyond my understanding tho. I believe the algorithm with the loop is to explain how negative base works. The loop-less bitwise algorithm is at the bottom of the wiki page. See my answer. – Alex Blex Mar 23 '17 at 01:48
  • @Casimir et Hippolyte it looks cooler. :) I don't remember. Maybe in first version rules were separated in two arrays. Will fix it. – Oleg Abrazhaev Mar 23 '17 at 12:10
  • As it turned out the 'practical' use is a Codility test =) – Alex Blex Mar 23 '17 at 13:54

2 Answers2

3

You may use a simple /11|10/ regex with a preg_replace_callback:

$s = '10011';
echo preg_replace_callback("/11|10/", function($m) {
    return $m[0] == "11" ? "10" : "11"; // if 11 is matched, replace with 10 or vice versa
}, $s);
// => 11010

See the online PHP demo.

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
1

Answering the question

algorithm for converting a positive number in a base -2 to a negative one in a base -2

I believe following function is more efficient than a regex:

function negate($negabin)
{
    $mask = 0xAAAAAAAAAAAAAAA;
    return decbin((($mask<<1)-($mask^bindec($negabin)))^$mask);     
}

Parameter is a positive int60 in a base -2 notation, e.g. 11111011101.

The function converts the parameter to base 10, negate it, and convert it back to base -2 as described in the wiki: https://en.wikipedia.org/wiki/Negative_base#To_negabinary

Works on 64bit system, but can easily adopted to work on 32bit.

Alex Blex
  • 34,704
  • 7
  • 48
  • 75
  • Doesn't work. 10011 should return 11010. This function returns 110001. – Oleg Abrazhaev Mar 23 '17 at 12:26
  • your mistake that you have used bindec to decode binary string in a number. It should be done using special formula. – Oleg Abrazhaev Mar 23 '17 at 12:30
  • how come? 10011 `-2` = 15 `10`. -15 `10` = 110001 `-2`. Your expected result 11010 `-2` = 6 `10`. Did I miss something in the quoted part of the question? – Alex Blex Mar 23 '17 at 12:40
  • 10011 `-2` = 9 `10`, because of reversal conversion formula. `$N = count($A); $decodedValue = 0; //by formula for ($i = $N - 1; $i >= 0; $i--) { $decodedValue += $A[$i] * pow(-2, $i); }` – Oleg Abrazhaev Mar 23 '17 at 12:45
  • and then -9 `10` = 11010 `-2` produced by code with a loop from Wikipedia. this is correct. – Oleg Abrazhaev Mar 23 '17 at 12:48
  • 1
    Sorry mate, but it must be another kind of negabinary, or an error in your *conversion formula*. There is a list of precalculated values for positive integers http://oeis.org/A039724/b039724.txt which can be used for unittests. – Alex Blex Mar 23 '17 at 12:54
  • Formula is correct. `B[i]*(-2)^i`. Read this answer for clarification. http://stackoverflow.com/questions/33056593/how-to-negate-base-2-numbers/33056852?noredirect=1#comment72879711_33056852 – Oleg Abrazhaev Mar 23 '17 at 13:08
  • 1
    okay, I'm not going to convince you that 10011 `-2` = (-2)^4 + (-2)^1 + (-2)^0 = 16 - 2 + 1 = 15 `10`. If it is 9 `10` in your reality, let it be so. Let's put it simple - my answer applies to the universe where 10011 `-2` is 15 `10`, as it is in the table on https://en.wikipedia.org/wiki/Negative_base – Alex Blex Mar 23 '17 at 13:37
  • I just figured out from txt file that you have posted above, that 9 in a base -2 is 11001 and 15 is 10011. It means that I have reversed binary string as input. :) But it's not a mistake, it was provided by my task definition. That means I could fix your code just by doing `$S = implode('', array_reverse(str_split($S)));` before applying a mask. And then it works. – Oleg Abrazhaev Mar 23 '17 at 13:51
  • 1
    You need to do the same for the result of the function. – Alex Blex Mar 23 '17 at 13:55