Loop through array grouping indexes by value within that array (PHP) - php

I wish to create a list of items from an array, grouped by a value within that array.
Take this array:
$people = array(
0 => array(
"Forename" => "Jim",
"Surname" => "Smith"
),
1 => array(
"Forename" => "Mike",
"Surname" => "Johnson"
),
2 => array(
"Forename" => "Kim",
"Surname" => "Smith"
),
3 => array(
"Forename" => "Paul",
"Surname" => "Jones"
)
);
Specifically I'd like to run a foreach on $people, grouping them by unique surname. i.e. the desired output would be:
<select>
<optgroup label="Smith">
<option>Jim</option>
<option>Kim</option>
</optgroup>
<optgroup label="Johnson">
<option>Mike</option>
</optgroup>
<optgroup label="Jones">
<option>Paul</option>
</optgroup>
</select>
I'm struggling to come up with anything vaguely efficient and the Google gods aren't watching over me today :( What's the best approach for such a use-case in PHP?

$surnames = array();
foreach($people as $person) {
$surnames[$person['surname']][] = $person;
}
This code stores all persons in an array grouped by their surnames.
The resulting array:
array(
'smith' => array(
0 => array(
"Forename" => "Jim",
"Surname" => "Smith"
),
1 => array(
"Forename" => "Kim",
"Surname" => "Smith"
)
),
'jones' => array(
0 => array(
"Forename" => "Paul",
"Surname" => "Jones"
)
)
)

I would do this way:
$grouped = array();
foreach ($people as $p){
if (!array_key_exist($p["Surname"], $grouped)){
$grouped[$p["Surname"]] = array();
}
$grouped[$p["Surname"]][] = $p;
}

I've added one more duplicate person:
....
4 => array
(
"Forename" => "Kim",
"Surname" => "Smith"
)
);
this is how you filter array :
$uniqueNames = array();
foreach($people as $person)
{
$uniqueNames[$person['Surname']][] = $person['Forename'];
}
if however you also need Forename to be unique, you can do it like this:
$uniqueNames = array_map
(
function($arrayItem)
{
if (is_array($arrayItem))
{
return array_unique($arrayItem);
}
}
, $uniqueNames
);
Also I've made some easy functions to generate html code:
function htmlSelect($name, $optionsData, $selectedItem = null)
{
$str = "\n<select name='$name' id='select-$name'>";
foreach ($optionsData as $k => $value_s)
{
if(is_array($value_s))
{
$str .= htmlOptgroup($k, $value_s);
}
else
{
$selected = ($selectedItem && $selectedItem == $k);
$str .= "\n\t".htmlOption($value_s, $k, $selected);
}
}
$str .= "\n</select>";
return $str;
}
function htmlOptgroup($label, $optionsData, $selectedItem = null)
{
$str = "\n\t<optgroup label='$label'>";
foreach ($optionsData as $k => $value)
{
$selected = ($selectedItem && $selectedItem == $k);
$str .= "\n\t\t".htmlOption($value, $k, $selected);
}
$str .= "\n\t</optgroup>";
return $str;
}
function htmlOption($display, $value, $selected = false)
{
$selectedStr = $selected ? " selected='selected'" : "" ;
return "<option$selectedStr value='$value'>$display</option>";
}
these functions can easily move to static class for html.
finally you call:
echo htmlSelect('unique-surnames', $uniqueNames);
I swear when I started there was no any answers :d

Related

Remove duplicates array from a multidimensional array in PHP

How, from this array, I can filter the duplicate values?
Actually, for the same country and city, the data are the same - Except the population changed.
How can I remove the array that contains the higher population?
$arr = array
(
"100" => array(
array(
"country" => 'France',
"city" => 'Paris',
"population" => '1800000',
),
array(
"country" => 'France',
"city" => 'Paris',
"population" => '2000000',
),
array(
"country" => 'France',
"city" => 'Toulouse',
"population" => '500000',
),
)
"101" => array(
array(
"country" => 'Russia',
"city" => 'Moscow',
"population" => '144000000',
)
)
);
So the desired output should be:
$arr = array
(
"100" => array(
array(
"country" => 'France',
"city" => 'Paris',
"population" => '1800000'
),
array(
"country" => 'France',
"city" => 'Toulouse',
"population" => '500000'
),
)
"101" => array(
array(
"country" => 'Russia',
"city" => 'Moscow',
"population" => '144000000',
)
)
);
This is what I tried:
$temp_array = [];
foreach ($array as &$v) {
if (!isset($temp_array[$v['country']] && $temp_array[$v['city']]))
$temp_array[$v[$key]] =& $v;
}
$array = array_values($temp_array);
return $array;
You can first use array_reduce for filtering the lower population (use the combination of country and city as key). Then explode them and reset the array with that min value:
foreach($arr as $k => &$ar) {
$temp = array_reduce($ar, function ($carry, $item) {
$key = $item["country"] . "###" . $item["city"];
$carry[$key] = (isset($carry[$key]) && $item["population"] > $carry[$key]) ? $carry[$key] : $item["population"];
return $carry;
}, []);
$ar = [];
foreach($temp as $k => $val) {
list($country, $city) = explode("###", $k);
$ar[] = array("country" => $country, "city" => $city, "population" => $val);
}
}
Live example: 3lv4
Edit:
You can use array_filter instead the foreach loop to avoid coping:
$ar = array_filter($ar, function ($item) use ($mins) {
$key = $item["country"] . "###" . $item["city"];
return $mins[$key] == $item["population"];
});
from the looks of it the array is already grouped by country, i'm assuming that each array element only has child arrays from the same country. I'd also say it's more sensible to have an array with keys for each country anyway for easier access and filtering down the line so I would say
$populations = [];
foreach($array as $arr) {
if(!isset($populations[$arr['country']])) $populations[$arr['country']] = [];//create an array entry for the country
$cities = [];
foreach($arr as $city) {
if(!isset($cities[$city]) || $cities[$city]['population'] > $city['population']) $cities[$city] = ['population' => $city['population']];//you could put the value straight in, but this means if you expand later you could add extra fields to each cities info such as district, number of unemployed or whatever you're using this for
}
$populations[$arr['country']] = $cities;
}
this is a slightly different output to that which you have outlined, but i think it will make it simpler to use further on as you can access all data for specific countries, and then for cities therein rather than having to continually loop through and check if the child contains a country you are after.
I hope this makes sense, my fiancee is trying to talk to me about ikea at the same time as I'm answering, so it may not be 100% perfect but will point you in a good direction at least
what I did two nested loops, the first gets the subarray that contains all the content for a specific key (e.g. 100 and 101).
next I iterate through the data, and keep a temporary array with two levels, the first will be the country as key, and the second will be the city as key that tracks the lowest population.
once the above is done, I iterate through the temporary array to get the country, city and population in the correct format and append it to a new array. I then substitute the previous array for this newly acquired result.
<?php
$arr = array
(
"100" => array(
array(
"country" => 'France',
"city" => 'Paris',
"population" => '1800000',
),
array(
"country" => 'France',
"city" => 'Paris',
"population" => '2000000',
),
array(
"country" => 'France',
"city" => 'Toulouse',
"population" => '500000',
),
),
"101" => array(
array(
"country" => 'Russia',
"city" => 'Moscow',
"population" => '144000000',
)
)
);
foreach($arr as $key=>$subarr) {
$tmp = array();
foreach($subarr as $v) {
$country = $v['country'];
$city = $v['city'];
$population = $v['population'];
if(isset($tmp[$country])) {
if(isset($tmp[$country][$city])) {
if($tmp[$country][$city] > $population) {
$tmp[$country][$city] = $population;
}
} else {
$tmp[$country][$city] = $population;
}
} else {
$tmp[$country] = array();
$tmp[$country][$city] = $population;
}
}
$res = array();
foreach($tmp as $country=>$cities) {
foreach($cities as $city=>$population) {
$res[] = array('country'=>$country,'city'=>$city,'population'=>$population);
}
}
$arr[$key] = $res;
}
print_r($arr);
You can make a compound array key with the country and city that way it's easy to keep track of what you have looped.
Since city may not be in the arrays then a if it needed to not get a notice.
foreach($arr as $key => $sub){
foreach($sub as $item){
if(isset($item['city'])){
if(!isset($res[$key][$item['country'] . $item['city']])) $res[$key][$item['country'] . $item['city']] = $item;
if($res[$key][$item['country'] . $item['city']] < $item['population']) $res[$key][$item['country'] . $item['city']] = $item;
}else{
if(!isset($res[$key][$item['country']])) $res[$key][$item['country']] = $item;
if($res[$key][$item['country']] < $item['population']) $res[$key][$item['country']] = $item;
}
}
}
var_dump($res);
Output:
array(2) {
[100]=>
array(2) {
["FranceParis"]=>
array(3) {
["country"]=>
string(6) "France"
["city"]=>
string(5) "Paris"
["population"]=>
string(7) "1800000"
}
["FranceToulouse"]=>
array(3) {
["country"]=>
string(6) "France"
["city"]=>
string(8) "Toulouse"
["population"]=>
string(6) "500000"
}
}
[101]=>
array(1) {
["Russia"]=>
array(2) {
["country"]=>
string(6) "Russia"
["population"]=>
string(9) "144000000"
}
}
}
https://3v4l.org/KNuId
If you need to remove the keys such as "FranceToulouse" then just loop the array again and use array_values

Compare Dynamic Multidimensional Arrays

I have two multidimensional dynamic arrays like so in PHP:
$old = array(
"meta_data" => array(
"page_title" => "Test1",
"page_description" => "Test2",
"page_keywords" => "Test3"
),
"content" => array(
"page_header_one" => "1",
"page_content_one" => "2",
"page_header_two" => "3",
"page_content_two" => "4",
"page_header_three" => "5"
),
);
$new = array(
"meta_data" => array(
"page_title" => "Test1",
"page_description" => "Test2",
"page_keywords" => "Test3324"
),
"content" => array(
"page_header_one" => "124",
"page_content_one" => "243",
"page_header_two" => "343"
),
);
I'm struggling to compare these as they're dynamic, e.g. the keys change. What I'm aiming to do is compare the arrays, find out what's changed, leave out keys that don't match, and only add the changes to the new array.
The only things that will be constant are "meta_data" and "content"
So for example in $old we have 5 items in the content array, but in the $new array we only have 3 items (3 changed items), so the new array would have 3 content items.
Is there a way to do this I can't for the life in me figure out how?
Final array should look like so:
$final = array(
"meta_data" => array(
"page_keywords" => "Test3324"
),
"content" => array(
"page_header_one" => "124",
"page_content_one" => "243",
"page_header_two" => "343"
),
);
Maybe it can be done easier, but I think this will do the job:
<?php
$newArray = [];
foreach($old['meta_data'] as $key => $value) {
if(array_key_exists($key, $new['meta_data'])) {
if($new['meta_data'][$key] == $old['meta_data'][$key]) {
$newArray['meta_data'][$key] = $value;
}
}
}
foreach($old['content'] as $key => $value) {
if(array_key_exists($key, $new['content'])) {
if($new['content'][$key] == $old['content'][$key]) {
$newArray['content'][$key] = $value;
}
}
}
You can also make the top key dynamic, so content and meta_data are dynamic as well, like this:
foreach($old as $topKey => $subArray) {
if(array_key_exists($topKey, $new)) {
foreach($subArray as $key => $value) {
if(array_key_exists($key, $new[$topKey])) {
if($new[$topKey][$key] == $old[$topKey][$key]) {
$newArray[$topKey][$key] = $value;
}
}
}
}
}
I haven't tested it. But I think you'll get the gist.
The only things that will be constant are "meta_data" and "content"
Given this thing in mind, the solution to your problem would be to use array_diff() function like this:
$final = array();
$final['meta_data'] = array_diff($new['meta_data'], $old['meta_data']);
$final['content'] = array_diff($new['content'], $old['content']);
// display $final array
var_dump($final);
Here's a live demo
You can simply use the PHP array_diff() function.
$final = array_diff($new, $old);

Not getting array all values using php

I have this following array
$question = array(
"ques_15" => array(
"name" => array(
"0" => "aaa"
)
),
"ques_16" => array(
"name" => array(
"0" => "bbb",
"1" => "ccc"
)
)
);
$i=0;
foreach($question as $k=>$v)
{
echo $question[$k]['name'][$i];
$i++;
}
But my output is only
aaaccc
I am missing the value bbb
You need to iterate the inner 'name' arrays - you could use a nested foreach loop:
$question = array(
"ques_15" => array(
"name" => array(
"0" => "aaa"
)
),
"ques_16" => array(
"name" => array(
"0" => "bbb",
"1" => "ccc"
)
)
);
foreach($question as $quest)
{
foreach($quest['name'] as $val)
{
echo $val;
}
}
you should loop though like so
foreach($question as $q)
{
foreach($q['name'] as $v)
{
echo $v;
}
}
in foreach you don't need a counter $i, it's for while() or for()
you array is two dimensional so you need 2 foreach
Check it out in a functional way.
The shorthand array declaration works only on PHP 5.4+ though, but it still works with your longhand array declaration.
$questions = [
'ques_15' => ['name' => ['aaa']],
'ques_16' => ['name' => ['bbb', 'ccc']]
];
array_map(function($a){
foreach ($a['name'] as $v) echo $v;
}, $questions);

Put nested array into one array

Suppose i have a array like this :
Array(
'1' => Array(
"ID" => 1,
"Name" => "name 1"
),
'2' => Array (
Array(
"ID" => 2,
"Name" => "name 2"
)
),
'3' => Array(
Array(
Array(
Array(
"ID" => 3,
"Name" => "name3"
)
)
),
'4' => Array (
Array {
"ID" => 4,
"Name" => "name 4"
),
Array(
"ID" => 5,
"Name" => "name 5"
),
Array(
"ID" => 6,
"Name" => "name 6"
)
);
number of sub-arrays is not ordered it may be 3, 4 or 5 etc...
and i wanted to get :
Array(
Array( "ID" => 1, "Name" => "name 1"),
Array( "ID" => 2, "Name" => "name 2"),
Array( "ID" => 3, "Name" => "name 3"),
Array( "ID" => 4, "Name" => "name 4"),
Array( "ID" => 5, "Name" => "name 5"),
Array( "ID" => 6, "Name" => "name 6"));
Is there an easy way to do this ?
EDIT :
Edited the above array to add :
'4' => Array (
Array {
"ID" => 4,
"Name" => "name 4"
),
Array(
"ID" => 5,
"Name" => "name 5"
),
Array(
"ID" => 6,
"Name" => "name 6"
)
);
which aren't nested but i still want them to be in my final array.
Thanks.
//Assuming your data is in $in
$out=array();
foreach($in as $k=>$v) {
while ((is_array($v)) && (isset($v[0]))) $v=$v[0];
//See below for next line
$out[]=$v;
}
print_r($out);
With the marked line being either $out[$k]=$v; or $out[]=$v; depending on wether you want to keep the 1st-level keys. In your desired output you do not ,so use the shown version
Edit
With you changed input array, you need to do something like
function addtoarray($inarray, &$outarray) {
foreach ($inarray as $i) {
if (!is_array($i)) continue;
if (isset($i['ID'])) $outarray[]=$i;
else addtoarray($i,$outarray);
}
}
$out=array();
addtoarray($in,$out);
print_r($out);
This was tested against your new input data
You could recurse through the array and check for the ID field.
function combine_array(array $src, array &$dest)
{
if( isset( $src['ID'] ) ) {
$dest[] = $src;
return;
}
foreach( $sub in $src ) {
combine_array( $sub, $dest );
}
}
Just wrote this off the top of my head, no testing, so it might have a few problems. But that's the basic idea.
This may work for you, assuming $array is the variable and php 5.3
//process
$array = array_map(function($block) {
$return = '';
foreach ($block as $sub) {
if (true === isset($sub['ID']) {
$return = $block;
break;
}
}
return $block;
}, $array);
array_filter($array); //remove empty elements
Wrote up a quick recursive function. You'll need to write any appropriate error handling and check the logic, since a discrepancy in your data could easily turn this into an infinite loop:
$output = array();
foreach ( $data as $d ) {
$output[] = loop_data( $d );
}
function loop_data( $data ) {
// Check if this is the right array
if( ! array_key_exists( "ID", $data ) ) {
// Go deeper
$data = loop_data( $data[0] );
}
return $data;
}

substr by using array as condition

I have a file contain text
ABBCDE1990-12-10JOBALPHABETabbcde1990-12-10jobalphabet
$field = array(
"fullname" => array("length"=5,"mandat"=>True),
"bithday" => array("length"=>10,"mandat"=>True)
"job" => array("length"=>3,"mandat"=>True),
"desc" => array("length"=>8,"mandat"=>false)
);
How can I get the array some thing like this:
$output = array(
//ABBCDE1990-12-10JOBALPHABET
0=>array(
"fullname" => "ABBCDE"
"bithday" => 1990-12-10
"job" => "JOB"
"desc"=> "ALPHABET"
)
//abbcde1990-12-10jobalphabet
1=>array(
"fullname" => "abbcde"
"bithday" => 1990-12-10
"job" => "job"
"desc"=> "alphabet"
)
);
I am tryin to buld a function
function toOutput($str,$filed){
$per_line = 27;//len of abbcde1990-12-10jobalphabet
$pos = 0;
while ($pos<strlen($str)){
$pos += 27;
//
}
}
$field = array(
"fullname" => array("length"=>6,"mandat"=>True),
"bithday" => array("length"=>10,"mandat"=>True),
"job" => array("length"=>3,"mandat"=>True),
"desc" => array("length"=>8,"mandat"=>false)
);
$string = 'ABBCDE1990-12-10JOBALPHABETabbcde1990-12-10jobalphabet';
$result = array();
$countString = strlen($string)/27;
$oldPos = 0;
for($i=0;$i<$countString;$i++) {
foreach($field as $k=>$v) {
$result[$i][$k] = substr($string,$oldPos,$v['length']);
$oldPos += $v['length'];
}
}
print_r($result);
You can see it up and running here: http://codepad.org/CEQNIPCg (version 0.3)
Based on that you can create a function so you can pass to it all $string that you have

Categories