I have an array that contains values of 1 or 0 representing true or false values. e.g.
array(1,0,0,1,0,1,1,1,1);
I want to compress/encode this array into the shortest string possible so that it can be stored within a space constrained place such as a cookie. It also need to be able to be decoded again later. How do I go about this?
ps. I am working in PHP
Here is my proposal:
$a = array(1,0,0,1,0,1,1,1,1,1,0,0,1,0,1,1,1,1,1,0,0,1,0,1,1,1,1);
$compressed = base64_encode(implode('', array_map(function($i) {
return chr(bindec(implode('', $i)));
}, array_chunk($a, 8))));
var_dump($compressed); // string(8) "l8vlBw=="
So you get each 8 characters (which in fact is a binary 0..255), convert them to an integer, represent as an ASCII character, implode it to a string and convert to base64 to be able to save it as a string.
UPD:
the opposite is pretty straightforward:
$original = str_split(implode('', array_map(function($i) {
return decbin(ord($i));
}, str_split(base64_decode($compressed)))));
How exactly I wrote it (just in case anyone interesting how to write such unreadable and barely maintainable code):
I've written the $original = $compressed; and started reversing the right part of this expression step by step:
Decoded from base64 to a binary string
Split it to an array
Converted every character to its ASCII code
Converted decimal ASCII code to a binary
Joined all the binary numbers into a single one
Split the long binary string to an array
Dont use serialize. Just make a string of it:
<?php
$string = implode( '', $array );
?>
You are left with an string like this:
100101111
If you want to have an array again, just access it like an array:
$string = '100101111';
echo $string[1]; // returns "0"
?>
Of course you could also make it a decimal and just store the number. That's even shorter then the "raw" bits.
<?php
$dec = bindec( $string );
?>
How about pack and unpack
$arr = array(1,1,1,1,0,0,1,1,0,1,0,0,1,1,0,0,1,1,1,1);
$str = implode($arr);
$res = pack("h*", $str);
var_dump($res);
$rev = unpack("h*", $res);
var_dump($rev);
output:
string(10) # Not visible here
array(1) {
[1]=>
string(20) "11110011010011001111"
}
Here is my solution based on zerkms answer, this deals with the loss of leading 0's when converting decimals back into binary.
function compressBitArray(array $bitArray){
$byteChunks = array_chunk($bitArray, 8);
$asciiString = implode('', array_map(function($i) {
return chr(bindec(implode('', $i)));
},$byteChunks));
$encoded = base64_encode($asciiString).'#'.count($bitArray);
return $encoded;
}
//decode
function decompressBitArray($compressedString){
//extract origional length of the string
$parts = explode('#',$compressedString);
$origLength = $parts[1];
$asciiChars = str_split(base64_decode($parts[0]));
$bitStrings = array_map(function($i) {
return decbin(ord($i));
}, $asciiChars);
//pad lost leading 0's
for($i = 0; $i < count($bitStrings); $i++){
if($i == count($bitStrings)-1){
$toPad = strlen($bitStrings[$i]) + ($origLength - strlen(implode('', $bitStrings)));
$bitStrings[$i] = str_pad($bitStrings[$i], $toPad, '0', STR_PAD_LEFT);
}else{
if(strlen($bitStrings[$i]) < 8){
$bitStrings[$i] = str_pad($bitStrings[$i], 8, '0', STR_PAD_LEFT);
}
}
}
$bitArray = str_split(implode('', $bitStrings));
return $bitArray;
}
Related
I want to create PHP function to encode any string just with following rule.
a=1; b=2; c=3;.....y=25;z=26;
for eg.
If my string is "abc" then my encoded data will be "123".
We can use $key=>$value array but it will iterate 26 times for every letter!!
Use some delimiter so that you can identify separate characters. You can try this.
$chars = array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 4);
$str = "acd";
$encoded = array();
for ($i = 0; $i < strlen($str); $i++) {
$encoded[] = $chars[$str[$i]];
}
echo implode('|', $encoded);
Output
1|3|4
function encodedString($your_string)
$alpha_arr = range("A", "Z");
$your_string = strtoupper($your_string);
$encoded = "";
for($i=0; $i<strlen($your_string); $i++)
{
$strOne = substr($your_string, $i, 1);
if (in_array($strOne, $alpha_arr))
{
$encoded .= array_search($strOne, $alpha_arr)+1;
}
}
return $encoded;
}
You should have a padded encoding number to avoid confusions, with an array such as:
$converter = array('a' => '01', 'b' => '02' ...);
then go through each letter of the original string and construct your encoded string accessing the $converter array by key ('a', 'b' ... 'z').
you can also use str_replace to loop through the converter array and replace each character. Using str_replace you have at most N iterations, where N is the number of items in the converter array, regardless of the original string dimension.
The ord() method outputs the ascii value. now you can subtract 64 and 96 based on the text case to get the corresponding values. You will need no iterations or loop or anything. use a change case function and get the ascii value. the subtract the constant.
More details about ord method here
I have a string like this:
9.018E-14
Now I want to convert to this to the normal decimal numbers.
MyGeekPal has a nice article on it.
Code:
<?php
$total_time = 2.8848648071289E-5;
echo exp2dec($total_time);
function exp2dec($number) {
preg_match('/(.*)E-(.*)/', str_replace(".", "", $number), $matches);
$num = "0.";
while ($matches[2] > 0) {
$num .= "0";
$matches[2]--;
}
return $num . $matches[1];
}
?>
If your input is a float
If you have $number = 0.00023459 then printing this value in PHP will probably result in this exponential format. It doesn't mean the variable is stored that way; it's just an output artefact.
Use printf to work around this and gain control over your numeric output.
If your input is a string
Why the complexity?
$matches = Array();
if (preg_match('/(.*)E-(.*)/', $number, $matches)) {
$number = $matches[1] * pow(10, -1*$matches[2]);
}
Though you can tighten up the regex a bit:
$matches = Array();
if (preg_match('/(\d+(?:\.\d+)?)E(-?\d+)/i', $number, $matches)) {
$number = (float)$matches[1] * pow(10, (int)$matches[2]);
}
Live demo
EDIT: Here is some PHP magic:
$stringval = "12e-3";
$numericval = 0 + $stringval;
From the PHP docs:
If the string does not contain any of the characters '.', 'e', or 'E' and the numeric value fits into integer type limits (as defined by PHP_INT_MAX), the string will be evaluated as an integer. In all other cases it will be evaluated as a float.
If you need a more flexible format (e.g. extract four numbers from the same string), use sscanf like this:
$stringval = "12e-3";
$numericval = sscanf($stringval, "%f")[0];
echo $numericval;
The inet_pton function returns an IP address in its packed in_addr representation, i.e. ::1 becomes ``. How can I convert this 16-byte value to an integer? (Well, the string representation of an integer.)
Matty, seems there should be a capital "A" used in unpack() functions, otherwise you will lose last 4 bits if they are nulls:
$unpacked = unpack('a16', $in_addr);
2001:0db8:11a3:09d7:1f34:8a2e:07a0:7600
is converted to string(120)
$unpacked = unpack('A16', $in_addr);
2001:0db8:11a3:09d7:1f34:8a2e:07a0:7600
is converted to string(128)
Also this solution can be used for same conversion results.
The following code does it:
$in_addr = inet_pton('21DA:00D3:0000:2F3B:02AA:00FF:FE28:9C5A');
$unpacked = unpack('a16', $in_addr);
$unpacked = str_split($unpacked[1]);
$binary = '';
foreach ($unpacked as $char) {
$binary .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
}
$gmp = gmp_init($binary, 2);
$str = gmp_strval($gmp);
You can also reverse it with the following code:
$gmp = gmp_init($str);
$binary = gmp_strval($gmp, 2);
$binary = str_pad($binary, 128, '0', STR_PAD_LEFT);
$binary_arr = str_split($binary, 8);
$packed = '';
foreach ($binary_arr as $char) {
$packed .= chr(bindec($char));
}
$packed = pack('a16', $packed);
echo inet_ntop($packed);
You can't represent a 16-byte (128-bit) integer precisely in a PHP number -- they're typically stored as doubles, which lose precision before then. Here's how you can unpack one into four 32-bit integers without using GMP, though:
$in_addr = inet_pton('2001:0db8:85a3:0000:0000:8a2e:0370:7334');
list($junk, $a, $b, $c, $d) = unpack("N4", $in_addr);
printf("%08x %08x %08x %08x", $a, $b, $c, $d);
Also note the inet_ntop function, which will turn the output of inet_pton back into a human-readable format.
I've spent half day trying to figure out this and finally I got working solution.
However, I feel like this can be done in simpler way.
I think this code is not really readable.
Problem: Find first non-repetitive character from a string.
$string = "abbcabz"
In this case, the function should output "c".
The reason I use concatenation instead of $input[index_to_remove] = ''
in order to remove character from a given string
is because if I do that, it actually just leave empty cell so that my
return value $input[0] does not not return the character I want to return.
For instance,
$str = "abc";
$str[0] = '';
echo $str;
This will output "bc"
But actually if I test,
var_dump($str);
it will give me:
string(3) "bc"
Here is my intention:
Given: input
while first char exists in substring of input {
get index_to_remove
input = chars left of index_to_remove . chars right of index_to_remove
if dupe of first char is not found from substring
remove first char from input
}
return first char of input
Code:
function find_first_non_repetitive2($input) {
while(strpos(substr($input, 1), $input[0]) !== false) {
$index_to_remove = strpos(substr($input,1), $input[0]) + 1;
$input = substr($input, 0, $index_to_remove) . substr($input, $index_to_remove + 1);
if(strpos(substr($input, 1), $input[0]) == false) {
$input = substr($input, 1);
}
}
return $input[0];
}
<?php
// In an array mapped character to frequency,
// find the first character with frequency 1.
echo array_search(1, array_count_values(str_split('abbcabz')));
Python:
def first_non_repeating(s):
for i, c in enumerate(s):
if s.find(c, i+1) < 0:
return c
return None
Same in PHP:
function find_first_non_repetitive($s)
{
for($i = 0; i < strlen($s); $i++) {
if (strpos($s, $s[i], $i+1) === FALSE)
return $s[i];
}
}
Pseudocode:
Array N;
For each letter in string
if letter not exists in array N
Add letter to array and set its count to 1
else
go to its position in array and increment its count
End for
for each position in array N
if value at potition == 1
return the letter at position and exit for loop
else
//do nothing (for clarity)
end for
Basically, you find all distinct letters in the string, and for each letter, you associate it with a count of how many of that letter exist in the string. then you return the first one that has a count of 1
The complexity of this method is O(n^2) in the worst case if using arrays. You can use an associative array to increase it's performance.
1- use a sorting algotithm like mergesort (or quicksort has better performance with small inputs)
2- then control repetetive characters
non repetetive characters will be single
repetetvives will fallow each other
Performance : sort + compare
Performance : O(n log n) + O(n) = O(n log n)
For example
$string = "abbcabz"
$string = mergesort ($string)
// $string = "aabbbcz"
Then take first char form string then compare with next one if match repetetive
move to the next different character and compare
first non-matching character is non-repetetive
This can be done in much more readable code using some standard PHP functions:
// Count number of occurrences for every character
$counts = count_chars($string);
// Keep only unique ones (yes, we use this ugly pre-PHP-5.3 syntax here, but I can live with that)
$counts = array_filter($counts, create_function('$n', 'return $n == 1;'));
// Convert to a list, then to a string containing every unique character
$chars = array_map('chr', array_keys($counts));
$chars = implode($chars);
// Get a string starting from the any of the characters found
// This "strpbrk" is probably the most cryptic part of this code
$substring = strlen($chars) ? strpbrk($string, $chars) : '';
// Get the first character from the new string
$char = strlen($substring) ? $substring[0] : '';
// PROFIT!
echo $char;
$str="abbcade";
$checked= array(); // we will store all checked characters in this array, so we do not have to check them again
for($i=0; $i<strlen($str); $i++)
{
$c=0;
if(in_array($str[$i],$checked)) continue;
$checked[]=$str[$i];
for($j=$i+1;$j<=strlen($str);$j++)
{
if($str[$i]==$str[$j])
{
$c=1;
break;
}
}
if($c!=1)
{
echo "First non repetive char is:".$str[$i];
break;
}
}
This should replace your code...
$array = str_split($string);
$array = array_count_values($array);
$array = array_filter($array, create_function('$key,$val', 'return($val == 1);'));
$first_non_repeated_letter = key(array_shift($array));
Edit: spoke too soon. Took out 'array_unique', thought it actually dropped duplicate values. But character order should be preserved to be able to find the first character.
Here's a function in Scala that would do it:
def firstUnique(chars:List[Char]):Option[Char] = chars match {
case Nil => None
case head::tail => {
val filtered = tail filter (_!=head)
if (tail.length == filtered.length) Some(head) else firstUnique(filtered)
}
}
scala> firstUnique("abbcabz".toList)
res5: Option[Char] = Some(c)
And here's the equivalent in Haskell:
firstUnique :: [Char] -> Maybe Char
firstUnique [] = Nothing
firstUnique (head:tail) = let filtered = (filter (/= head) tail) in
if (tail == filtered) then (Just head) else (firstUnique filtered)
*Main> firstUnique "abbcabz"
Just 'c'
You can solve this more generally by abstracting over lists of things that can be compared for equality:
firstUnique :: Eq a => [a] -> Maybe a
Strings are just one such list.
Can be also done using array_key_exists during building an associative array from the string. Each character will be a key and will count the number as value.
$sample = "abbcabz";
$check = [];
for($i=0; $i<strlen($sample); $i++)
{
if(!array_key_exists($sample[$i], $check))
{
$check[$sample[$i]] = 1;
}
else
{
$check[$sample[$i]] += 1;
}
}
echo array_search(1, $check);
Just looked at function
str_pad($input, $pad_length, $pad_str, [STR_PAD_RIGHT, STR_PAD_LEFT, or STR_PAD_BOTH])
which helps to pad some string on left, right or on both sides of a given input.
Is there any php function which I can use to insert a string inside an input string?
for example ..
$input = "abcdef";
$pad_str = "#";
so if I give insert index 3, it inserts "#" after first 3 left most characters and $input becomes "abc#def".
thanks
You're looking for a string insert, not a padding.
Padding makes a string a set length, if it's not already at that length, so if you were to give a pad length 3 to "abcdef", well it's already at 3, so nothing should happen.
Try:
$newstring = substr_replace($orig_string, $insert_string, $position, 0);
PHP manual on substr_replace
you need:
substr($input, 0, 3).$pad_str.substr($input, 3)
Bah, I misread the question. You want a single insert, not insert every X characters. Sorry.
I'll leave it here so it's not wasted.
You can use regular expressions and some calculation to get your desired result (you probably could make it with pure regexp, but that would be more complex and less readable)
vinko#mithril:~$ more re.php
<?php
$test1 = "123123123";
$test2 = "12312";
echo puteveryXcharacters($a,"#",3);
echo "\n";
echo puteveryXcharacters($b,"#",3);
echo "\n";
echo puteveryXcharacters($b,"$",3);
echo "\n";
function puteveryXcharacters($str,$wha,$cnt) {
$strip = false;
if (strlen($str) % $cnt == 0) {
$strip = true;
}
$tmp = preg_replace('/(.{'.$cnt.'})/',"$1$wha", $str);
if ($strip) {
$tmp = substr($tmp,0,-1);
}
return $tmp;
}
?>
vinko#mithril:~$ php re.php
123#123#123
123#12
123$12