Related
I am looking to group an array into subarrays based on its keys.
Sample Array
Array
(
[0] => Array
(
[a_id] => 1
[a_name] => A1
[b_id] => 1
[b_name] => B1
[c_id] => 1
[c_name] => C1
)
[1] => Array
(
[a_id] => 1
[a_name] => A1
[b_id] => 1
[b_name] => B1
[c_id] => 2
[c_name] => C2
)
[2] => Array
(
[a_id] => 1
[a_name] => A1
[b_id] => 2
[b_name] => B2
[c_id] => 3
[c_name] => C3
)
[3] => Array
(
[a_id] => 2
[a_name] => A2
[b_id] => 3
[b_name] => B3
[c_id] => 4
[c_name] => C4
)
)
I need this sample array to be converted into a JSON array of the following format:
Expected Output
[{
"a_id": 1,
"a_name": "A1",
"b_list": [{
"b_id": 1,
"b_name": "B1",
"c_list": [{
"c_id": 1,
"c_name": "C1"
}, {
"c_id": 2,
"c_name": "C2"
}]
}, {
"b_id": 2,
"b_name": "B2",
"c_list": [{
"c_id": 3,
"c_name": "C3"
}]
}]
}, {
"a_id": 2,
"a_name": "A2",
"b_list": [{
"b_id": 3,
"b_name": "B3",
"c_list": [{
"c_id": 4,
"c_name": "C4"
}]
}]
}]
I was able to group by a key using the code below.
$array = array(
array("a_id" => "1","a_name" => "A1","b_id" => "1","b_name" => "B1","c_id" => "1","c_name" => "C1"),
array("a_id" => "1","a_name" => "A1","b_id" => "1","b_name" => "B1","c_id" => "2","c_name" => "C2"),
array("a_id" => "1","a_name" => "A1","b_id" => "2","b_name" => "B2","c_id" => "3","c_name" => "C3"),
array("a_id" => "2","a_name" => "A2","b_id" => "3","b_name" => "B3","c_id" => "4","c_name" => "C4")
);
$return = array();
foreach($array as $val) {
$return[$val["a_id"]][] = $val;
}
print_r($return);
But my actual scenario involves grouping into sub arrays didn't worked.
Looking forward to see if there is an optimized way or useful function to get into my expected JSON response.
Note: I am looking into a generalized use case here . For example : a_list as countries,b_list as states and c_list as cities.
Man that is very specific use case for arrays. Well here is your solution.
$array = <YOUR SAMPLE ARRAY>
$output = [];
/*
* Nesting array based on a_id, b_id
*/
foreach ($array as $item) {
$aid = $item['a_id'];
$bid = $item['b_id'];
$cid = $item['c_id'];
if(!isset($output[$aid])){
$output[$aid] = [
'a_id' => $item['a_id'],
'a_name' => $item['a_name'],
'b_list' => [
$bid => [
'b_id' => $item['b_id'],
'b_name' => $item['b_name'],
'c_list' => [
$cid = [
'c_id' => $item['c_id'],
'c_name' => $item['c_name']
]
]
]
]
];
} else if (!isset($output[$aid]['b_list'][$bid])){
$output[$aid]['b_list'][$bid] = [
'b_id' => $item['b_id'],
'b_name' => $item['b_name'],
'c_list' => [
$cid => [
'c_id' => $item['c_id'],
'c_name' => $item['c_name']
]
]
];
} else if(!isset($output[$aid]['b_list'][$bid]['c_list'][$cid])) {
$output[$aid]['b_list'][$bid]['c_list'][$cid] = [
'c_id' => $item['c_id'],
'c_name' => $item['c_name']
];
} else {
// Do/Dont overrider
}
}
/*
* Removing the associativity from the b_list and c_list
*/
function indexed($input){
$output = [];
foreach ($input as $key => $item) {
if(is_array($item)){
if($key == 'b_list' || $key == 'c_list'){
$output[$key] = indexed($item);
} else {
$output[] = indexed($item);
}
} else {
$output[$key] = $item;
}
}
return $output;
}
$indexed = indexed($output);
print_r(json_encode($indexed, 128));
Interesting requirement there.
Here is my generalized solution that is also extendable.
function transform($array, $group=[
['a_id','a_name','b_list'],
['b_id','b_name','c_list'],
['c_id','c_name'],
]){
foreach($array as $a){
$r = &$result;
foreach($group as $g){
$x = &$r[$a[$g[0]]];
$x[$g[0]] = $a[$g[0]];
$x[$g[1]] = $a[$g[1]];
if(isset($g[2])) $r = &$x[$g[2]]; else break;
}
}
return transformResult($result);
}
function transformResult($result){
foreach($result as &$a)
foreach($a as &$b)
if(is_array($b)) $b = transformResult($b);
return array_values($result);
}
To extend this solution, all you have to do is modify the $group parameter,
either directly in the function declaration or by passing an appropriate value as the 2nd parameter.
Usage example:
echo json_encode(transform($array), JSON_PRETTY_PRINT);
This will return the same output assuming the same $array input in your example.
Now here is the code that works best in the given situation. I have created a similar situation and then explained the solution in detail.
Situation
The Order Form is multipage depending on the number of days served based on the package selected. Details of each package are stored in the database with the following fields:
package_id (Unique Field)
package_name (Name of the Package, e.g. Package A)
servings_count (Total Servings in a Day)
days_served (Number of Days Served in a Month)
In order to carry forward the selection of meals for each day and serving of that day to store as an Order in the database, I required a Multidimensional Array of PHP that can be defined/populated dynamically.
Expected output is something like:
Array
(
[Day 1] => Array
(
[meal_id_1] => Unique ID //to be replaced with user selection
[meal_code_1] => Meal Name //to be replaced with user selection
[meal_type_1] => Meal //prefilled based on the selected package
[meal_id_2] => Not Available //to be replaced with user selection
[meal_code_2] => 2 //to be replaced with user selection
[meal_type_2] => Meal //prefilled based on the selected package
)
[Day 2] => Array
(
[meal_id_1] => Unique ID //to be replaced with user selection
[meal_code_1] => Meal Name //to be replaced with user selection
[meal_type_1] => Meal //prefilled based on the selected package
[meal_id_2] => Not Available //to be replaced with user selection
[meal_code_2] => 2 //to be replaced with user selection
[meal_type_2] => Meal //prefilled based on the selected package
)
This above array has been created 100% dynamically based on the explained structure and number of servings and days. Below is the code with some explanation.
First, we have to declare two PHP Arrays.
$total_meals_array = []; //Primary, Multidimension Array
$meals_selected_array = []; //Meals Details Array to be used as primary array's key value.
After doing this, run MySQL query to read packages from the database. Now based on the result, do the following:
$total_meals_array = []; //Primary, Multidimension Array
$meals_selected_array = []; //Meals Details Array to be used as primary array's key value.
if( $num_row_packages >= 1 ) {
while($row_packages = mysqli_fetch_array ($result_packages)) {
$package_id = $row_packages['package_id'];
$package_name = $row_packages['package_name'];
$servings_count = $row_packages['servings_count'];
$days_served = $row_packages['days_served'];
//this for loop is to repeat the code inside `$days_served` number of times. This will be defining our primary and main Multidimensional Array `$total_meals_array`.
for ($y = 1; $y <= $days_served; $y++) {
//once inside the code, now is the time to define/populate our secondary array that will be used as primary array's key value. `$i`, which is the meal count of each day, will be added to the key name to make it easier to read it later. This will be repeated `$meals_count` times.
for ($i = 1; $i <= $meals_count; $i++) {
$meals_selected_array["meal_id_" . $i] = "Unique ID";
$meals_selected_array["meal_code_" . $i] = "Meal Name";
$meals_selected_array["meal_type_" . $i] = "Meal";
}
//once our secondary array, which will be used as the primary array's key value, is ready, we will start defining/populating our Primary Multidimensional Array with Keys Named based on `$days_served`.
$total_meals_array["Day " . $y] = $meals_selected_array;
}
}
}
That's it! Our dynamic Multidimensional Array is ready and can be viewed by simply the below code:
print "<pre>";
print_r($total_meals_array);
print "</pre>";
Thank you everyone, specially #yarwest for being kind enough to answer my question.
Here is the code, you can use it for index from a_ to y_ deep. The innerest element is null, if you don't want it. Terminate the for loop before last element, then process last element seperately. You also can do some improvement on this code. Hope this helps.
<?php
$array = array(
array("a_id" => "1","a_name" => "A1","b_id" => "1","b_name" => "B1","c_id" => "1","c_name" => "C1"),
array("a_id" => "1","a_name" => "A1","b_id" => "1","b_name" => "B1","c_id" => "2","c_name" => "C2"),
array("a_id" => "1","a_name" => "A1","b_id" => "2","b_name" => "B2","c_id" => "3","c_name" => "C3"),
array("a_id" => "2","a_name" => "A2","b_id" => "3","b_name" => "B3","c_id" => "4","c_name" => "C4")
);
$arrays = array_map(function($v){return array_chunk($v, 2, true);}, $array);
$result = [];
foreach($arrays as $value)
{
$ref = &$result;
$len = count($value);
$index = 0;
for(; $index < $len; $index++)
{
$arr = $value[$index];
$char = key($arr)[0];
$charAdd = chr(ord($char)+1);
$key = $arr[$char.'_id'].$arr[$char.'_name'];
$listKey = $charAdd.'_list';
foreach($arr as $k => $v)
{
$ref[$key][$k] = $v;
}
$ref = &$ref[$key][$listKey];
}
}
var_dump($result);
Output: the online live demo
ei#localhost:~$ php test.php
array(2) {
["1A1"]=>
array(3) {
["a_id"]=>
string(1) "1"
["a_name"]=>
string(2) "A1"
["b_list"]=>
array(2) {
["1B1"]=>
array(3) {
["b_id"]=>
string(1) "1"
["b_name"]=>
string(2) "B1"
["c_list"]=>
array(2) {
["1C1"]=>
array(3) {
["c_id"]=>
string(1) "1"
["c_name"]=>
string(2) "C1"
["d_list"]=>
NULL
}
["2C2"]=>
array(3) {
["c_id"]=>
string(1) "2"
["c_name"]=>
string(2) "C2"
["d_list"]=>
NULL
}
}
}
["2B2"]=>
array(3) {
["b_id"]=>
string(1) "2"
["b_name"]=>
string(2) "B2"
["c_list"]=>
array(1) {
["3C3"]=>
array(3) {
["c_id"]=>
string(1) "3"
["c_name"]=>
string(2) "C3"
["d_list"]=>
NULL
}
}
}
}
}
["2A2"]=>
array(3) {
["a_id"]=>
string(1) "2"
["a_name"]=>
string(2) "A2"
["b_list"]=>
array(1) {
["3B3"]=>
array(3) {
["b_id"]=>
string(1) "3"
["b_name"]=>
string(2) "B3"
["c_list"]=>
array(1) {
["4C4"]=>
array(3) {
["c_id"]=>
string(1) "4"
["c_name"]=>
string(2) "C4"
["d_list"]=>
&NULL
}
}
}
}
}
}
This is rather interesting. As far as I can tell, you are trying to transform a flat array into a multidimensional array, as well as transforming the keys into a multidimensional representation.
The top level difference seems to reside in the part before the underscore of the a_* keys.
Then, for each of these keys, every other *_ letters should induce it's own list.
This recursive function does the trick without hardcoding, will work with whatever number of levels, letters (or whatever else) and right identifiers.
It seems to return exactly the json you show in the sample ($array being the array as defined in your question)
$multidimension = multidimensionalify($array, ['a', 'b', 'c'], ['name']);
var_dump(json_encode($multidimension, JSON_PRETTY_PRINT));
function multidimensionalify(
array $input,
array $topLevelLetters,
array $rightHandIdentifiers,
$level = 0,
$parentId = null,
$uniqueString = 'id'
)
{
$thisDimension = [];
$thisLetter = $topLevelLetters[$level];
foreach ($input as $entry)
{
$thisId = $entry["{$thisLetter}_{$uniqueString}"];
$condition = true;
if ($parentId !== null)
{
$parentLetter = $topLevelLetters[$level - 1];
$condition = $entry["{$parentLetter}_{$uniqueString}"] === $parentId;
}
if (!isset($thisDimension[$thisId]) && $condition)
{
$thisObject = new stdClass;
$thisObject->{"{$thisLetter}_{$uniqueString}"} = $thisId;
foreach ($rightHandIdentifiers as $identifier)
{
$thisObject->{"{$thisLetter}_{$identifier}"} = $entry["{$thisLetter}_{$identifier}"];
}
if (isset($topLevelLetters[$level + 1])) {
$nextLetter = $topLevelLetters[$level + 1];
$thisObject->{"{$nextLetter}_list"} = multidimensionalify($input, $topLevelLetters, $rightHandIdentifiers, $level + 1, $thisId, $uniqueString);
}
$thisDimension[$thisId] = $thisObject;
}
}
return array_values($thisDimension);
}
Try this function just pass your array and key name for grouping and then convert to json.
public function _group_by($array, $key) {
$return = array();
foreach ($array as $val) {
$return[$val[$key]][] = $val;
}
return $return;
}
This is for tie-break situations ranking players in a series. As such, ignore the totals of the scores, they'll be the same (1400 in this example).
So, given the following array:
array(2) {
array(3) {
[0] => array(1) {
["score"] => 500
}
[1] => array(1) {
["score"] => 500
}
[2] => array(1) {
["score"] => 400
}
}
array(4) {
[0] => array(1) {
["score"] => 600
}
[1] => array(1) {
["score"] => 600
}
[2] => array(1) {
["score"] => 100
}
[3] => array(1) {
["score"] => 100
}
}
}
I'd like to sort it so that the subarray with the highest individual score (or NUMBER of highest scores) comes out on top. If that's stiil a tie, we work down to their second highest scores, etc.
E.g. in the above example, 600,600,100,100 is better than 500,500,400, even though they both total 1400.
Similarly 0,100,300,300 would be better than 300,200,200,0,0
0,100,100,50 would be better than 100,100,25,0,0,25, etc.
This is the best I've come up with so far, and it seems very messy to me, I feel there must be better/cleaner options:
function sortTies($a, $b)
{
// get arrays of the scores and their counts
// each array item will have the score as the key and a count as the value
$aa = array_count_values(array_column($a, 'score'));
$bb = array_count_values(array_column($b, 'score'));
// sort them so that the highest scores are first
krsort($aa);
krsort($bb);
// get the length of the longest array
$maxLength = max(count($aa), count($bb));
// now we loop through, comparing the $i'th index of $aa to that of $bb
for($i = 0; $i < $maxLength; $i++) {
reset($aa);
reset($bb);
// move $i items deeper into the arrays
for($n=0; $n < $i; $n++) {
next($aa);
next($bb);
}
// if the keys differ at a certain position, we have our sort criteria, so should return...
if (key($aa) !== key($bb)) {
return key($bb) <=> key($aa)
}
// ...otherwise, we check the value stored under those keys in each array
elseif (current($aa) !== current($bb)) {
return current($bb) <=> current($aa);
}
// if the key and the value are the same, we don't return and we carry on into the next
// iteration of the loop, going one element deeper in each array
}
return 0; //dead heat
}
What could I do to make this better?
Loop through the list, group by score, sum quantity ( instance of score ).
Order by score desc. Get a factor Score * Quantity, then construct an array with the factor and the list.
Finally, sort desc by factor.
Like this:
<?php
$data=array(
array(
array(
"score" => 500
),
array(
"score" => 500
),
array(
"score" => 400
)
),
array(
array(
"score" => 600
),
array(
"score" => 600
),
array(
"score" => 100
),
array(
"score" => 100
)
)
);
$result = array();
foreach( $data as $i => $list ) {
$group = array();
foreach( $list as $index => $item ) {
if ( empty( $group ) ) {
$group[$item["score"]] = 1;
} else {
if ( isset( $group[$item["score"]] ) ) {
$qty = $group[$item["score"]];
$qty = $qty + 1;
$group[$item["score"]] = $qty;
} else {
$group[$item["score"]] = 1;
} // end if isset score
} // end if then else empty
} // end foreach $list
if ( !empty( $group ) ) {
// Order by key, desc
krsort( $group );
// Get the factor
$key = key( $group );
$factor = $key * $group[$key];
// Push
$result[$factor] = $list;
} // end if group not empty
} // end foreach $data
krsort( $result );
print_r( $result );
I want to iterate over a multidimensional array, count the occurrences of a String inside and delete Array items where the count is higher than e.g. 3.
I've already tried a pretty messy combination of array_search, array_count_values and strpos inside a N^N loop, but this takes way to long to process and the results are wrong...
This is the Array, I'm trying to alter
array(2) {
[0]=>
array(13) {
["id"]=>
string(6) "1234"
["name"]=>
string(28) "aa"
["productcategory"]=>
string(30) "Branch1^^subbranch1"
["streamID"]=>
int(0)
["streamContext"]=>
string(16) "static"
["prio"]=>
string(3) "100"
}
[1]=>
array(11) {
["id"]=>
string(6) "9876"
["name"]=>
string(30) "bb"
["productcategory"]=>
string(66) "Branch1^^subbranch2"
["streamID"]=>
int(0)
["streamContext"]=>
string(16) "static"
["prio"]=>
string(3) "100"
}
}
The surrounding Array can have around 200 Items. I'm looking for a way to remove Items if theyr productcategory is found more than X times.
Can you guys help me with this?
Yeah I've had to deal with something kind of similar. If you're looking at an array of around 200, then it should be too slow to create a counter loop and then unset the values of the original array based on those counters. I've provided a template to think about, to see if this is the direction you're after.
It makes a copy of the array, then counts the productcategory, of course I'm assuming that category^^subcategory is the count you are looking for.
<?php
$your_array = array(
array(
array(
"id" => "1234",
"name" => "aa",
"productcategory" => "Branch1^^subbranch1",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch1",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch3",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch2",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch3",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
array(
"id" => "9876",
"name" => "bb",
"productcategory" => "Branch1^^subbranch1",
"streamID" => '',
"streamContext" => "static",
"prio" => "100",
),
),
);
$counters = array();
$limit = 1; // whatever the limit is that you want
foreach ($your_array as $index => $array) {
for ($i = 0; $i < count($array); $i++) {
if (!isSet($counters[$array[$i]['productcategory']])) {
$counters[$array[$i]['productcategory']] = 0;
}
$counters[$array[$i]['productcategory']]++;
if ($counters[$array[$i]['productcategory']] > $limit) {
unset($your_array[$index][$i]);
}
}
}
print '<pre>' . print_r($counters, true) . '</pre>';
print '<pre>' . print_r($your_array, true) . '</pre>';
I'm unsetting that item in the sub array, as I'm not sure if you want to just unset the whole item.
My first question for you would be "where is your data coming from?" If this is coming out of a database, then I would recommend you tweak your query there. You can definitely solve this in PHP, but as your data set grows it will take longer and longer to loop over the dataset in PHP.
To solve this in PHP, I would recommend you create a new "product index" array. This array would be associative with the product name as the keys and the values would contain an array of all the top-level indexes in your dataset array. Once you've built the index array, you can loop over that to find which product types occur more than 3 times in the main dataset and quickly delete those items.
$productIndex = [];
// Build an index of product categories
foreach($dataset as $i => $row) {
if (!is_array($productIndex[$row['productcategory']]) {
$productIndex[$row['productcategory']] = [];
}
$productIndex[$row['productcategory']][] = $i;
}
// Search for indexes with > 3 rows
foreach($productIndex as $items) {
if (count($items) > 3) {
// Delete said rows
foreach ($items as $index) {
unset($dataset[$index]);
}
}
}
I havent been able to use a one size fits it all approach, but for future reference i will share my "solution". It doesnt feel like super sophisiticated but it gets the job done...
function filter_categories($input, $count) {
$output = $input;
$exploded_input = [];
foreach ($output as $key => $value) {
$exploded_items = explode("^^", $value["productcategory"]);
array_push($exploded_input, $exploded_items);
}
$sortedbyCategory = [];
$last_items = [];
$counted_items = [];
foreach ($exploded_input as $key => $value) {
$end = end($value);
array_push($last_items, $end);
}
$counted = array_count_values($last_items);
foreach ($counted as $key => $value) {
if($value<=$count) {
unset($counted[$key]);
}
}
foreach ($counted as $k => $v) {
for ($i=0; $i < count($input); $i++) {
if(strpos($input[$i]["productcategory"], $k)){
if($counted[$k] > $count) {
$input[$i]["hide"] = true;
$counted[$k]--;
}
}
}
}
foreach ($input as $key => $value) {
if(isset($value["hide"])) {
unset($input[$key]);
}
}
return $input;
}
I'll start by showing a non-recursive example
Non- recursive example
$given_key_name = 'site_id';
$rows[] = array(
'site_id' => '0',
'language_id' => '1',
'name' => 'sitename',
'description' =>'site desc',
);
$results = array();
foreach($rows as $row){
$key_value = $row[$given_key_name];
unset($row[$given_key_name]);
$results[$key_value] = $row;
}
// OR This method is faster than the forloop
$results = array_combine(array_column($rows, $given_key_name),$rows);
foreach($results as &$row){
unset($row[$given_key_name]);
}
$results Equals
$results[0] = array(
'language_id' => '1',
'name' => 'sitename',
'description' =>'site desc',
);
Simple, the key name has been set to the value of the given child element. But I would like to be able to nest and unnest by using multiple key names.
Example
$given_key_names = array('site_id', 'language_id');
In this case the required result would be.
$results[0][1] = array(
'name' => 'sitename',
'description' =>'site desc',
);
Explanation
The first keys value has been used as the first key in the $results array and a new empty array is created as its value. $results[0] = array();
As there is a second key, its value is set as a key to the newly created array and its value is also a new empty array. $results[0][1] = array();
As there are no more keys the empty array is populated with the remaining values
$results[0][1] = array(
'name' => 'sitename',
'description' =>'site desc',
);
so i would like two functions nestByKeyNames and unNestByKeyName.
NestByKeyNames Function
Christians Answer solves this
function nestByKeyNames($arrayRows, $arrayKeyOrder){
// Prepare resulting array
$arrayResult = array();
// Cycle the input array
foreach($arrayRows as $someRow){
// We will acomplish this using references
$current = &$arrayResult;
// get the current level
foreach($arrayKeyOrder as $someKey){
$someValue = $someRow[$someKey];
if(isset($current[$someValue])){
$current = &$current[$someValue];
}else{
$current[$someValue] = array();
$current = &$current[$someValue];
}
unset($someRow[$someKey]);
}
$current = $someRow;
}
return $arrayResult;
}
I wonder whether array_combine(array_column($arrayRows, $key_name),$arrayRows); could be used instead of the first iteration to improve performance?
This represents the results from a mysql select statement.
$rows = array(
array(
'pri_id_1' =>1,
'pri_id_2' =>1,
'pri_id_3' =>1,
'col_1' =>'col_value_1111',
'col_2' =>'col_value_1112',
'col_3' =>'col_value_1113',
),
array(
'pri_id_1' =>1,
'pri_id_2' =>2,
'pri_id_3' =>1,
'col_1' =>'col_value_1211',
'col_2' =>'col_value_1212',
'col_3' =>'col_value_1213',
),
array(
'pri_id_1' =>1,
'pri_id_2' =>3,
'pri_id_3' =>1,
'col_1' =>'col_value_1311',
'col_2' =>'col_value_1312',
'col_3' =>'col_value_1313',
)
);
$keyNames = array('pri_id_1','pri_id_2','pri_id_3');
$results = nestByKeyNames($rows, $keyNames);
The following output is produced
Array
(
[1] => Array
(
[1] => Array
(
[1] => Array
(
[col_1] => col_value_1111
[col_2] => col_value_1112
[col_3] => col_value_1113
)
)
[2] => Array
(
[1] => Array
(
[col_1] => col_value_1211
[col_2] => col_value_1212
[col_3] => col_value_1213
)
)
[3] => Array
(
[1] => Array
(
[col_1] => col_value_1311
[col_2] => col_value_1312
[col_3] => col_value_1313
)
)
)
)
UnNestByKeyNames Function
unNestByKeyNames should be able to take this output and convert it back to the original array providing that it is given the key names.
Christians Answer did not solves this as it doesnt work with a single key name but i can tell its very close.
function unNestByKeyNames($arrayRows, $arrayKeyOrder){
}
$keyNames = array('pri_id_1','pri_id_2','pri_id_3');
$rows = unNestKeyNames($results, $keyNames);
My true goal is to take the results from MYSQL SELECT statement and populate a form using the same naming convention by using nestByKeyNames.
e.g.
<input name="rows[1][1][1][col_1]" value="col_value_1" />
and then convert the $_POST request back into an MYSQL INSERT statement by first using unNestByKeyNames.
From this i will create an INSERT statement.
function returnValues($rows, $column_names){
//validation has been removed for clarity
$implode_VALUES = array();
foreach ($rows as $key => $row) {
$implode_row_values = array();
foreach ($column_names as $column_name) {
$implode_row_values[$column_name] = $row[$column_name];
}
if($implode_row_values){
$implode_VALUES[] = " ('" . implode("','", $implode_row_values) . "') ";
}
}
return $implode_VALUES;
}
$implode_COLUMNS = array('pri_id_1','pri_id_2','pri_id_3','col_1','col_2','col_3');
$implode_VALUES = returnValues($rows, $implode_COLUMNS)
$sql = "INSERT INTO table_name (" . implode(',', $implode_COLUMNS) . ") VALUES " . implode(',', $implode_VALUES);
The final result should produce a sql statement like so
INSERT INTO table_name (pri_id_1,pri_id_2,pri_id_3,col_1,col_2,col_3) VALUES ('1','1','1','NEW_value_1111','NEW_value_1112','NEW_value_1113') , ('1','2','1','NEW_value_1211','NEW_value_1212','NEW_value_1213') , ('1','3','1','NEW_value_1311','NEW_value_1312','NEW_value_1313')
What I Would like
Improvement suggestions on the 'nestByKeyNames' function (performance/ does it have bugs)
help producing 'unNestByKeyNames' code
Improvement suggestions on my '$rows to mysql INSERT' approach
examples of how i could make any of my code perform better.
This was trickier than I first imagined but I believe I have a messy solution.
First of all, this is the data I am working with. dumpr is a custom function that formats var_dump better.
$arrayKeyOrder = array(
'site_id',
'language_id'
);
$original = array(
array(
'site_id' => '0',
'language_id' => '1',
'name' => 'sitename',
'description' =>'site desc',
),
array(
'site_id' => '0',
'language_id' => '2',
'name' => 'sitename',
'description' =>'site desc',
),
array(
'site_id' => '1',
'language_id' => '1',
'name' => 'sitename',
'description' =>'site desc',
),
array(
'site_id' => '2',
'language_id' => '1',
'name' => 'sitename',
'description' =>'site desc',
),
);
$zipped = doZip($original, $arrayKeyOrder);
$unzipped = unZip($zipped, $arrayKeyOrder);
dumpr($original);
dumpr($zipped);
dumpr($unzipped);
Here is the zip and unzip functions:
function doZip($arrayRows, $arrayKeyOrder){
// Prepare resulting array
$arrayResult = array();
// Cycle the input array
foreach($arrayRows as $someRow){
// We will acomplish this using references
$current = &$arrayResult;
// get the current level
foreach($arrayKeyOrder as $someKey){
$someValue = $someRow[$someKey];
if(isset($current[$someValue])){
$current = &$current[$someValue];
}else{
$current[$someValue] = array();
$current = &$current[$someValue];
}
unset($someRow[$someKey]);
}
$current = $someRow;
}
return $arrayResult;
}
function unZip($arrayRows, $arrayKeyOrder, $arrayValues = array(), $depth = 0){
$arrayResults = array();
if($depth < count($arrayKeyOrder)){
foreach($arrayRows as $key => $value){
$arrayValues[$depth] = $key;
$arrayResults[] = unZip($value, $arrayKeyOrder, $arrayValues, $depth + 1);
}
}else{
$extra = array_combine($arrayKeyOrder, $arrayValues);
$result = array_merge($extra, $arrayRows);
return $result;
}
if($depth == 0){
for($i = 1; $i < count($arrayKeyOrder); $i++){
$arrayResults = call_user_func_array('array_merge', $arrayResults);
}
}
return $arrayResults;
}
And finally, here is the output. let me know if this is what you were asking for and if it worked OK on a larger data-set.
/vhost/virtual/sandbox/public/index.php:54
array(4) {
[0] = array(4) {
[site_id] = string(1) "0"
[language_id] = string(1) "1"
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
[1] = array(4) {
[site_id] = string(1) "0"
[language_id] = string(1) "2"
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
[2] = array(4) {
[site_id] = string(1) "1"
[language_id] = string(1) "1"
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
[3] = array(4) {
[site_id] = string(1) "2"
[language_id] = string(1) "1"
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
}
/vhost/virtual/sandbox/public/index.php:55
array(3) {
[0] = array(2) {
[1] = array(2) {
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
[2] = array(2) {
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
}
[1] = array(1) {
[1] = array(2) {
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
}
[2] = array(1) {
[1] = array(2) {
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
}
}
/vhost/virtual/sandbox/public/index.php:56
array(4) {
[0] = array(4) {
[site_id] = int(1) 0
[language_id] = int(1) 1
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
[1] = array(4) {
[site_id] = int(1) 0
[language_id] = int(1) 2
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
[2] = array(4) {
[site_id] = int(1) 1
[language_id] = int(1) 1
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
[3] = array(4) {
[site_id] = int(1) 2
[language_id] = int(1) 1
[name] = string(8) "sitename"
[description] = string(9) "site desc"
}
}
Try this:
// initialize your array
$all_rows = array();
// loop through query results
while( $row = $qry->fetch_assoc() )
{
// temporarily store these vars for easy use later
$s_id = $row['site_id'];
$l_id = $row['language_id'];
// create an empty array based on site_id and language_id
$all_rows[ $s_id ][ $l_id ] = array();
// loop through all columns returned from query
foreach ( $row as $key => $val )
{
// if it's not one of the two primary keys, push it to the array
if ( ! in_array($key, $all_primary_keys) )
{
$all_rows[ $s_id ][ $l_id ][ $key ] = $val;
}
}
}
Is there a reason the below wouldn't work?
$results = array();
while($row = $qry->fetch_assoc()){
$results[$row['site_id']][$row['language_id']] = array(
'name' => $row['name'],
'description' => $row['description']
);
}
Here are two simple functions to solve your problem. I don't put any example as I have used your data and the same function name and arguments.
The first one takes profit of pointers to solve the first step of the problem:
function nestByKeyNames($rows, $aKeys) {
$tab=Array();
foreach ($rows as &$v) {
// calculate the pointer position
$t=&$tab;
foreach ($aKeys as $v1) {
$t=&$t[$v[$v1]];
unset($v[$v1]);
}
// save the value
$t=$v;
}
return $tab;
}
This one uses a recursive algorithm and give the reverse output
function unNestByKeyNames($arrayRows, $aKeys){
$t=Array();
if (!count($aKeys)) return Array($arrayRows);
foreach ($arrayRows as $k=>&$v) {
$res=unNestByKeyNames($v, array_slice($aKeys,1));
foreach ($res as $k1=>$v1) $t[]=array_merge(Array($aKeys[0]=>$k), $v1);
}
return $t;
}
I have no suggestion about your SQL INSERT approach as long as you take care of sql injection, which I suppose might be the reason of your comment "validation has been removed for clarity"
There is no real method to what you wanting if you want to use the primary key you have to know the column name of the primary key hell you should not the columns your querying for. the best way to do it would be to use the AS keyword in the MySQL Query
SELECT primary as ID, ... where primary is the column name of your primary key and now ID is your primary key in the result set.
You can then just do the standard
$sortedResults = array();
while($row = $queryResult->fetch_assoc()){
$rowId = $row["ID"];
$sortedResults[$rowId] = $row;
}
If you don't know what the primary key is there i no reasonable way to obtain it there is a method to get the table columns and then you could go though them find the primary key save it then you have the primary key to do your while on but this would be one hell of an overhead on every query you make.
I have an array like this:
$pricing = array(
"2" => 8.23,
"5" => 10.97,
"10" => 13.28,
"15" => 15.40,
"20" => 18.15,
"25" => 20.36,
"30" => 22.84,
"40" => 25.60,
"50" => 28.35,
"60" => 31.89,
"70" => 36.23,
"80" => 39.40,
"90" => 42.52,
"100" => 44.75
);
And I have a variable that has the client given value from 1 to 100.
Question is: what is the best and fastest way to find the next biggest key, if the key itself doesn't exist?
E.g. I have a variable with value of 12, and I need to get the price for that. According to the array here, the price would be 15.40 because the next biggest key is 15.
I could try and find key number '12', and if it doesn't exist, i would add one(12+1) and try again, until next key is found, but is there any function that could do this for me, or something even better/faster?
Edit: Clarification about the structure of the array.
Array is like it is in the example here. Keys are ordered as can be seen.
A simple foreach will do, but to guard against either empty arrays or a needle that's higher than the highest key, here's an implementation that will cover that as well:
function find(array $pricing, $needle)
{
$last = null; // return value if $pricing array is empty
foreach ($pricing as $key => $value) {
if ($key >= $needle) {
return $key; // found it, return quickly
}
$last = $key; // keep the last key thus far
}
return $last;
}
$result = find($pricing, 12); // 15
$result = find($pricing, 101); // 100
$result = find([], 12); // null
Assuming you are looking for the 'requiredKey' and that the array is sorted by key
This seem to do what you want.
Code:
<?php
$pricing = array(
"2" => 8.23,
"5" => 10.97,
"10" => 13.28,
"15" => 15.40,
"20" => 18.15,
"25" => 20.36,
"30" => 22.84,
"40" => 25.60,
"50" => 28.35,
"60" => 31.89,
"70" => 36.23,
"80" => 39.40,
"90" => 42.52,
"100" => 44.75
);
// What key we want...
$requiredKey = 12;
// outout in here
$foundKey = -1;
$foundValue = -1;
// always run the loop once...
do {
$foundKey = key($pricing); // store the current details
$foundValue = current($pricing);
next($pricing); // will be equal or greater
}
while ( current($pricing) !== false
&& $foundKey < $requiredKey);
echo '<pre>';
echo '<br />', 'key: ', $foundKey, ' value: ', $foundValue;
echo '</pre>';
Output:
key: 15 value: 15.4
Your logic is ok, you can do it using next()
http://php.net/manual/en/function.next.php
$search = 12;
$pricing = array(
"2" => 8.23,
"5" => 10.97,
"10" => 13.28,
"15" => 15.40,
"20" => 18.15,
"25" => 20.36,
"30" => 22.84,
"40" => 25.60,
"50" => 28.35,
"60" => 31.89,
"70" => 36.23,
"80" => 39.40,
"90" => 42.52,
"100" => 44.75
);
$result = null;
if (!isset($pricing[$search])) {
do {
} while (next($pricing) && $search > key($pricing));
$result = current($pricing);
} else {
$result = $pricing[$search];
}
echo $result;
Your lookup array will not change so it makes perfect sense to declare it as a constant (instead of a variable) and to make your code immediately understandable, give the constant a self-explanatory name.
const THRESHOLD_PRICES = [
2 => 8.23,
5 => 10.97,
10 => 13.28,
15 => 15.40,
20 => 18.15,
25 => 20.36,
30 => 22.84,
40 => 25.60,
50 => 28.35,
60 => 31.89,
70 => 36.23,
80 => 39.40,
90 => 42.52,
100 => 44.75
];
A valuable benefit of using a constant is that it is not necessary to explicitly pass the data into a function's scope.
In your function, I recommend type hinting the parameter and the return value -- again to make the code easier to understand.
Before iterating the array, do a quick scan of the keys for an exact match. Because of the way that php treats arrays as hashmaps, this is a very low-cost technique.
Next iterate the array and break the loop as soon as the array key is larger than the search value.
Regardless of if/when the loop breaks, the final iteration will declare the targeted price, so unconditionally return that float value.
function getThresholdPrice(int $search): float {
if (isset(THRESHOLD_PRICES[$search])) {
return THRESHOLD_PRICES[$search];
}
foreach (THRESHOLD_PRICES as $threshold => $price) {
if ($threshold > $search) {
break;
}
}
return $price;
}
Tests: (Demo)
foreach (range(1, 105, 13) as $test) {
printf("%d => %.02f\n", $test, getThresholdPrice($test));
}
Output:
1 => 8.23
14 => 15.40
27 => 22.84
40 => 25.60
53 => 31.89
66 => 36.23
79 => 39.40
92 => 44.75
105 => 44.75