I'm trying to convert mib-style strings into PHP array indices. The trick is that I have to do this for a variable number of strings. As an example:
$strings = ['1.1.1' => 1, '1.1.2' => 2, '1.2.1' => 1];
# Given the above, generate the below:
$array = [ 1 => [ 1 => [1 => 1, 2 => 2] ], 2 => [1 => 1] ] ] ] ]
I can't think of a way to do it that isn't just a brute-force, inefficient method. Any helpful function/approach/advice is welcome.
You could take a recursive approach since the problem/result you provide seems to be of a recursive nature. (You can achieve the same result with a loop, applying the same logic as the recursive function ofcourse)
So under the assumption that there are no conflicting string inputs/edge cases what so ever, the following could be one approach:
Loop over all the strings and their values, break it up and create a nested structure by passing the result array by its reference.
function createNested($pieces, $currentIndex, &$previous, $value)
{
$index = $pieces[$currentIndex];
// Our base case: when we reached the final/deepest level of nesting.
// Hence when the we reached the final index.
if ($currentIndex == count($pieces) - 1) {
// Can now safely assign the value to index.
$previous[$index] = $value;
} else {
// Have to make sure we do not override the key/index.
if (!key_exists($index, $previous)) {
$previous[$index] = [];
}
// If the key already existed we can just make a new recursive call (note one level deeper as we pass the array that $previous[$index] points to.
createNested($pieces, $currentIndex + 1, $previous[$index], $value);
}
}
$strings = ['1.1.1' => 1, '1.1.2' => 2, '1.2.1' => 1];
$result = [];
foreach ($strings as $string => $value) {
// Break up the string by .
createNested(explode('.', $string), 0, $result, $value);
}
echo '<pre>';
print_r($result);
echo '</pre>';
Will output:
Array
(
[1] => Array
(
[1] => Array
(
[1] => 1
[2] => 2
)
[2] => Array
(
[1] => 1
)
)
)
Related
I have the following working code that from 2 separate arrays ($I & $f) creates final multidimension array with the data as associated columns.
The problem is i feel the code is clunky, but i cant see if, or how it could me improved. So I'm hoping a second pair of eyes can help.
<?php
//main array of input data
$i = [ 'input_tickettype1_storeno_00' => null,
'input_tickettype1_deliverydate_00' => null,
'input_tickettype1_ticketref_00' => null,
'input_tickettype1_storeno_01' => '9874',
'input_tickettype1_deliverydate_01' => '2022-02-01',
'input_tickettype1_ticketref_01' => 'EDN6547',
'input_tickettype1_storeno_02' => '8547',
'input_tickettype1_deliverydate_02' => '2022-01-31',
'input_tickettype1_ticketref_02' => 'EDN5473',
'input_tickettype1_storeno_03' => '9214',
'input_tickettype1_deliverydate_03' => '2022-02-28',
'input_tickettype1_ticketref_03' => 'EDN1073'
];
//headers
$h = [ 'input_tickettype1_storeno' ,
'input_tickettype1_deliverydate',
'input_tickettype1_ticketref'
];
//final multidim array
$f = array();
//Create a multidim for the headers and the values
foreach ($h as $k => $v)
{
$f[] = [$v=>null];
}
//loop throught the headers looping for matches in the input data
for ($x = 0; $x < count($f); $x++) {
foreach ($f[$x] as $fk => $fv) {
foreach ($i as $ik => $iv) {
if (str_contains($ik,$fk)) {
array_push($f[$x],$iv);
}
}
}
}
print_r($f);
//Actual Working Output
// Array (
// [0] => Array ( [input_tickettype1_storeno] =>
// [0] =>
// [1] => 9874
// [2] => 8547
// [3] => 9214
// )
// [1] => Array ( [input_tickettype1_deliverydate] =>
// [0] =>
// [1] => 2022-02-01
// [2] => 2022-01-31
// [3] => 2022-02-28
// )
// [2] => Array ( [input_tickettype1_ticketref] =>
// [0] =>
// [1] => EDN6547
// [2] => EDN5473
// [3] => EDN1073
// )
// )
?>
Yes, indeed I think the code can be optimised for readability and logic.
I can think of two methods you can use.
Method 1 : nested foreach
First of all, you don't need a foreach to initialize your multidimentional array, you can do it within the main loop you use to read the data. So you can remove the foreach -- $f[] = [$v=>null];
Then, instead of having 1 for and 2 foreach you can just have 2 foreach one for each array and use a very fast strpos to identify if the key matches and populate the final array.
Here's the resulting code.
$f = [];
foreach ($h as $prefix) {
$f[$prefix] = [];
foreach ($i as $key => $val) {
if (strpos($key, $prefix) === 0) {
$f[$prefix][] = $val;
}
}
}
This first method is simple, with a straightforward logic. However it requires a nested foreach. Which means that if both arrays get larger, your code gets much slower.
Method 2 : key manipulation
This method assumes that the keys of the first array never change structure and they are always somestringid_[digits]
In this case we can avoid looping the second array and just use a regular expression to recreate the key.
$f = [];
foreach ($i as $key => $value) {
preg_match('/^(.*)_[0-9]+$/', $key, $m);
$key = $m[1];
if (empty($f[$m[1]])) {
$f[$m[1]] = [];
}
$f[$m[1]][] = $value;
}
I don't see any need to implement any additional data or conditions. You only need to read your main array, mutate the keys (trim the trailing unique identifiers) as you iterate, and push data into their relative groups.
Code: (Demo)
$result = [];
foreach ($array as $k => $v) {
$result[preg_replace('/_\d+$/', '', $k)][] = $v;
}
var_export($result);
Output:
array (
'input_tickettype1_storeno' =>
array (
0 => NULL,
1 => '9874',
2 => '8547',
3 => '9214',
),
'input_tickettype1_deliverydate' =>
array (
0 => NULL,
1 => '2022-02-01',
2 => '2022-01-31',
3 => '2022-02-28',
),
'input_tickettype1_ticketref' =>
array (
0 => NULL,
1 => 'EDN6547',
2 => 'EDN5473',
3 => 'EDN1073',
),
)
I have these two arrays:
$list = [
'fruit' => [],
'animals' => [],
'objects' => [],
];
$dataArray = [
'fruit' => 'apple',
'animals' => ['dog', 'cat'],
'asd' => 'bla'
];
I want to merge them so that $list at the end is:
[fruit] => Array
(
[0] => apple
)
[animals] => Array
(
[0] => dog
[1] => cat
)
[objects] => Array
(
)
so, things to pay attention to:
even if 'fruit' had only one element, is still an array in $list
keys missing from $list ('asd' key) are simply ignored
keys with no values are still kept, even if empty
Using array_merge doesn't work:
$merged = array_merge($list, $dataArray);
[fruit] => apple
[animals] => Array
(
[0] => dog
[1] => cat
)
[objects] => Array
(
)
[asd] => bla
I managed to get what I want with this:
foreach ($dataArray as $key => $value) {
if (isset($list[$key])) {
if (is_array($value)) {
$list[$key] = $value;
}
else {
$list[$key] = [$value];
}
}
}
But I was wondering if there was a cleaner way to do it or some other php function that I'm not aware of.
You can achieve this in a couple of steps:
$result = array_intersect_key($dataArray, $list);
This will return you an array containing all the elements that are in the first array, and then filter against the keys in the second. This gives you:
Array
(
[fruit] => apple
[animals] => Array
(
[0] => dog
[1] => cat
)
)
You can then re-add the missing keys from the first array, using:
$result + $list;
The difference between the + operator and array_merge is that the former does not overwrite the first array using the values from the second. It will just add any missing elements.
In one line, this works out as:
$result = array_intersect_key($dataArray, $list) + $list;
You can see it in action here: https://eval.in/865733
EDIT: Sorry, completely missed the part about keeping the elements as arrays. If they'll always be arrays, then you can add a quick one-liner like:
$result = array_map(function ($e) { return (array) $e; }, $result);
Which will cast all top-level elements to an array. If your logic is more complex than that, then I'm not sure you'll find a way to do it with in-built functions, sorry.
The second rule ("2. keys missing from $list ('asd' key) are simply ignored") tells me to iterate over $list, not over $dataArray. If $dataArray is much bigger than $list, iterating over it is a waste of time as most of its elements are simply ignored.
Your rules do not explain how to process the elements of $list when they are not empty (I'll assume they are always arrays, otherwise the game changes and it becomes too complicated to provide generic code to handle it).
The code I suggest looks like this:
// Build the result in $result
// (you can modify $list as well if you prefer it that way)
$result = array();
// 2. keys missing from $list ('asd' key) are simply ignored
// iterate over the keys of $list, handle only the keys present in $dataArray
foreach ($list as $key => $value) {
if (array_key_exists($dataArray, $key)) {
// $key is present in $dataArray, use the value from $dataArray
$value = $dataArray[$key];
// 1. even if 'fruit' had only one element, is still an array in $list
if (! is_array($value)) {
$value = array($value);
}
}
// 3. keys with no values are still kept, even if empty
// if the key is not present in $dataArray, its value from $list is used
$result[$key] = $value;
}
If you move the if (! is_array($value)) block outside the if (array_key_exists()) block it will convert to arrays also the values of $list that are not arrays and are associated with keys that are not present in $dataArray (e.g $list['objects']). This way, after the code runs, all values of $result are arrays.
Your code is good as well. Apart from iterating over $list and not over $dataArray, there is no way to make it faster or easier to read in a spectacular manner. The code I suggest here is just another way to write the same thing.
Here is two lines solution ( but you can simply make it one line:) )
$list = [
'fruit' => [],
'animals' => [],
'objects' => [],
];
$dataArray = [
'fruit' => 'apple',
'animals' => ['dog', 'cat'],
'asd' => 'bla'
];
$list = array_merge($list, array_intersect_key($dataArray, $list));
array_walk($list, function(&$item){$item = (array)$item;});
My approach will do 3 very simple operations:
Run a single loop while modifying $list by reference using &$v.
Ignore iterations where a matching key is not found in $dataArray.
Hard-cast each qualifying value from $dataArray as an array and overwrite the initial empty subarray.
If the qualifying value is not an array, it will become the lone element in the generated subarray; if it is, then nothing about the subarray is changed.
Code: (Demo)
$list = [
'fruit' => [],
'animals' => [],
'objects' => []
];
$dataArray = [
'fruit' => 'apple',
'animals' => ['dog', 'cat'],
'asd' => 'bla'
];
foreach ($list as $k => &$v) {
if (isset($dataArray[$k])) {
$v = (array)$dataArray[$k];
}
}
var_export($list);
Output:
array (
'fruit' =>
array (
0 => 'apple',
),
'animals' =>
array (
0 => 'dog',
1 => 'cat',
),
'objects' =>
array (
),
)
I'm probably [super]overthinking this. I'm trying to analyze an array with values like [1,9], [4,6] [5,5], [6,4], [9,1] and duplicate digits (I'm having a super brain fart and can't even remember the term for numbers like this) remove (the last two) so that only [1,9], [4,6] [5,5] are printed.
I was thinking that turning this array into a string and using preg_match, but I'm pretty sure this wouldn't work even if I had the correct regex.
If you have an array of pairs like this:
$x = array(
array(1,9),
array(4,6),
array(5,5),
array(6,4),
array(9,1)
);
Here is one way to get the unique pairs:
foreach ($x as $pair) {
sort($pair);
$unique_pairs[implode(',', $pair)] = $pair;
}
This uses string representations of each sorted pair as keys in a new array, so the result will have distinct values by definition.
As far as the printing them out part of your question, once you have the unique values you can loop over them and print them out in whichever format you like, for example:
foreach ($unique_pairs as $pair) { vprintf("[%d,%d]<br>", $pair); }
It looks like elements are distributed symmetrically.
We can cut the array in two halves and get only the first half with array_slice():
$array = array(
array(1,9),
array(4,6),
array(5,5),
array(6,4),
array(9,1),
);
print_r(array_slice($array, 0, ceil(count($array) / 2)));
Result:
Array(
[0] => Array(
[0] => 1
[1] => 9
)
[1] => Array(
[0] => 4
[1] => 6
)
[2] => Array(
[0] => 5
[1] => 5
)
)
Demo at Codepad.
ceil() is used to round the number up to the next highest integer if there is an even number of items in the array. Example: if there is 3 items in the array, 5 / 2 will return 2.5, we want 3 items so we use ceil(2.5) which gives 3.
Example with 3 items:
$array = array(
array(1,9),
array(5,5),
array(9,1),
);
print_r(array_slice($array, 0, ceil(count($array) / 2)));
Result:
Array(
[0] => Array(
[0] => 1
[1] => 9
)
[1] => Array(
[0] => 5
[1] => 5
)
)
Example with 4 items:
$array = array(
array(1,9),
array(7,7),
array(7,7),
array(9,1),
);
print_r(array_slice($array, 0, ceil(count($array) / 2)));
Result:
Array(
[0] => Array(
[0] => 1
[1] => 9
)
[1] => Array(
[0] => 7
[1] => 7
)
)
If I'm correct in understanding what you are trying to do, you want to remove the final 2 elements from the array?
There is a function in PHP called array_pop that removes the final element from the array.
$array = array_pop($array);
So if you run this twice, you will remove the final 2 elements from the array.
This is how I'd do it (and I hope I am not overthinking this :))
$stringArray = array();
$stringArray[] = '1,9';
$stringArray[] = '4,6';
$stringArray[] = '5,5';
$stringArray[] = '6,4';
$stringArray[] = '9,1';
foreach($stringArray as &$numString) {
$numString = explode(',', $numString);
usort($numString, function($a, $b) {return $a - $b;});
$numString = implode(',', $numString);
}
$a = array_unique($a);
print_r($a);
You basically explode every element into a subarray, sort it and then implode it back. After calling the array_unique, you're left with unique values in the array.
The output would be
Array
(
[0] => 1,9
[1] => 4,6
[2] => 5,5
)
The result you suggest treats [a,b] as equivalent to [b,a] which makes the problem a lot more complex. The code below gives the result you asked for, but without really understanding what the problem is that you are trying to fix and whether [1,9] is equivalent to [9,1] in the solution:
$a=array(array(1,9),array(4,6),...
$dup=array();
for ($i=0; $i<count($a) -1; $i++) {
for ($j=$i+1; $j<count($a); $j++) {
if (($a[$i][0]==$a[$j[0] && $a[$i][1]==$a[$j[1])
|| ($a[$i][0]==$a[$j[1] && $a[$i][1]==$a[$j[0])) {
$dup[]=$j;
}
}
}
foreach ($dup as $i) {
unset($a[$i]);
}
So I'm actually going to assume your question to have a different meaning than everyone else did. I believe what you're asking is:
How do you filter out array items where a reverse of the item has already been used?
<?php
// The example set you gave
$numberSets = [[1, 9], [4, 6], [5, 5], [6, 4], [9, 1]];
// Initialize an empty array to keep track of what we've seen
$keys = [];
// We use array filter to get rid of items we don't want
// (Notice that we use & on $keys, so that we can update the variable in the global scope)
$numberSets = array_filter($numberSets, function($set) use(&$keys) {
// Reverse the array
$set = array_reverse($set);
// Create a string of the items
$key = implode('', $set);
// Get the reverse of the numbers
$reversedKey = strrev($key);
// If the palindrome of our string was used, return false to filter
if (isset($keys[$reversedKey])) {
return false;
}
// Set the key so it's not used again
// Since $keys is being passed by reference it is updated in global scope
$keys[$key] = true;
// Return true to NOT filter this item, since it or it's reverse were not matched
return true;
});
var_dump($numberSets);
The question is simple, let's suppose that I have an array like:
$array = array(array('bla1' => 'bla1'), array('bla2' => 'bla2'),
array('bla3' => 'bla3'), array('bla4' => 'bla4'));
Yeah it's multi-dimensional so I need to set value between e.g. array('bla1' => 'bla1') and array('bla2' => 'bla2') without erasure.
I was puzzled to find, through all array_ like functions in php how to do this.
So, as any other programmer would do, I wrote the function:
function setArrVal($array, $key, $val) {
for ($i = count($array) - 1; $i >= $key; $i--) {
$array[$i + 1] = $array[$i];
}
$array[$key] = $val;
return $array;
}
Works well.
But still need to do this with php function, is there any way to complete this that way?
I've tried array_splice($input, 1, 0, $replacement); - worthless, it's only working with simple values, not with arrays in array.
You can use array_splice() like so:
$array = array(array('bla1' => 'bla1'), array('bla2' => 'bla2'),
array('bla3' => 'bla3'), array('bla4' => 'bla4'));
$insert = array(array('bla2.5' => 'bla2.5'));
// note the third argument argument is 0
// this will prevent it from overwriting existing values
array_splice($array, 2, 0, $insert );
print_r($array);
/* Result */
Array
(
[0] => Array
(
[bla1] => bla1
)
[1] => Array
(
[bla2] => bla2
)
[2] => Array
(
[bla2.5] => bla2.5
)
[3] => Array
(
[bla3] => bla3
)
[4] => Array
(
[bla4] => bla4
)
)
Demo: http://codepad.org/ivBmZRdn
Is this what you wanted?
my php array looks like this:
Array (
[0] => dummy
[1] => stdClass Object (
[aid] => 1
[atitle] => Ameya R. Kadam )
[2] => stdClass Object (
[aid] => 2
[atitle] => Amritpal Singh )
[3] => stdClass Object (
[aid] => 3
[atitle] => Anwar Syed )
[4] => stdClass Object (
[aid] => 4
[atitle] => Aratrika )
) )
now i want to echo the values inside [atitle].
to be specific i want to implode values of atitle into another variable.
how can i make it happen?
With PHP 5.3:
$result = array_map(function($element) { return $element->atitle; }, $array);
if you don't have 5.3 you have to make the anonymous function a regular one and provide the name as string.
Above I missed the part about the empty element, using this approach this could be solved using array_filter:
$array = array_filter($array, function($element) { return is_object($element); });
$result = array_map(function($element) { return $element->atitle; }, $array);
If you are crazy you could write this in one line ...
Your array is declared a bit like this :
(Well, you're probably, in your real case, getting your data from a database or something like that -- but this should be ok, here, to test)
$arr = array(
'dummy',
(object)array('aid' => 1, 'atitle' => 'Ameya R. Kadam'),
(object)array('aid' => 2, 'atitle' => 'Amritpal Singh'),
(object)array('aid' => 3, 'atitle' => 'Anwar Syed'),
(object)array('aid' => 4, 'atitle' => 'Aratrika'),
);
Which means you can extract all the titles to an array, looping over your initial array (excluding the first element, and using the atitle property of each object) :
$titles = array();
$num = count($arr);
for ($i=1 ; $i<$num ; $i++) {
$titles[] = $arr[$i]->atitle;
}
var_dump($titles);
This will get you an array like this one :
array
0 => string 'Ameya R. Kadam' (length=14)
1 => string 'Amritpal Singh' (length=14)
2 => string 'Anwar Syed' (length=10)
3 => string 'Aratrika' (length=8)
And you can now implode all this to a string :
echo implode(', ', $titles);
And you'll get :
Ameya R. Kadam, Amritpal Singh, Anwar Syed, Aratrika
foreach($array as $item){
if(is_object($item) && isset($item->atitle)){
echo $item->atitle;
}
}
to get them into an Array you'd just need to do:
$resultArray = array();
foreach($array as $item){
if(is_object($item) && isset($item->atitle)){
$resultArray[] = $item->atitle;
}
}
Then resultArray is an array of all the atitles
Then you can output as you'd wish
$output = implode(', ', $resultArray);
What you have there is an object.
You can access [atitle] via
$array[1]->atitle;
If you want to check for the existence of title before output, you could use:
// generates the title string from all found titles
$str = '';
foreach ($array AS $k => $v) {
if (isset($v->title)) {
$str .= $v->title;
}
}
echo $str;
If you wanted these in an array it's just a quick switch of storage methods:
// generates the title string from all found titles
$arr = array();
foreach ($array AS $k => $v) {
if (isset($v->title)) {
$arr[] = $v->title;
}
}
echo implode(', ', $arr);
stdClass requires you to use the pointer notation -> for referencing whereas arrays require you to reference them by index, i.e. [4]. You can reference these like:
$array[0]
$array[1]->aid
$array[1]->atitle
$array[2]->aid
$array[2]->atitle
// etc, etc.
$yourArray = array(); //array from above
$atitleArray = array();
foreach($yourArray as $obj){
if(is_object($obj)){
$atitleArray[] = $obj->aTitle;
}
}
seeing as how not every element of your array is an object, you'll need to check for that.