update the string and preserve old data in array - php

I'm curious if it is possible to make this piece of code I've made a bit shorter and probably faster? The goal of this code below is to update the string by changing (and preserving) numbers in it with ordered replacements such as {#0}, {#1} and so on for each number found.
Also, keep that found numbers separately in array so we may recover information at any time.
The code below works but I believe it may be significantly optimized and hopefully done in one step.
$str = "Lnlhkjfs7834hfdhrf87whf4akuhf999re";//could be any string
$nums = array();
$count = 0;
$res = preg_replace_callback('/\d+/', function($match) use(&$count) {
global $nums;
$nums[] = $match[0];
return "{#".($count++)."}";
}, $str);
print_r($str); // "Lnlhkjfs7834hfdhrf87whf4akuhf999re"
print_r($res); // "Lnlhkjfs{#0}hfdhrf{#1}whf{#2}akuhf{#3}re"
print_r($nums); // ( [0] => 7834 [1] => 87 [2] => 4 [3] => 999 )
Is it possible?

$str = "Lnlhkjfs7834hfdhrf87whf4akuhf999re";//could be any string
$nums = array();
$count = 0;
$res = preg_replace_callback('/([0-9]+)/', function($match) use (&$count,&$nums) {
$nums[] = $match[0];
return "{#".($count++)."}";
}, $str);
print_r($str); // "Lnlhkjfs7834hfdhrf87whf4akuhf999re"
print_r($res); // "Lnlhkjfs{#0}hfdhrf{#1}whf{#2}akuhf{#3}re"
print_r($nums); // ( [0] => 7834 [1] => 87 [2] => 4 [3] => 999 )
After some little fixes it works. \d+ works too.
NOTE: Can not explain why global $nums; wont work. Maybe php internal issue/bug

Nothing to add to #JustOnUnderMillions answer, just an other way that avoids the callback function:
$nums = [];
$res = preg_split('~([0-9]+)~', $str, -1, PREG_SPLIT_DELIM_CAPTURE);
foreach ($res as $k => &$v) {
if ( $k & 1 ) {
$nums[] = $v;
$v = '{#' . ($k >> 1) . '}';
}
}
$res = implode('', $res);
Not shorter, but faster.

Related

How to get the ages that are greater than 50 in php

Need to get all the age keys and count the number of them greater than 50. Given data
$data = '{"data":"key=IAfpK, age=58, key=WNVdi, age=64, key=jp9zt, age=47"}';
Output should be 2 as there are 2 ages greater than 50
This is what I am trying
$array = json_decode($data, true);
$strArray = explode(',', $array['data']);
$temp1 = array();
for($i=0; $i < count($strArray); $i++){
$key_value = explode('=', $strArray[$i]);
$temp1[$i][$key_value[0]] = $key_value[1];
}
print "<pre>";
print_r($temp1);
print "</pre>";
Output is coming as
Array
(
[0] => Array
(
[key] => IAfpK
)
[1] => Array
(
[ age] => 58
)
[2] => Array
(
[ key] => WNVdi
)
[3] => Array
(
[ age] => 64
)
[4] => Array
(
[ key] => jp9zt
)
[5] => Array
(
[ age] => 47
)
)
Need to get all the ages in an array to compare.
If your data is truly semi-structured like that and isn’t too massive, and you want to keep the keys, I think this is a good case for array_shift to reduce the array until it is exhausted:
$data = '{"data":"key=IAfpK, age=58, key=WNVdi, age=64, key=jp9zt, age=47"}';
$array = json_decode($data, true);
$strArray = explode(',', $array['data']);
$clean = [];
while(count($strArray) >= 2){
$key = explode('=', array_shift($strArray))[1];
$age = explode('=', array_shift($strArray))[1];
$clean[$key] = (int)$age;
}
var_dump($clean);
Demo: https://3v4l.org/RIF2q
You should hopefully be able to add whatever logic you need to test ages then.
Short and simple. Hope this will work for you,
$output = [];
$data = '{"data":"key=IAfpK, age=58, key=WNVdi, age=64, key=jp9zt, age=47"}';
$array = json_decode($data, true);
foreach (explode(',',$array['data']) as $key => $value)
{
if(str_contains($value,'age'))
{
$age = explode('=',$value);
if($age[1] > 50)
{
array_push($output, $age[1]);
}
}
}
print_r($output);
An effective way to achieve this since your data is strangely formatted would be to use regex. For some reason, the ?: non capturing group is still being captured so all of the ages will be in the second capture group of the match array - you can play to optimise that.
Once you have all your ages in an array, a simple array_filter for ages larger than 50 is more than enough. You can see a live working example on 3v4l.org.
$data = '{"data":"key=IAfpK, age=58, key=WNVdi, age=64, key=jp9zt, age=47"}';
preg_match_all('/(?:age=)([0-9]+)/', json_decode($data)->data, $matches, PREG_PATTERN_ORDER);
// Only continue if we found any ages in the string
if (array_key_exists(1, $matches))
{
// 58, 64
$greaterThan50 = array_filter($matches[1], fn($age) => intval($age) > 50);
}
Ofc, if you then need the corresponding key, you will need to do more magic.
If you're using less than PHP 7.4, arrow functions are not supported. You can instead replace $greaterThan50 assignment to:
$greaterThan50 = array_filter($matches[1], function($age) {
return intval($age) > 50;
});
See it working live on 34vl.org
Based on your logic, You can do it as follows :
<?php
$data = '{"data":"key=IAfpK, age=58, key=WNVdi, age=64, key=jp9zt, age=47"}';
$array = json_decode($data, true);
$strArray = explode(',', $array['data']);
$keys = array();
$ages = array();
for($i=0; $i < count($strArray); $i++){
$key_value = explode('=', $strArray[$i]);
if ((int)$key_value[1] > 0 ) {
array_push($ages, $key_value[1]);
} else {
array_push($keys, $key_value[1]);
}
}
$result = array_combine($keys, $ages);
$result = array_filter($result, function($age) {
return intval($age) > 50;
});
print "<pre>";
print_r($result);
print "</pre>";

PHP group certain results from foreach on array into another array

I have an array that looks something like this:
$array = array( [0] => FILE-F01-E1-S01.pdf
[1] => FILE-F01-E1-S02.pdf
[2] => FILE-F01-E1-S03.pdf
[3] => FILE-F01-E1-S04.pdf
[4] => FILE-F01-E1-S05.pdf
[5] => FILE-F02-E1-S01.pdf
[6] => FILE-F02-E1-S02.pdf
[7] => FILE-F02-E1-S03.pdf );
Basically, I need to look at the first file and then get all the other files that have the same beginning ('FILE-F01-E1', for example) and put them into an array. I don't need to do anything with the other ones at this point.
I've been trying to use a foreach loop finding the previous value to do this, but am not having any luck.
Like this:
$previousFile = null;
foreach($array as $file)
{
if(substr_replace($previousFile, "", -8) == substr_replace($file, "", -8))
{
$secondArray[] = $file;
}
$previousFile = $file;
}
So then $secondArray would look like this:
Array ( [0] => FILE-F01-E1-S01.pdf [1] => FILE-F01-E1-S02.pdf
[2] => FILE-F01-E1-S03.pdf [3] => FILE-F01-E1-S04.pdf
[4] => FILE-F01-E1-S05.pdf)
As my result.
Thank you!
You can use array_filter combined with strpos:
$result = array_filter($array, function($filename) {
return strpos($filename, 'FILE-F01-E1') === 0;
});
Are you sure this will be the naming format? That is crucial information to have to construct a regexp or something to check for being a substring of the following strings.
If we can assume this and that the "base" name is always at index 0 then you could do something like.
<?php
$myArr = [
'FILE-F01-E1-S01.pdf',
'FILE-F01-E1-S02.pdf',
'FILE-F01-E1-S03.pdf',
'FILE-F01-E1-S04.pdf',
'FILE-F01-E1-S05.pdf',
'FILE-F02-E1-S01.pdf',
'FILE-F02-E1-S02.pdf',
'FILE-F02-E1-S03.pdf'
];
$baseName = '';
$allSimilarNames = [];
foreach($myArr as $index => &$name) {
if($index == 0) {
$baseName = substr($name, 0, strrpos($name, '-'));
$allSimilarNames[] = $name;
}
else {
if(strpos($name, $baseName) === 0) {
$allSimilarNames[] = $name;
}
}
}
var_dump($allSimilarNames);
This will
Check at index one to get the base name to compare against
Loop all items in the array and match all items, no matter where in the array they are, that are similar according to your naming convention
So if you next time have an array that is
$myArr = [
'FILE-F02-E1-S01.pdf',
'FILE-F01-E1-S01.pdf',
'FILE-F01-E1-S02.pdf',
'FILE-F01-E1-S03.pdf',
'FILE-F01-E1-S04.pdf',
'FILE-F01-E1-S05.pdf',
'FILE-F02-E1-S02.pdf',
'FILE-F02-E1-S03.pdf'
];
this will return all the items that match FILE-F02-E1*.
You could also make a small function of it for easier use and not have to rely on the element at index 0 having to be the "base" name.
<?php
function findMatches($baseName, &$names) {
$matches = [];
$baseName = substr($baseName, 0, strrpos($baseName, '-'));
foreach($names as &$name) {
if(strpos($name, $baseName) === 0) {
$matches[] = $name;
}
}
return $matches;
}
$myArr = [
'FILE-F01-E1-S01.pdf',
'FILE-F01-E1-S02.pdf',
'FILE-F01-E1-S03.pdf',
'FILE-F01-E1-S04.pdf',
'FILE-F01-E1-S05.pdf',
'FILE-F02-E1-S01.pdf',
'FILE-F02-E1-S02.pdf',
'FILE-F02-E1-S03.pdf'
];
$allSimilarNames = findMatches('FILE-F01-E1-S01.pdf', $myArr);
var_dump($allSimilarNames);
Run a simple foreach with strpos() which looks for an occurrence of a string within a string.
$results = array();
foreach($array as $item){
if (strpos($item, 'FILE-F01-E1') === 0) {
array_push($results, $item);
}
}
You could get the first item from the array and use explode and implode to get the part from the filename without the last hyphen and the content after that.
Then use array_filter and use substr using 0 as the start position and the length of the $fileBeginning as the length to check if the string starts with FILE-F01-E1:
$array = [
'FILE-F01-E1-S01.pdf',
'FILE-F01-E1-S02.pdf',
'FILE-F01-E1-S03.pdf',
'FILE-F01-E1-S04.pdf',
'FILE-F01-E1-S05.pdf',
'FILE-F02-E1-S01.pdf',
'FILE-F02-E1-S02.pdf',
'FILE-F02-E1-S03.pdf',
"TESTFILE-F01-E1-S03.pdf"
];
$parts = explode('-', $array[0]);
array_pop($parts);
$fileBeginning = implode('-', $parts);
$secondArray = array_filter($array, function ($x) use ($fileBeginning) {
return substr($x, 0, strlen($fileBeginning)) === $fileBeginning;
});
print_r($secondArray);
Result
Array
(
[0] => FILE-F01-E1-S01.pdf
[1] => FILE-F01-E1-S02.pdf
[2] => FILE-F01-E1-S03.pdf
[3] => FILE-F01-E1-S04.pdf
[4] => FILE-F01-E1-S05.pdf
)
Demo

Query string like parameters regex

From a text like:
category=[123,456,789], subcategories, id=579, not_in_category=[111,333]
I need a regex to get something like:
$params[category][0] = 123;
$params[category][1] = 456;
$params[category][2] = 789;
$params[subcategories] = ; // I just need to know that this exists
$params[id] = 579;
$params[not_category][0] = 111;
$params[not_category][1] = 333;
Thanks everyone for the help.
PS
As you suggested, I clarify that the structure and the number of items may change.
Basically the structure is:
key=value, key=value, key=value, ...
where value can be:
a single value (e.g. category=123 or postID=123 or mykey=myvalue, ...)
an "array" (e.g. category=[123,456,789])
a "boolean" where the TRUE value is an assumption from the fact that "key" exists in the array (e.g. subcategories)
This method should be flexible enough:
$str = 'category=[123,456,789], subcategories, id=579, not_in_category=[111,333]';
$str = preg_replace('#,([^0-9 ])#',', $1',$str); //fix for string format with no spaces (count=10,paginate,body_length=300)
preg_match_all('#(.+?)(,[^0-9]|$)#',$str,$sections); //get each section
$params = array();
foreach($sections[1] as $param)
{
list($key,$val) = explode('=',$param); //Put either side of the "=" into variables $key and $val
if(!is_null($val) && preg_match('#\[([0-9,]+)\]#',$val,$match)>0)
{
$val = explode(',',$match[1]); //turn the comma separated numbers into an array
}
$params[$key] = is_null($val) ? '' : $val;//Use blank string instead of NULL
}
echo '<pre>'.print_r($params,true).'</pre>';
var_dump(isset($params['subcategories']));
Output:
Array
(
[category] => Array
(
[0] => 123
[1] => 456
[2] => 789
)
[subcategories] =>
[id] => 579
[not_in_category] => Array
(
[0] => 111
[1] => 333
)
)
bool(true)
Alternate (no string manipulation before process):
$str = 'count=10,paginate,body_length=300,rawr=[1,2,3]';
preg_match_all('#(.+?)(,([^0-9,])|$)#',$str,$sections); //get each section
$params = array();
foreach($sections[1] as $k => $param)
{
list($key,$val) = explode('=',$param); //Put either side of the "=" into variables $key and $val
$key = isset($sections[3][$k-1]) ? trim($sections[3][$k-1]).$key : $key; //Fetch first character stolen by previous match
if(!is_null($val) && preg_match('#\[([0-9,]+)\]#',$val,$match)>0)
{
$val = explode(',',$match[1]); //turn the comma separated numbers into an array
}
$params[$key] = is_null($val) ? '' : $val;//Use blank string instead of NULL
}
echo '<pre>'.print_r($params,true).'</pre>';
Another alternate: full re-format of string before process for safety
$str = 'count=10,paginate,body_length=300,rawr=[1, 2,3] , name = mike';
$str = preg_replace(array('#\s+#','#,([^0-9 ])#'),array('',', $1'),$str); //fix for varying string formats
preg_match_all('#(.+?)(,[^0-9]|$)#',$str,$sections); //get each section
$params = array();
foreach($sections[1] as $param)
{
list($key,$val) = explode('=',$param); //Put either side of the "=" into variables $key and $val
if(!is_null($val) && preg_match('#\[([0-9,]+)\]#',$val,$match)>0)
{
$val = explode(',',$match[1]); //turn the comma separated numbers into an array
}
$params[$key] = is_null($val) ? '' : $val;//Use blank string instead of NULL
}
echo '<pre>'.print_r($params,true).'</pre>';
You can use JSON also, it's native in PHP : http://php.net/manual/fr/ref.json.php
It will be more easy ;)
<?php
$subject = "category=[123,456,789], subcategories, id=579, not_in_category=[111,333]";
$pattern = '/category=\[(.*?)\,(.*?)\,(.*?)\]\,\s(subcategories),\sid=(.*?)\,\snot_in_category=\[(.*?)\,(.*?)\]/';
preg_match($pattern, $subject, $matches, PREG_OFFSET_CAPTURE, 3);
print_r($matches);
?>
I think this will get you the matches out... didn't actually test it but it might be a good starting point.
Then you just need to push the matches to the correct place in the array you need. Also test if the subcategories string exists with strcmp or something...
Also, notice that I assumed your subject string has that fixe dtype of structure... if it is changing often, you'll need much more than this...
$str = 'category=[123,456,789], subcategories, id=579, not_in_category=[111,333]';
$main_arr = preg_split('/(,\s)+/', $str);
$params = array();
foreach( $main_arr as $value) {
$pos = strpos($value, '=');
if($pos === false) {
$params[$value] = null;
} else {
$index_part = substr($value, 0, $pos);
$value_part = substr($value, $pos+1, strlen($value));
$match = preg_match('/\[(.*?)\]/', $value_part,$xarr);
if($match) {
$inner_arr = preg_split('/(,)+/', $xarr[1]);
foreach($inner_arr as $v) {
$params[$index_part][] = $v;
}
} else {
$params[$index_part] = $value_part;
}
}
}
print_r( $params );
Output :
Array
(
[category] => Array
(
[0] => 123
[1] => 456
[2] => 789
)
[subcategories] =>
[id] => 579
[not_in_category] => Array
(
[0] => 111
[1] => 333
)
)

Print_r line bug ? \n?

I have this OUTPUT array from Decode function down:
Array ( [
] =>
[HostName] => Survival4fun
[GameType] => SMP
[Version] => 1.5.2
[Plugins] => Array
(
[0] => WorldEdit
)
[Map] => world
[Players] => 0
[MaxPlayers] => 10
[HostPort] => 25608
[HostIp] => 31.133.13.99
[RawPlugins] => WorldEdit5.5.6;
[Software] => CraftBukkitonBukkit1.5.2-R0.1
[Status] => online
[Ping] => 15ms
[
] =>
[PlayersOnline] => Array
(
[P0] => NoPlayers
)
[
] => )
And so, you can see this:
[
] =>
How can I remove it ? I tried using str_replace("\n", "", $arr); But this doesn't work.
Here is the original array - http://status.mc-host.cz/s8.mc-host.cz:25608-feed
And here is my function code:
Function Decode_query($link) {
$data = file($link, FILE_IGNORE_NEW_LINES);
$arr = array();
$string = array("[", "]", " ", "(", ")", "Array", "\n", "\r");
$replace = array("", "", "", "", "", "", "", "");
ForEach ($data as $line) {
$s = str_replace($string, $replace, $line);
If (Empty($s)) {} Else {
$stat = explode("=>", $s);
$P = str_replace("P", "", $stat[0]);
If (is_numeric($stat[0])) {
$arr["Plugins"][$stat[0]] = $stat[1];
}
ElseIf (is_numeric($P)) {
$arr['PlayersOnline'][$stat[0]] = $stat[1];
} Else {
$arr[$stat[0]] = $stat[1];
}
}
}
Return $arr;
}
$arr = Decode_query("http://status.mc-host.cz/s8.mc-host.cz:25608-feed");
Print_r($arr);
Thanks for help and sorry for long question..
You could use a regex to scan for keys that are composed of only whitespace:
$keys = array_keys($your_array);
$blank_keys = preg_grep('/^\s*$/', $keys);
foreach($blank_keys as $blank) {
unset($your_array[$blank]);
}
I would work with trim in stead of str_replace. It is less expensive, and it takes care of the trailing spaces and whatever whitespace there may be. In your case your function would probably look something like this:
Function Decode_query($link) {
// fetch the data
$data = file($link, FILE_IGNORE_NEW_LINES);
// prepare output array
$arr = array('Plugins' => array(), 'PlayersOnline' => array());
// prepare the list of characters we want to remove
$removeChars = ' \t\n\r[]';
ForEach ($data as $line) {
// split line into key, value
$stat = explode("=>", $line);
// no 2 elements, means no '=>', so ignore line
if (count($stat) < 2) continue;
// remove unwanted characters from key
$trimmed = trim($stat[0], $removeChars);
$pTrimmed = trim($trimmed, 'P');
// if key = plugins, ignore line
if ($trimmed == 'Plugins') continue;
// if key is numeric
If (is_numeric($trimmed)) {
// store in plugins subarray
$arr['Plugins'][$trimmed] = trim($stat[1]);
}
// if (key - P) is numeric
ElseIf (is_numeric($pTrimmed)) {
// store in players online subarray
$arr['PlayersOnline'][$pTrimmed] = trim($stat[1]);
} Else {
// all others store in level 1 array
$arr[$trimmed] = trim($stat[1]);
}
}
Return $arr;
}
I didn't test the code, but I think it should work fine.
PS: You can never put enough comments in your code, may seem a waste of time at first, but you, or anyone who has to work on your code, will be very grateful some day...

Translate a string with point separated into array keys

I have an string (like one.two.three or month.2) and I need to translate this string exploding point character into array keys.
So I have month.2 and I need the codeline to translate this string into $lang['month'][2]
I was looking for that solution but I am not able to find it, I am blocked.
I finally found this:
<?php
$lang['one']['two']['three'] = 'well done';
$str = 'one.two.three';
$list = explode('.', $str);
$result = '$lang';
foreach ($list as $item)
{
$result .= '["'.$item.'"]';
}
var_dump(eval("return " . $result.';'));
?>
Start by exploding it:
$in = explode('.', $in);
Then rewrite it as a nested array:
$arg = array();
foreach (array_reverse($in) as $key) {
$arg = array($key => $arg);
}
And finally merge:
$out = array_merge_recursive($arg, $out);
Edit: in case you're seeking to read the array instead, then bazmegakapa's answer is what you're looking for.
I wrote a little recursive function to handle this. First you explode the string, and pass the array ($lang in your example, $a in mine) and this exploded array to GetVal(). It will do its best to return the needed value into $x.
$s="month.2";
$keys=explode('.', $s);
$a=array(
'month' => array('1' => 'fos', '2' => 'fos2'),
'retek' => 1
);
function GetVal($array, $keyarray) {
$key=array_shift($keyarray);
if (array_key_exists($key, $array)) {
if (count($keyarray)==0) {
return $array[$key];
} else {
return GetVal($array[$key], $keyarray);
}
} else {
return null;
}
}
$x=GetVal($a, $keys);
var_dump($x);
Its a clarification for the use of explode function:
<?php
$str = "Hello world. It's a beautiful day.";
print_r (explode(" ",$str));
?>
this code explodes as:
Array
(
[0] => Hello
[1] => world.
[2] => It's
[3] => a
[4] => beautiful
[5] => day.
)
not as Array[hello][world][its][a].......

Categories