How to conditionally merge a multi-dimensional array - php

I have a multi-dimensional array:
Array
(
[10] => Array
(
[from] => Jack
[to] => Terry
[Bribe] => 0
[Joke_Payment] => 0
[Corrupt_Support] => 1
[Legitimate_Support] => 0
[Obfuscation] => 1
[Legal_Enforcement] => 0
[Whistleblower] => 0
)
[11] => Array
(
[from] => Terry
[to] => Jack
[Bribe] => 0
[Joke_Payment] => 0
[Corrupt_Support] => 1
[Legitimate_Support] => 0
[Obfuscation] => 0
[Legal_Enforcement] => 1
[Whistleblower] => 0
)
)
I want to update the above array to like this: have a 1 (going from from to to) and a -1 for the opposite direction, a 2 signify for "both directions.
Array ( [10] => Array
(
[from] => Jack
[to] => Terry
[Bribe] => 0
[Joke_Payment] => 0
[Corrupt_Support] => 2
[Legitimate_Support] => 0
[Obfuscation] => 1
[Legal_Enforcement] => -1
[Whistleblower] => 0
) }
How can I firstly calculate their intersect and then update the original array?
They will have the same amount to keys, and when from matches to, and to matches from element, I want to combine these two arrays into one. A '1' is used to indicate that the property relates in the from to to direction, and a -1 indicates that the property goes in the reverse direction (from to to from).
My current code is:
$fileRelation = explode("\r", $end);
$rowsRelation = array_map('str_getcsv', $fileRelation);
$headerRelation = array_shift($rowsRelation);
$csvArrRelation = array();
$countR = count($headerRelation);
foreach ($rowsRelation as $key => &$row) {
$index = 2;$sum = 0;$sumArr = array();
while ($index < $countR) {
if ($row[$index]>0) {
$sumArr[$sum]=$index; //store row $headerRelation index
$sum++;
}
$index++;
}
if($sum > 0){ //remove element if no relationship exist
foreach ($csvArrRelation as $k => $a) {
if (in_array($row[0], $csvArrRelation[$k]) && in_array($row[1], $csvArrRelation[$k])){
$p = array_values($a);$i = 2;$cp= count($p);
while($i < $cp ){
if($row[$i] == $p[$i]){
$a[$headerRelation[$i]] += $row[$i];
}else{
$a[$headerRelation[$i]] -= $row[$i];
}
$i++;
}
unset( $rowsRelation[$key] );
}
}
$csvArrRelation[] = array_combine($headerRelation, $row);
}
}

I won't write this for you, but here is a good start:
$newRelations = [];
$deletions = [];
foreach ($rowsRelation as $key1 => $row1)
{
foreach ($rowsRelation as $key2 => $row2)
{
// Don't compare with self
if ($key1 != $key2)
{
// Have we found a reverse match?
if (
$row1['from'] == $row2['to'] &&
$row1['to'] == $row2['from']
)
{
$newRelations[] = myMerge($row1, $row2);
$deletions[] = $key1;
$deletions[] = $key2;
}
}
}
}
// Delete old rows
foreach ($deletions as $deleteKey)
{
unset($rowsRelation[$deleteKey]);
}
// Add in new rows
$rowsRelation = array_merge($rowsRelation, $newRelations);
function myMerge(array $row1, array $row2)
{
// todo
}
My strategy is to compare each row with every other, and if a reverse match is found, we know a merge is due. To avoid corrupting the foreach loops, I add the new array value to a different array, and log which keys I wish to delete from the original. These are then merged/deleted after the work is done.

I assume this is some kind of game, so let's call "player 1" the player whose name appears in "from" field and "player 2" the one whose name appears in "to" field.
According to what I understood, you consider player 1's values positive and player 2's negative. Then you sum both values in "from player 1 to player 2" record and discard "from player 2 to player 1" record.
According to that rule, Corrupt_Support should end up with a value of 0, not 2.
Basically, after merging your records, you will not be able to tell the difference between no actions at all and actions that have cancelled each other.
Besides, there is no rule to chose a "winner", i.e. which "from" player will be retained, leading to potentially impractical results.
If you have 3 players Abe, Bob and Cal, you can end up with any possible order, for instance
["Abe vs Bob", "Bob vs Cal" and "Cal vs Abe"] or
["Abe vs Bob", "Abe vs Cal" and "Cal vs Bob"].
Looking up actions for Bob will be rather problematic in the latter case.
Now if you still want to do that, here is a way that takes advantage of PHP hash tables to speed up the merge and choses the "winner" according to alphabetic order:
// use "from player 1 to player 2" relation as key
foreach ($relations as $r) $relation_pairs[$r['from'].'+'.$r['to']] = $r;
// pick the "winners" according to alphabetic order
ksort ($relation_pairs);
// merge relations
while (list($k,$r) = each($relation_pairs)) // this will take elements deletion into account immediately, contrary to foreach
{
$symetric = $r['to'].'+'.$r['from'];
if (isset($relation_pairs[$symetric])) // found a symetric relation
{
foreach ($relation_pairs[$symetric] as $field=>$value) // combine all numeric fields
if (is_numeric ($value))
$relation_pairs[$k][$field] -= $value;
unset ($relation_pairs[$symetric]); // get rid of symetric relation
}
}
$compressed_relations = array_values ($relation_pairs); // get rid of keys

Related

How to check if value is equal to string in array

I want to check if the value: diam-mm exist in array, if the value not exist do something.
A array can have multiple properties, property name is: [PropertyType]->[Name]
i thought i loop to the properties and check if diam-mm value exist, else do something but because of the loop he does import the value mutliple times instead of one time.
Example of one array with properties:
[2] => Array
(
[PropertyType] => Array
(
[Guid] =>
[DataType] => Text
[Name] => diam-mm
[Unit] =>
)
[BooleanValue] =>
[DateTimeValue] =>
[NumericValue] =>
[TextValue] => 400
[XmlValue] =>
[UrlValue] => 400
)
[3] => Array
(
[PropertyType] => Array
(
[Guid] =>
[DataType] => Text
[Name] => lengte-mm
[Unit] =>
)
[BooleanValue] =>
[DateTimeValue] =>
[NumericValue] =>
[TextValue] => 2000
[XmlValue] =>
[UrlValue] => 2000
)
<?php
for ($i=0; $i <count($array) ; $i++) {
if($array[$i]['PropertyType']['Name']=="diam-mm"){
// your code
}
}
?>
If you want to check if an array key matches a value you can do so using simple variable assignment. However you would need to loop through each array index item and enumarate the loop based on it's iteration.
To create the loop I would suggest using count to count the amount of items in the array. We will assign the result to a variable :
$count = count($my_array);
Do keep in mind that count only counts the number of items based on their actual count, not their array index. This means that an array with indexes starting from zero which has an index of 0-30 would return 31 as the result of count because count counted the zero index as an actual count value.
To fix this we need to subtract 1 from the result of count :
$count = $count - 1;
Then we can use the count as the number of repeats in a for loop. Where the variable $i represents the iteration that the loop is going through :
//Loop through each array index
for($i=0; $i <= $count; $i++){
//Assign the value of the array key to a variable
$value = $my_array[$i]['PropertyType']['Name'];
//Check if result string contains diam-mm
if(str_contains($value, 'diam-mm'){
echo 'The value matches!';
} else{
echo 'The value does not match!';
}
}
Try this function, i hope this answer your question...
function array_recursive_search_key_map($needle, $haystack) {
foreach($haystack as $first_level_key=>$value) {
if ($needle === $value) {
return array($first_level_key);
} elseif (is_array($value)) {
$callback = $this->array_recursive_search_key_map($needle, $value);
if ($callback) {
return array_merge(array($first_level_key), $callback);
}
}
}
return false;
}
How to use
$yourValue = "diam-mm";
$array_keymap = array_recursive_search_key_map($yourValue, $yourArray);
var_dump($array_keymap);
Output
Array
(
[0] => 0
[1] => PropertyType
[2] => Name
)

Distribute associative array elements into groups with a maximum sum per group

I need to split my associative array into bunches of not greater than 50 in each bunch. Multiple elements may be pushed into a given group to ensure that a group reaches 50 before starting a new group.
Sample input:
$array = [
'5' => 142,
'2' => 57,
'18' => 37
];
Desired result:
[
['5' => 50],
['5' => 50],
['5' => 42, '2' => 8],
['2' => 49, '18' => 1],
['18' => 36],
];
just mind games
$line = [];
// Invert array
foreach($arr as $k=>$v) {
$line = array_merge($line, array_fill(0, $v, $k));
}
// Split and count occurrences
$res = array_map('array_count_values', array_chunk($line, 50));
print_r($res);
demo
To create an array where the sum of each entry would not exceed a given amount, you can use an iterative approach.
Let's start with an empty array and a variable representing the working index of that array. As we go through the input array, we add the maximum possible remaining quantity to the new array. If we reach the limit, we increment the index variable. And we continue as long as the input array has not been completely browsed.
Code:
const MAX_SUM = 50;
$total = []; // Store the new data
$curKey = 0; // Store the current key of $total array.
foreach ($array as $key => $value) {
while ($value) {
// Get the current sum for the current key:
$curSum = array_sum($total[$curKey] ?? []);
// If the max was reached, we can go to the next key:
if ($curSum == MAX_SUM) $curKey++;
// Now, compute if the value to add (max to reach 50);
$add = $value + $curSum > MAX_SUM // If above,
? MAX_SUM - $curSum // got only the difference,
: $value; // else, use the full value.
// Add the value
$total[$curKey][$key] = $add;
// Finally, remove what we added just before.
$value -= $add;
}
}
print_r($total);
Outputs :
Array (
[0] => Array (
[5] => 50
)
[1] => Array (
[5] => 50
)
[2] => Array (
[5] => 42
[2] => 8
)
[3] => Array (
[2] => 49
[18] => 1
)
[4] => Array (
[18] => 36
)
)
See also a the nice answer of #mickmackusa.
My train of thought for this task aligns with #Syscall's "push & consume" approach.
Iterate the input array to access the keys and values.
Use an inner loop to repeatedly process the current value until it is fully consumed by subtraction. Only break the loop upon the value being reduced to zero.
With each pass of the inner loop:
Calculate the current total of all values in the group, then
Find the lesser integer value between how much room is left in the group and how much is left in the value; push that integer into the group with the current key; then subtract that integer from the current value, then
Increment the group key if the current group has reach its limit
Repeat until all input elements are reduced to zero.
Code: (Demo)
$groupLimit = 50; // declare as a variable to avoid "magic numbers" in code
$result = [];
$groupKey = 0;
foreach ($array as $key => $value) {
while ($value) {
$sum = array_sum($result[$groupKey] ?? []); // get group sum
$value -= $result[$groupKey][$key] = min($groupLimit - $sum, $value); // push key with limited value; decrease value
$groupKey += ($sum + $result[$groupKey][$key] === $groupLimit); // only increment group key if at $groupLimit
}
}
var_export($result);

How to make an array using other 2 array

$champions array =
Array (
[0] => Shen
[1] => Graves
[2] => Lux
[3] => Tristana
[4] => Janna
[5] => Lissandra
[6] => RekSai
[7] => Anivia
[8] => Lucian
[9] => Alistar )
This array has always 10 values.
$fbps array =
Array (
[0] => RekSai
[1] => Alistar
[2] => Lucian )
This array has always 1-5 values.
What i want to make
Array (
[0] => 0
[1] => 0
[2] => 0
[3] => 0
[4] => 0
[5] => 0
[6] => 1
[7] => 0
[8] => 1
[9] => 1 )
My english is bad to explain this, i hope arrays are enough to tell. Sorry for bad title and explanation.
Edit: Ill try to explain it more. For example Shen's key is 0 in first array. $fbps array doesnt have a value named "Shen" so in third array 0 => 0. Lucian's key is 8 in first array. fbps have a value named Lucian. So third arrays 8th key has value "1"
$cArr = array('Shen','Graves','Lux','Tristana','Janna','Lissandra','RekSai','Anivia','Lucian','Alistar');
$fbps = array('RekSai','Anivia','Lucian');
foreach ($cArr as $key=>$value) {
if(array_search($value, $fbps) !== false) {
$cArr[$key] = 1;
} else {
$cArr[$key] = 0;
}
}
var_dump($cArr);
Or a more compact version:
$cArr = array('Shen','Graves','Lux','Tristana','Janna','Lissandra','RekSai','Anivia','Lucian','Alistar');
$fbps = array('RekSai','Anivia','Lucian');
foreach ($cArr as $key=>$value) {
$cArr[$key] = (array_search($value, $fbps) !== false) ? 1 : 0;
}
var_dump($cArr);
EDIT:
added in the !== false conditional as matches found in position 0 of the $fbps array incorrectly evaluated to false because 0 also = false in PHP land...
EDIT 2:
This function has O(N) complexity, meaning it'll grow linearly and in direct proportion to the size of the input data set.
Does the resulting array just have a value of 1 for every element of $fbps that appears in $champions? If so, something like this should do it;
$champions = ['Shen', 'Graves', 'Alister', '...'];
$fbps = ['Shen', 'Alister', '...'];
$result = array_map(function($value) use ($fbps) {
return (int)in_array($value, $fbps);
}, $champions);
I know you've already accepted an answer but here is the most efficient solution:
<?php
// Your arrays
$champions = array('Shen','Graves','Lux','Tristana','Janna','Lissandra','RekSai','Anivia','Lucian','Alistar');
$fbps = array('RekSai','Alistar','Lucian');
// New array which will store the difference
$champ_compare = array();
// Flip the array so that it is associative and uses the names as keys
// http://php.net/manual/en/function.array-flip.php
$fbps = array_flip($fbps);
// Loop all champions and use $v as reference
foreach($champions as &$v)
{
// Check for the existent of $v in the associative $fbps array
// This is leaps and bounds faster than using in_array()
// Especially if you are running this many times with an unknown number of array elements
$champ_compare[] = (int)isset($fbps[$v]);
}
unset($v);
// Flip it back if you need to
$fbps = array_flip($fbps);
print_r($champ_compare);
If you just want the most compact code and do not care about performance then you can try this:
<?php
// Your arrays
$champions = array('Shen','Graves','Lux','Tristana','Janna','Lissandra','RekSai','Anivia','Lucian','Alistar');
$fbps = array('RekSai','Alistar','Lucian');
// New array which will store the difference
$champ_compare = array();
// Loop all champions and use $v as reference
foreach($champions as &$v)
{
// Check if the current champion exists in $fbps
$champ_compare[] = (int)in_array($v, $fbps);
}
unset($v);
print_r($champ_compare);
Not as detailed as Garry's answer, but is based on your existing arrays.
$res = array()
foreach($champions as $key => $val) {
$res[$key] = (in_array($val, $fbps)) ? 1 : 0;
}
var_dump($res)
Edit: I've switched the array_key_exists to in_array.
<?php
$champions = array('Shen', 'Graves', 'Lux', 'Tristana', 'Janna', 'Lissandra', 'RekSai', 'Anivia', 'Lucian', 'Alistar');
$fbps = array('RekSai', 'Alistar', 'Lucian');
$res = array();
foreach($champions as $key => $val) {
$res[$key] = (in_array($val, $fbps)) ? 1 : 0;
}
var_dump($res);

Comparing array values of just 1 array

Here is my array:
Array
(
[0] => Array
(
[0] => Array
(
[0] => West
[1] => 4
)
[1] => Array
(
[0] => South west
[1] => 20
)
)
)
What I want to achieve is be able to compare $array[0][0][1] and $array[0][1][1]. So whichever is lesser comes first and I get the value. So in this case I know that 4 is lesser so I get West first then South west is next. It should be dynamic since we can get more than 2.
This a simple example you can use :
$arr = array(
array("name"=>"Bob","age"=>8,"colour"=>"red"),
array("name"=>"Greg","age"=>12,"colour"=>"blue"),
array("name"=>"Andy","age"=>5,"colour"=>"purple"));
$sortArray = array();
foreach($arr as $val){
foreach($val as $key=>$value){
if(!isset($sortArray[$key])){
$sortArray[$key] = array();
}
$sortArray[$key][] = $value;
}
}
$orderby = "age"; //change this to whatever key you want from the array
array_multisort($sortArray[$orderby],SORT_ASC,$arr);
print_r($arr);
Assuming the depth of the array structure remains the same, and you only want the very lowest, you can use a simple foreach.
function findLowestName($array) {
$lowest = 0; // variable to hold the index of the lowest entry.
// go through every entry in the first array member
foreach($array[0] as $key => $value) {
// if this entry is lower than the 'current' lowest, mark this as the new lowest
if($value[1] < $array[0][$lowest]) {
$lowest = $key;
}
}
return $array[0][$lowest][0];
}
Built as a function, so you can include it, and call the function, passing it the array as a parameter.

PHP 3D array count unique values

I have a 3D array. I need to extract the following:
the number of occurrences of unique items in deepest level array
the number of co-occurrences of certain items in deepest level array
Array looks like this:
$arr = (
[0] => array([0] => array([flag][token][value])
[1] => array([flag][token][value])
)
)
I tried looping through the array using a for loop and an if statement, like this:
$new_arr1 = ""; // $new_arr1 = array(); //same thing, the later code doesn't work
for ($i=0; $i<count($arr);$i++)
{
for ($j=0; $j<count($arr[$i]); $j++)
{
$value= $arr[$i][$j][value];
if ($arr[$i][$j][flag] == "noun")
array_push($new_arr1, $value)
elseif ($arr[$i][$j][flag] == "verb")
array_push($new_arr2, $value)
}
}
did the same thing with switch statement, couldn't get it to work. By doing this kind of conditioning I was hoping to get all of the values from the deepest level array that meet the certain criteria into one array, and then perform *array_count_values* on them. That would give me the following:
for flag == "noun", value of value element stored in $new_arr1, and for flag == "verb", value of value element stored in $new_arr2:
$new_arr1 = (
[dog] => 7
[cat] => 2
[horse] => 4
);
$new_arr2 = (
[chase] => 2
[eat] => 5
[pwn] => 3
);
this would give me a count of unique values of the deepes level array that I can't access using *array_uniqe* function.
Besides that, I need to perform a count of co-occurrences of these values, something like this:
count the number of times that some token with certain flag comes before or after another token with a certain flag:
pseudocode:
$count=0;
if ( (($arr[$i][$j][flag] == "noun") && ($arr[$i][$j][value] == "dog")) &&
(($arr[$i][$j+/-1][flag] == "verb") && ($arr[$i][$j+/-1][value] == "chase"))
)
$count++;
$counter = array($count = array($arr[$i][$j][value]), $arr[$i][$j+/-1][value])
that would basically give the following:
counter:
array(
"7" => ([0] => "dog", [1] => "chase")
"4" => ([0] => "cat", [1] => "eat")
)
where the "7" and "4" are the names of the associative array, while "dog" and "chase" are the values stored in another array.
)
I'm not sure if I follow your question 100%, but something like this might work:
$verb_array = array();
$noun_array = array();
foreach($arr as $index => $data){
if($data['flag'] == 'noun'){
if(isset($noun_array[$data['value']])){
$noun_array[$data['value']]++;
}
else{
$noun_array[$data['value']] = 0;
}
}
else if($data['flag'] == 'verb'){
if(isset($noun_array[$data['value']])){
$verb_array[$data['value']]++;
}
else{
$verb_array[$data['value']] = 0;
}
}
}
Then to get the count of occurrences:
$noun_count = count($noun_array);
$verb_count = count($verb_array);
And if you need the count of a particular item, it will exist inside of the array.

Categories