Check if all arrays inside an array have a specific key - php

Example array:
$items = array(
array('sort': 1, 'name': 'name'),
array('sort': 3, 'name': 'name'),
array('sort': null, 'name': 'name')
);
I need to know if all child arrays have the sort key. If not, I'd manually create the sort via a for loop (and over-write the sort value for the ones that have it).
I'm already running a for loop, and I was thinking of adding another key such as manual_sort which I would equal to index + 1, and after the loop, if at least 1 array didn't have the sort key or if it's null), I'd use the manual_sort instead of the sort key (for example when looping the elements via the front-end or whatever usage the data has).
Also thought about doing a second loop to know whether or not sort should be overwritten.
These seems like dirty solutions though, any ideas?
My current code looks something like:
$db_sorted_items = true;
for ($i=0; $i < count($items); $i++) {
$items[$i]['name'] = ucfirst($items[$i]['name']);
if (empty($items[$i]['sort']) {
$db_sorted_items = false;
}
$items[$i]['number'] = $i + 1; // $i+1 because sort number starts at 1.
}
// err, gotta use `$items[$i]['number']` (or `sort` had all arrays had a positive `sort` key)

// use array_column to get array for key 'sort'
// then use array_filter to remove null values
// then compare count
if(count($array) == count(array_filter(array_column($array,'sort')))){
echo 'All have valid sort key';
}else{
echo 'No, few are missing';
}
In case if you want to have strict check, to make sure value corresponding to key sort is numeric then you may use below one, using is_numeric
if(count($array)==count(array_filter(array_column($array,'sort'),'is_numeric'))){
}

Your code looks good to me. I'd change it so it refreshes all sort attributes if it finds one object that doesn't have it, like this:
$db_sorted_items = true;
for ($i=0; $i < count($items); $i++) {
$items[$i]['name'] = ucfirst($items[$i]['name']);
if ($db_sorted_items && empty($items[$i]['sort']) {
$db_sorted_items = false;
$i = -1; // Re-start for (the for will add 1 at the end)
}else{
$items[$i]['sort'] = $i + 1; // $i+1 because sort number starts at 1.
}
}

Related

Generate combinations and choose best based on three parameters

I'm trying to generate pairs and then would like to choose the best pair based on set parameters. Generating pairs isn't that hard, but what's tricky is to select the best out of them.
I think it'd be best if i'd continue with example, let's take we currently have 4 elements and name them element1,element2,element3,element4. Each element has properties which are important when generating pairs:
while ($element = mysqli_fetch_assoc($queryToGetElements)){
$elementId = $element['eid'];
$elementOpponents = getElementOpponents($elementId); //Returns opponents Id's as an array. These are the possible elements which can be paired.
$elementPairs = generatePairsWithElement($elementId,$elementOpponents); //Pair generating function, this uses the possible pairs which is defined below. Pretty much checks who hasn't paired before and puts them together in the pair.
$bestPair = chooseBest($elementPairs,$allOtherPairs) // This should select the best pair out of the elementPairs considering uniqueness with other pairs.
}
//So let's define our four elements:
$element1Place = 1;
$element2Place = 2;
$element3Place = 3;
$element4Place = 4;
//execute while loop with $element1:
$element1Opponents = [$element2,$element3,$element4];
$elementPairs = [[$element1,$element3],[$element1,$element3],[$element1,$element4]];
$element2Opponents = [$element3]
$elementPairs = [[$element2,$element3],[$element2,$element1]];
$elemenet3Opponents = [$element2]
$elementPairs = [[$element2,$element3],[$element1,$element3]];
$element4Opponents = [$element1]
$elementPairs = [[$element1,$element4];
//Pairs returned should be: [$element1,$element4] & [$element2,$element3].
Possible pairs - This is an array of other elements which can be paired with current element. With current example I do have 4 elements, but some of them can not be paired together as they may have been paired together previously (and it's not allowed to pair someone together twice). This constraint is applied in function generatePairsWithElements.
Place - This is an unique integer value which cannot be same for two elements. When choosing paired element, the element with lower Place will be selected. This constraint is applied in function chooseBest.
Combinations has to be unique - For example if element1 can pair with either element2 and element3 and element4 can pair with only element2 then element1 can only pair with element3 even if element1 is iterated earlier and element2 has lower place. So the combinations would be [element1,element3] and [element2,element4]. This constraint is applied in function chooseBest.
This task wouldn't be so difficult if there wouldn't be the third aspect, generating unique combinations. It would be a pretty easy to iterate over the possible opponents and just choose the best one however generating unique combinations is vital for my project.
So I want to preface this answer by a disclaimer: I am not a mathematician; my understanding of linear algebra, matrix algebra and statistic is adequate but by no means extensive. There may be ways to achieve the same results with fewer lines of code or more efficiently. However, I believe that the verbosity level of this answer will allow more people to understand it, and follow the logic step by step. Now that that's out of the way, let's jump into it.
Calculate the Weight of Each Pair
The problem with the current approach is that the logic for finding the best possible match is happening as you loop through the results of the query. What that means is that you may be left with elements that can't be matched together when you get to the last iteration. To fix this, we're going to need to split the process a bit. The first step is to get all of the possible pairs for the given array elements.
$elements = [
1,2,3,4,5,6
];
$possiblePairs = [];
for($i = 1; $i <= $elementCount; $i++) {
for($j = $i + 1; $j <= $elementCount; $j++) {
$possiblePairs[] = [
'values' => [$elements[$i - 1], $elements[$j - 1]],
'score' => rand(1, 100)
];
}
}
As you can see, for each possible pair, I am also attaching a score element, a random integer in this case. This score represents how strongly this pair matches. The value of this score doesn't actually matter: it could be values in a specific range (ex: 0-100) or every value defined by some functions of your own. The important thing here is that pairs that have highest match potential should have a higher score, and pairs with little to no potential should have a low score. If some pairs absolutely cannot go together, simply set the score to zero.
The next step is to then use than score value to sort the array so the the pairs that are more strongly matched are on top, and the weaker (or impossible) pairs at the bottom. I used PHP 7.0's spaceship operator to get the job done, but you can find other ways to achieve this sort here.
usort($possiblePairs, function($a, $b) {
return $b['score'] <=> $a['score'];
});
Now we're finally equipped to build our final output. This step is actually fairly easy: loop through the possible pairs, check if the values haven't already been used, then push them to the output array.
$used = []; // I used a temporary array to store the processed items for simplicity
foreach($possiblePairs as $key => $pair) {
if($pair['score'] !== 0 && // additional safety if two elements cannot go together
!in_array($pair['values'][0], $used) &&
!in_array($pair['values'][1], $used))
{
$output[] = $pair['values']; // push the values to the return of the function
array_push($used, $pair['values'][0], $pair['values'][1]); // add the element to $used so they get ignored on the next iteration
}
}
var_dump($output);
// output
array(3) {
[0]=> array(2) {
[0]=> int(2)
[1]=> int(4)
}
[1]=> array(2) {
[0]=> int(1)
[1]=> int(3)
}
[2]=> array(2) {
[0]=> int(5)
[1]=> int(6)
}
}
And there it is! The strongest pair is chosen first, and then it goes down in priority. Play around with weighting algorithms, see what works for your specific needs. As a final word: if I were writing this in the context of a business application, I would probably add some error reporting if there happens to be un-matcheable pairs (after all, it is statistically possible) to make it easier to spot the cause of the problem and decide if the weighting needs tweaking.
You can try the example here
Edit to add:
In order to prevent the scenario where an element gets stored in a pair when another element can only be paired with that first element (which results in the pair never being created) you can try this little patch. I started by recreating your requirements in a getScore() function.
function getScore($elem1, $elem2) {
$score;
if($elem1 > $elem2) {
$tmp = $elem1;
$elem1 = $elem2;
$elem2 = $tmp;
}
if($elem1 === 1 && $elem2 === 2) {
$score = 100;
}
elseif(($elem1 === 3 && $elem2 !== 2) || ($elem1 !== 2 && $elem2 === 3)) {
$score = 0;
}
else {
$score = rand(0, 100);
}
return $score;
}
Then, I modified the $possiblePairs array creation to do two additional things.
if the score is 0, don't append to the array and
keep track of how many matches were found for each element. By doing this, any element that only has one possible match will have an associated value in $nbMatches of 1.
$nbMatches = [
1 => 0,
2 => 0,
3 => 0,
4 => 0
]
$possiblePairs = [];
for($i = 1; $i <= $elementCount; $i++) {
for($j = $i + 1; $j <= $elementCount; $j++) {
$score = getScore($elements[$i - 1], $elements[$j - 1]);
if($score > 0) {
$possiblePairs[] = [
'values' => [$elements[$i - 1], $elements[$j - 1]],
'score' => $score
];
$nbMatches[$elements[$i - 1]]++;
$nbMatches[$elements[$j - 1]]++;
}
}
}
Then I added another loop that will bump up those elements so that they end up on top of the list to be processed before the rest.
foreach($nbMatches as $elem => $intMatches) {
if($intMatches === 1) {
foreach($possiblePairs as $key => $pair) {
if(in_array($elem, $pair['values'])) {
$possiblePairs[$key]['score'] = 101;
}
}
}
}
usort($possiblePairs, function($a, $b) {
return $b['score'] <=> $a['score'];
});
The output is then:
array(2) {
[0]=> array(2) {
[0]=> int(2)
[1]=> int(3)
}
[1]=> array(2) {
[0]=> int(1)
[1]=> int(4)
}
}
I just want to stress: this is only a temporary patch. It will not protect you in cases where, for instance, element 1 can only match element 2 and element 3 can also only match element 2. However, we are dealing with a very small sample size, and the more elements you have, the less likely these edge case will be likely to occur. In my opinion this fix is not necessary unless you are only working with 4 elements. working with 6 elements yields 15 possible combinations, 10 elements yields 45 possible combinations. Already those cases will be unlikely to happen.
Also, if you find that those edge cases still happen, it may be a good idea to go back and tweak the matching algorithm to be more flexible, or take into account more parameters.
You can try the updated version here
So i think i got it figured out, thanks to William and thanks to ishegg from another thread.
So as a prequisite i have an array $unmatchedPlayers which is an associative array where element name is a key and element position (Place) is a value.
First off using that info, i'm generating unique permutation pairs
function generatePermutations($array) {
$permutations = [];
$pairs = [];
$i = 0;
foreach ($array as $key => $value) {
foreach ($array as $key2 => $value2) {
if ($key === $key2) continue;
$permutations[] = [$key, $key2];
}
array_shift($array);
}
foreach ($permutations as $key => $value) {
foreach ($permutations as $key2=>$value2) {
if (!in_array($value2[0], $value) && !in_array($value2[1], $value)) {
$pairs[] = [$value, $value2];
}
}
array_shift($permutations);
}
return $pairs;
}
This will return me an array called $pairs which has arrays of different possible pairs inside in a manner of: $pairs= [[['One','Two'],['Three','Four']],[['One','Three'],['Two','Four']],[['One','Four'],['Two','Three']]];
Now i will iterate over the array $pairs and choose the best, giving each permutation combination a 'score':
function chooseBest($permutations,$unmatchedPlayers){
$currentBest = 0;
$best = [];
foreach ($permutations as &$oneCombo){ //Iterate over all permutations [[1,2],[3,4]],[..]
$score = 0;
foreach ($oneCombo as &$pair){
$firstElement = $pair[0];
$secondElement = $pair[1];
//Check if these two has played against each other? If has then stop!
if(hasPlayedTP($firstElement,$secondElement)){
$score = 0;
break;
}
$score += $unmatchedPlayers[$firstElement];
$score += $unmatchedPlayers[$secondElement];
}
if($score > $currentBest){
$currentBest = $score;
$best = $oneCombo;
}
}
return $best;
}
The best score is calculated and permutation pair is returned.

PHP / json encode wrongly interprets as associative array

Edit1: The problem: I want to convert in php a associative array to a indexed one. So I can return it via json_encode as an array and not as an object. For this I try to fill the missing keys. Here the description:
Got a small problem, I need to transfer a json_encoded array as an array to js. At the moment it returns an Object. I´m working with Angular so I really need an Array. I try to explain it as much as possible.
$arrNew[0][5][0][0][1]["id"] = 1;
//$arrNew[0][0][0][0][1] = "";
//$arrNew[0][1][0][0][1] = "";
//$arrNew[0][2][0][0][1] = "";
//$arrNew[0][3][0][0][1] = "";
//$arrNew[0][4][0][0][1] = "";
$arrNew[0][5][0][0][1]["name"] = 'Test';
var_dump($arrNew);
So if I return it now It returns the second element as object cause of the missing index 0-4 and the 4th element cause of the missing index 0 (associative array -> object)
So if I uncomment the block it works like a charm. Now I have the problem its not every time the element 5 sometime 3, 4 or something else so I build a function which adds them automaticly:
$objSorted = cleanArray($arrNew);
function cleanArray($array){
end($array);
$max = key($array) + 1; //Get the final key as max!
for($i = 0; $i < $max; $i++) {
if(!isset($array[$i])) {
$array[$i] = '';
} else {
end($array[$i]);
$max2 = key($array[$i]) + 1;
for($i2 = 0; $i2 < $max2; $i2++) {
.... same code repeats here for every index
So if I vardump it it returns:
The problem:
On js side its still an object, what I also see is that the elements are not sorted. So I think somehow PHP sees it still as an associative array. Any clue why this happens ? The key is set with the index of the loop and has to be a integer value.
PS: I know reworking it in JS is possible but would have be done nearly on every request with a huge load of loops
If I understand your problem, you create a sparse multidimensional array of objects. Because the arrays have gaps in the keys, json_encode() produces objects on some levels but you need it to produce arrays for all but the most inner level.
The following function fills the missing keys (starting from 0 until the maximum value used as numeric key in an array) on all array levels. It then sorts each array by their keys to make sure json_encode() encodes it as array and not object.
The sorting is needed, otherwise json_encode() generates an object; this behaviour is explained in a note on the json_encode() documentation page:
When encoding an array, if the keys are not a continuous numeric sequence starting from 0, all keys are encoded as strings, and specified explicitly for each key-value pair.
// If $arr has numeric keys (not all keys are tested!) then returns
// an array whose keys are a continuous numeric sequence starting from 0.
// Operate recursively for array values of $arr
function fillKeys(array $arr)
{
// Fill the numeric keys of all values that are arrays
foreach ($arr as $key => $value) {
if (is_array($value)) {
$arr[$key] = fillKeys($value);
}
}
$max = max(array_keys($arr));
// Sloppy detection of numeric keys; it may fail you for mixed type keys!
if (is_int($max)) {
// Fill the missing keys; use NULL as value
$arr = $arr + array_fill(0, $max, NULL);
// Sort by keys to have a continuous sequence
ksort($arr);
}
return $arr;
}
// Some array to test
$arrNew[0][5][0][0][1]["id"] = 1;
$arrNew[0][3][0][2][1]["id"] = 2;
$arrNew[0][5][0][0][1]["name"] = 'Test';
echo("============= Before ==============\n");
echo(json_encode($arrNew)."\n");
$normal = fillKeys($arrNew);
echo("============= After ==============\n");
echo(json_encode($normal)."\n");
The output:
============= Before ==============
[{"5":[[{"1":{"id":1,"name":"Test"}}]],"3":[{"2":{"1":{"id":2}}}]}]
============= After ==============
[[null,null,null,[[null,null,[null,{"id":2}]]],null,[[[null,{"id":1,"name":"Test"}]]]]]
The line $arr = $arr + array_fill(0, $max, NULL); uses NULL as values for the missing keys. This is, I think, the best for the Javascript code that parses the array (you can use if (! arr[0]) to detect the dummy values).
You can use the empty string ('') instead of NULL to get a shorter JSON:
[["","","",[["","",["",{"id":2}]]],"",[[["",{"id":1,"name":"Test"}]]]]]
but it requires slightly longer code on the JS side to detect the dummy values (if (arr[0] != '')).

Compare array key, if true display the value PHP

I'm trying to compare the array key with another array, and if they are the same, I need to display the value of that key.
This is my array.
$events = array( 0 => var1,
1 => var2,
2 => var3
);
Lets assume that the $get_date is the key and the $get_name is the value, I want to compare the $get_date to another value, IF TRUE, it will display the value of the combine array. Just ignore the array_combine, they still have the same output anyway. Ignore also the variable used.
$events = array_combine($get_date, $get_name);
for($i=0; $i<=5; $i++)
{
/* this is where I want the comparing to be done.
/* this is the confusing part. Not sure what to do.
}
Try using array_key_exists
$result =array();
for($i=0; $i<=5; $i++){
if (array_key_exists($i, $events)) {
$result[] = $events[$i];
}
}
See demo here

php unique randomized numbers array with exception

I'm trying to generate a unique randomized array with exceptions,
i got this far:
function rand_except($min, $max,$no_numbers, $except) {
//loop until you get a unique randomized array without except number
$end=false;
while (!$end){
$numbers = rand_array($min, $max,$no_numbers);//get unique randomized array
if(!in_array($except,$numbers)){
$end=true;
break;
}
}
return $numbers;
}
but now i want the function to loop until the except parameter isn't in the array
I suspect it would be easier to solve this problem by updating the rand_array function (or writing a modified version of it) to generate the array without the $except value to start with.
If that is not an option, here is one possible solution that doesn't involve calling the rand_array function over and over again:
$numbers = rand_array($min, $max-1, $no_numbers);
for ($i = 0; $i < count($numbers); $i++) {
if ($numbers[$i] >= $except) {
$numbers[$i] += 1;
}
}
You call the rand_array function but specify one less than the actual maximum number you want in the array. Then you loop over the results, and any value that is greater than or equal to the $except value, you increment by 1.
This is assuming that the $except value is in the range $max to $max. If not, you can just return rand_array($min, $max, $no_numbers); as is.

php arrays get the order number of a certain element

Lets say i have an array in PHP
$test['michael_unique_id'] = 3;
$test['john_unique_id'] = 8;
$test['mary_unique_id'] = 10;
.
.
.
.
$test['jimmy_unique_id'] = 4;
(the values (3,8,10.........4) are unique)
Lets say i want to search for the unique id 10, and get the order of the matching element in the array. In this example, the third element has the value 10, so i should get the number 3.
I can do it by scanning with a for loop looking for the value i'm searching and then get the $i value when i have a match, but i wonder if there is any built-in function (or a better method) that implements this.
You can get all of the array's values as an array with array_values() and then use array_search() to get the position (offset) of that value in the array.
$uniq_id = 10;
$all_vals = array_values($test); // => array(3, 8, 10, ... )
echo array_search( $uniq_id, $all_vals ); // => 2
Because PHP array indices are zero-based, you'll get 0 for the first item, 1 for the second item, etc. If you want the first item to be "1," then just add one. All together now:
$uniq_id = 10;
echo array_search( $uniq_id, array_values( $test ) ) + 1; // => 3
It's not clear to me, however, that this is necessarily as performant as just doing a foreach:
$uniq_id = 10;
$idx = 1;
foreach($test as $val) {
if($val == $uniq_id) {
break;
}
$idx++;
}
echo $idx; // => 3
Well, array_search will give you the key, but in your case, you want the index of that key, so I believe a loop is your best bet. Is there a good reason why you need the index? It doesn't seem very useful to me.

Categories