Group data by multiple fields dynamically in PHP - php

I want to group an associative array by fields. The array itself is originally from a mysql database query.
Below is an example of how I do it by hard coding it:
<?php
$fields = array("ID,subID");
$fieldCounts = count($fields);
$data = array(); //there is sql querieed data
$parsedData = array();
foreach ($data as $val)
{
if ($fieldCounts == 1)
{
$f0 = $fields[0];
$fv0 = $val[$f0];
$parsedData[$fv0][] = $val;
}
else if ($fieldCounts == 2)
{
$f0 = $fields[0];
$fv0 = $val[$f0];
$f1 = $fields[10];
$fv1 = $val[$f1];
$parsedData[$fv0][$f1][] = $val;
}
else
{
exit("Third field not implemented");
}
}
?>
But how can I do it dynamically with an arbitrary number of fields?

Am not sure how this code has worked for you but some things are that wrong and might not allow the code to function properly
Fields has only as one valued with ,
$fields = array("ID,subID");
^----------- between string
Instead of
$fields = array("ID","subID");
Notice: Undefined offset:
$f1 = $fields[10];
^----- your array is not up to 10
Since you did not put your generate data and desired output. I would assume your final output and generate some temporary data
$fields = array("ID","subID"); //You can Increase or decrease this Fields
$fieldCounts = count($fields);
$data = array(); // there is sql querieed data
for($i = 0; $i < 3; $i ++) {
$data[] = array("ID" => mt_rand(1, 1000),"subID" => "sub" . mt_rand(100, 900));
}
Ruining your code with the 2 corrections above
foreach ( $data as $val ) {
if ($fieldCounts == 1) {
$f0 = $fields[0];
$fv0 = $val[$f0];
$parsedData[$fv0][] = $val;
} else if ($fieldCounts == 2) {
$f0 = $fields[0];
$fv0 = $val[$f0];
$f1 = $fields[1];
$fv1 = $val[$f1];
$parsedData[$fv0][$f1][] = $val;
} else {
exit("Third field not implemented");
}
}
Output
Array
(
[159] => Array
(
[subID] => Array <----------- SubID is fixed in your can cause confict
(
[0] => Array
(
[ID] => 159
[subID] => sub589
)
)
)
[334] => Array
(
[subID] => Array
(
[0] => Array
(
[ID] => 334
[subID] => sub703
)
)
)
)
A better Alternative to yours
$parsedData = array();
foreach ( $data as $val ) {
$temp = &$parsedData;
foreach ( array_slice($val, 0, $fieldCounts) as $key ) {
$temp = &$temp[$key];
}
$temp[] = $val;
}
print_r($parsedData);
Output
Array
(
[159] => Array
(
[sub589] => Array <---------- Make Sub ID Dynamic
(
[0] => Array
(
[ID] => 159
[subID] => sub589
)
)
)
[334] => Array
(
[sub703] => Array
(
[0] => Array
(
[ID] => 334
[subID] => sub703
)
)
)
)
Recommended Version For easy array path
$parsedData = array();
foreach ( $data as $val ) {
$temp = &$parsedData;
foreach ( array_slice($val, 0, $fieldCounts) as $key ) {
$temp = &$temp[$key];
}
$temp = $val;
}
print_r($parsedData);
Output
Array
(
[159] => Array
(
[sub589] => Array <---- Easy to asses as $parsedData['159']['sub589']
(
[ID] => 159
[subID] => sub589
)
)
[334] => Array
(
[sub703] => Array
(
[ID] => 334
[subID] => sub703
)
)
)

Instead of doing if/elseif/else inside your $data foreach-loop (which is always limited to the number you "write" in there with that structure and a lot of code-duplicateion) you need to turn that if/elseif/else into a loop of it's own.
But first of all transform the existing code, I start in the first if body, it contains already all code necessary:
$f0 = $fields[0];
$fv0 = $val[$f0];
$parsedData[$fv0][] = $val;
The $val should be assigned to the array $parsedData which is keyed by $fields name $value. Let's compress this here, the number 0 in names is superfluous as we don't want it any longer (but maybe the first):
$field = $fields[0];
$value = $values[$field];
$parsedData[$value][] = $values;
(I changed $val into $values to improve naming). This is now more easy to read and understand. Also we spot the magic number 0 here more easily.
Now to the magic. We want to add to an array here (push):
$parsedData[$value][] = $values;
To make this more easy, let's turn it this way:
$array = &$parsedData[$value];
$array[] = $values;
This right now seems superfluous, but when this turns into a loop, it will become more clear:
$array = &$parsedData;
...
$array = &array[$value];
...
$array[] = $values;
Let's review the code in with the outer loop at this moment:
foreach ($data as $values)
{
$array = &$parsedData;
$field = $fields[0];
$value = $values[$field];
$array = &$array[$value];
$array[] = $values;
}
Obviously this code is yet not complete. The inner-loop is missing but it starts to get some kind of body. And actually the inner loop is pretty simple to achieve:
$array = &$parsedData;
foreach ($fields as $field)
{
$value = $values[$field];
$array = &$array[$value];
}
$array[] = $values;
And that's already it. The single field has been turned into an iteration over all fields. The aliasing/referencing of the sub-array per each step in the iteration allows to push the value to the appropriate array entry after the inner loop has finished.
The whole outer and inner loop:
foreach ($data as $values)
{
$array = &$parsedData; # set reference
foreach ($fields as $field)
{
$value = $values[$field];
$array = &$array[$value];
}
$array[] = $values;
unset($array); # remove reference
}

Related

Get only Numeric values from Array in PHP

I have an array that looks something like this:
Array (
[0] => Array ( [country_percentage] => 5 %North America )
[1] => Array ( [country_percentage] => 0 %Latin America )
)
I want only numeric values from above array. I want my final array like this
Array (
[0] => Array ( [country_percentage] => 5)
[1] => Array ( [country_percentage] => 0)
)
How I achieve this using PHP?? Thanks in advance...
When the number is in first position you can int cast it like so:
$newArray = [];
foreach($array => $value) {
$newArray[] = (int)$value;
}
I guess you can loop the 2 dimensional array and use a preg_replace, i.e.:
for($i=0; $i < count($arrays); $i++){
$arrays[$i]['country_percentage'] = preg_replace( '/[^\d]/', '', $arrays[$i]['country_percentage'] );
}
Ideone Demo
Update Based on your comment:
for($i=0; $i < count($arrays); $i++){
if( preg_match( '/North America/', $arrays[$i]['country_percentage'] )){
echo preg_replace( '/[^\d]/', '', $arrays[$i]['country_percentage'] );
}
}
Try this:
$arr = array(array('country_percentage' => '5 %North America'),array("country_percentage"=>"0 %Latin America"));
$result = array();
foreach($arr as $array) {
$int = filter_var($array['country_percentage'], FILTER_SANITIZE_NUMBER_INT);
$result[] = array('country_percentage' => $int);
}
Try this one:-
$arr =[['country_percentage' => '5 %North America'],
['country_percentage' => '0 %Latin America']];
$res = [];
foreach ($arr as $key => $val) {
$res[]['country_percentage'] = (int)$val['country_percentage'];
}
echo '<pre>'; print_r($res);
output:-
Array
(
[0] => Array
(
[country_percentage] => 5
)
[1] => Array
(
[country_percentage] => 0
)
)
You can use array_walk_recursive to do away with the loop,
passing the first parameter of the callback as a reference to modify the initial array value.
Then just apply either filter_var or intval as already mentioned the other answers.
$array = [
["country_percentage" => "5 %North America"],
["country_percentage" => "0 %Latin America"]
];
array_walk_recursive($array, function(&$value,$key){
$value = filter_var($value,FILTER_SANITIZE_NUMBER_INT);
// or
$value = intval($value);
});
print_r($array);
Will output
Array
(
[0] => Array
(
[country_percentage] => 5
)
[1] => Array
(
[country_percentage] => 0
)
)
You could get all nemeric values by looping through the array. However I don't think this is the most efficient and good looking answer, I'll post it anyways.
// Array to hold just the numbers
$newArray = array();
// Loop through array
foreach ($array as $key => $value) {
// Check if the value is numeric
if (is_numeric($value)) {
$newArray[$key] = $value;
}
}
I missunderstood your question.
$newArray = array();
foreach ($array as $key => $value) {
foreach ($value as $subkey => $subvalue) {
$subvalue = trim(current(explode('%', $subvalue)));
$newArray[$key] = array($subkey => $subvalue);
}
}
If you want all but numeric values :
$array[] = array("country_percentage"=>"5 %North America");
$array[] = array("country_percentage"=>"3 %Latin America");
$newArray = [];
foreach ($array as $arr){
foreach($arr as $key1=>$arr1) {
$newArray[][$key1] = intval($arr1);
}
}
echo "<pre>";
print_R($newArray);
This is kind of a ghetto method to doing it cause I love using not as many pre made functions as possible. But this should work for you :D
$array = array('jack', 2, 5, 'gday!');
$new = array();
foreach ($array as $item) {
// IF Is numeric (each item from the array) will insert into new array called $new.
if (is_numeric($item)) { array_push($new, $item); }
}

Concat values of array with same key

I want to concat values of array with same key
Example:
[0] => Array
(
[0] => A
[1] => XYZ
)
[1] => Array
(
[0] => B
[1] => ABC
)
[2] => Array
(
[0] => A
[1] => LMN
)
[3] => Array
(
[0] => B
[1] => PQR
)
)
Expected output:
[0] => Array
(
[0] => A
[1] => XYZ,LMN
)
[1] => Array
(
[0] => B
[1] => ABC,PQR
)
)
A simple solution uses the PHP function array_reduce():
// The input array you posted in the question
$input = array(
array('A', 'XYZ'),
array('B', 'ABC'),
array('A', 'LMN'),
array('B', 'PQR'),
);
// Reduce the array to a new array that contains the data aggregated as you need
$output = array_reduce(
// Process each $item from $input using a callback function
$input,
// The callback function processes $item; the partial result is $carry
function (array $carry, array $item) {
// Extract the key into a variable
$key = $item[0];
// If the key was encountered before
// then a partial entry already exists in $carry
if (isset($carry[$key])) {
// Append the desired value to the existing entry
$carry[$key][1] .= ','.$item[1];
} else {
// Create a new entry in $carry (copy $item to key $key for quick retrieval)
$carry[$key] = $item;
}
// Return the updated $carry
return $carry;
},
// Start with an empty array (it is known as $carry in the callback function)
array()
);
// $output contains the array you need
Try this:
$final = array();
foreach ($array_items as $item)
{
$key = $item[0];
$found_index = -1;
for ($i=0; $i<count($final); $i++)
{
if ($key == $final[$i][0])
{
$found_index = $i;
break;
}
}
if ($found_index == -1)
{
$final_item = array();
$final_item[0] = $key;
$final_item[1] = $item[1];
$final[] = $final_item;
}
else
{
$final[$found_index][1] .= ",".$item[1];
}
}
We create a new array $final, and loop through your old array $array_items. For each item, we see if there is already an item in $final that has the same [0] index. If it doesn't exist, we create it and add the initial string to the [1] index. If it does exist, we just have to add the string onto the end of the [1] index.
Try it, substituting $array_items for whatever your array is called, let me know if it works.
Check my solution. It should work fine. I hope it will help you much.
$result = $passed_keys = $extended_arr = [];
foreach ($arr as $k => $value) {
for($i = $k + 1; $i < count($arr); $i++){
if ( $value[0] == $arr[$i][0] ){ // compare each array with rest subsequent arrays
$key_name = $value[0];
if (!array_key_exists($key_name, $result)){
$result[$key_name] = $value[1] .",". $arr[$i][1];
} else {
if (!in_array($i, $passed_keys[$key_name])) {
$result[$key_name] .= ",". $arr[$i][1];
}
}
$passed_keys[$key_name][] = $i; // memorizing keys that were passed
}
}
}
array_walk($result, function($v, $k) use(&$extended_arr){
$extended_arr[] = [$k, $v];
});
The result is in $extended_arr
My solution, creates a custom key which makes identifying the letter much easier. This removes the need to continuously iterate through each array, which can become a major resources hog.
<?php
$inital_array = array(
array('A','XYZ'),
array('B','ABC'),
array('A','LMN'),
array('B','PQR')
);
$concat_array = array();
foreach($inital_array as $a){
$key = $a[0];
if( !isset($concat_array[$key]) ){
$concat_array[$key] = array($key,'');
}
$concat_array[$key][1] .= (empty($concat_array[$key][1]) ? '' : ',').$a[1];
}
$concat_array = array_values($concat_array);
echo '<pre>',print_r($concat_array),'</pre>';

Get path from adjacency list data

I have an array (data from adjacency table) and it looks like:
Array
(
[0] => Array
(
[id] => 1
[name] => Anniversary
[parent] => 0
)
[1] => Array
(
[id] => 12
[name] => New arrives
[parent] => 1
)
[2] => Array
(
[id] => 13
[name] => Discount
[parent] => 12
)
[3] => Array
(
[id] => 6
[name] => Birthday
[parent] => 0
)
)
And I'm looking for the way to retrieve my path by ID;
For example: getPath(13): Anniversary->New arrives->Discount;
For example: getPath(12): Anniversary->New arrives;
For example: getPath(1): Anniversary;
For example: getPath(6): Birthday;
How can I do this?
Thanks!
function getpath($id, $arr, $level = 0) {
$result = array();
foreach($arr as $key => $value){
if($id == $value['id']){
$result[] = $value['name'];
$id = $value['parent'];
if($id != 0){
$result = array_merge($result, getpath($id, $arr, $level+1));
}else{
break;
}
}
}
return $level ? $result : implode('->',array_reverse($result));
}
echo getpath(13,$arr);
Consider this array,
$input = [
['id'=>1, 'name'=>'Anniversary', 'parent'=>0],
['id'=>12, 'name'=>'New arrives', 'parent'=>1],
['id'=>13, 'name'=>'Discount', 'parent'=>12],
['id'=>6, 'name'=>'Birthday', 'parent'=>0]
];
and this function,
function path($element_id, $input, $ids = [])
{
if(!$ids) // for performance, make this once and pass it around
{
$ids = array_column($input, 'id'); // array containing only 'id's of $input
}
$current_key = array_search($element_id, $ids); // search for $input variable's current key
unset($ids[$current_key]); // unsetting used keys to make above array search faster next time
$current_element = $input[$current_key]; // get current element as array from $input
$names[] = $current_element['name']; // create an array containing current element
if($current_element['parent'] != 0) // check if current element have parent
{
$names[] = path($current_element['parent'], $input, $ids); // call this function, let it return string, append it to $names
}
return implode(' ⟶ ', array_reverse($names)); // make final return, seprate by ⟶
}
Reading echo path(13, $input); will return
Anniversary ⟶ New arrives ⟶ Discount
Here is minified version of the same function
function path($a,$b,$c=[]){if(!$c){$c=array_column($b,'id');}$d=array_search($a,$c);unset($c[$d]);$e=$b[$d];$f[]=$e['name'];if($e['parent']!=0){$f[]=path($e['parent'],$b,$c);}return implode(' ⟶ ',array_reverse($f));}
Thanks to code reviewers Loufylouf and Quill
$find = 13;
$path = array();
function FindById ($arr, $find) {
$k = null;
foreach($arr as $key => $item)
if ($item['id'] == $find)
{ $k = $key; break; }
return $k;
}
if ( false === ($k = FindById($arr, $find))) die("not found");
while (true) {
array_unshift($path, $arr[$k]['name']);
if( ! $arr[$k]['parent']) break;
if(false === ($k = FindById($arr, $arr[$k]['parent']))) die("illegal structure");
}
echo implode('->', $path);

Nested for each's looping through multi-dimensional array - how to do a conditional on the last value

After a rollercoaster ride, I am "this" close to finalising a script I have been working on.
I have a multi-dimensional array stored in $newarray as below. I have built this array myself so the code to build it can be changed if needs be. But after creation I then loop through it picking out the values I want. I build a new array for each key in the upper array (3 in this case, 111, 222 & 333) and populate each with a a bunch of data objects from next array key down along with some other data.
However, what I need in the case below is to generate each of the 3 arrays (111, 222, 333) twice, once where the final array value = 0 ($the_action) and once where it = '1'. Where it = 1, print it, else where it = 0, do something else.
I also think that the way I loop through arrays with a single value in it is probably not very efficient, and the same goes for using key names as values.
Grateful for any help.
Array
(
[111] => Array
(
[1234] => Array
(
[100000] => Array
(
[20000] => 0
)
)
[1244] => Array
(
[100001] => Array
(
[20001] => 1
)
)
[1255] => Array
(
[100002] => Array
(
[20002] => 1
)
)
)
[222] => Array
(
[1233] => Array
(
[100013] => Array
(
[20013] => 0
)
)
[1241] => Array
(
[100014] => Array
(
[20014] => 1
)
)
)
[333] => Array
(
[15633] => Array
(
[100026] => Array
(
[20026] => 0
)
)
[12144] => Array
(
[100028] => Array
(
[20028] => 0
)
)
)
)
Code to build $newarray ($stack comes from CSV with 5 columns):
$newarray = array();
foreach($stack as $val){
$lineid = $val[0]; $segmentid = $val[1]; $action = $val[2]; $recency = $val[3]; $frequency = $val[4];
$newarray[$lineid][$segmentid][$recency][$frequency] = $action;
}
Code to loop through array:
foreach($newarray as $key => $value) {
$target_pixels = array();
$owner_id = $key;
foreach($value as $key2 => $value2){
$target_pixel = new stdClass;
$target_pixel->conversion_id = $key2;
$target_pixel->negated = false;
foreach($value2 as $key3 => $value3){
$target_pixel->seconds_since_conversion = $key3 * 24 * 60 * 60;
foreach($value3 as $key4 => $value4){
$target_pixel->frequency_min = $key4;
$the_action = $value4;
}
}
$target_pixels[] = $target_pixel;
}
print_r($target_pixels);
}
Since you say you can change the structure of the array, I would go with something along the lines of:
$newarray = array();
foreach($stack as $val){
$lineid = $val[0];
$segmentid = $val[1];
$action = $val[2];
$recency = $val[3];
$frequency = $val[4];
$newarray[$lineid][$segmentid] = array(
'recency' => $recency,
'frequency' => $frequency
'action' => $action
);
}
Then your code would look like:
foreach ($newarray as $lineid => $line) {
$target_pixels = array();
$owner_id = $lineid;
foreach ($line as $segmentid => $segment){
$target_pixel = new stdClass;
$target_pixel->conversion_id = $segmentid;
$target_pixel->negated = false;
$target_pixel->seconds_since_conversion = $segment['recency'] * 24 * 60 * 60;
$target_pixel->frequency_min = $segment['frequency'];
$target_pixels[$segment['action']][] = $target_pixel;
}
var_dump($target_pixels);
}

php multidimensional array from known key values

I have a collection of keys in this massive flat single array I would like to basically expand that array into a multidimensional one organized by keys - here is an example:
'invoice/products/data/item1'
'invoice/products/data/item2'
'invoice/products/data/item2'
=>
'invoice'=>'products'=>array('item1','item2','item3')
how can I do this - the length of the above strings are variable...
Thanks!
$src = array(
'invoice/products/data/item1',
'invoice/products/data/item2',
'invoice/products/data/item2',
'foo/bar/baz',
'aaa/bbb'
);
function rsplit(&$v, $w)
{
list($first, $tail) = explode('/', $w, 2);
if(empty($tail))
{
$v[] = $first;
return $v;
}
$v[$first] = rsplit($v[$first], $tail);
return $v;
}
$result = array_reduce($src, "rsplit");
print_r($result);
Output is:
Array (
[invoice] => Array
(
[products] => Array
(
[data] => Array
(
[0] => item1
[1] => item2
[2] => item2
)
)
)
[foo] => Array
(
[bar] => Array
(
[0] => baz
)
)
[aaa] => Array
(
[0] => bbb
)
)
Something along these lines: (Didn't test it though!) Works now ;)
$data = array();
$current = &$data;
foreach($keys as $value) {
$parts = explode("/", $value);
$parts_count = count($parts);
foreach($parts as $i => $part) {
if(!array_key_exists($part, $current)) {
if($i == $parts_count - 1) {
$current[] = $part;
}
else {
$current[$part] = array();
$current = &$current[$part];
}
}
else {
$current = &$current[$part];
}
}
$current = &$data;
}
$keys beeing the flat array.
Although it's not clear from your question how the "/" separated strings will map to an array, the basic approach will probably be something like this:
$result = array();
$k1 = $k2 = '';
ksort($yourData); // This is the key (!)
foreach ($yourData as $k => $v) {
// Use if / else if / else if to watch for new sub arrays and change
// $k1, $k2 accordingly
$result[$k1][$k2] = $v;
}
This approach uses the ksort to ensure that keys at the same "level" appear together, like this:
'invoice/products/data1/item1'
'invoice/products/data1/item2'
'invoice/products/data2/item3'
'invoice/products2/data3/item4'
'invoice/products2/data3/item5'
Notice how the ksort corresponds to the key grouping you're aiming for.

Categories