Conditional remove adjacent duplicates from array - php

I have following code that removes adjacent duplicates from the $myArray
<?php
$myArray = array(
0 => 0,
1 => 0,
2 => 1,
5 => 1,
6 => 2,
7 => 2,
8 => 2,
9 => 0,
10 => 0,
);
$previtem= NULL;
$newArray = array_filter(
$myArray,
function ($currentItem) use (&$previtem) {
$p = $previtem;
$previtem= $currentItem;
return $currentItem!== $p ;
}
);
echo "<pre>";
print_r($newArray);
?>
It works perfectly fine, but I have to change a condition bit for value 2. That means for other values we can pick first occurrence and ignore the others. But for 2, we need to pick last occurrence and ignore others.
So required output is
Array
(
[0] => 0 //first occurrence of 0 in $myArray
[2] => 1 //first occurrence of 1 in $myArray
[8] => 2 //last occurrence of 2 in the $myArray
[9] => 0 //first occurrence of 0 in $myArray
)
How to modify my code to achieve above result??
In reality I have multidimensional array, but for better explanation I have used single dimensional array here in the question.
UPDATE
My actual array is
$myArray = array(
0 => array("Value"=>0, "Tax" => "11.00"),
1 => array("Value"=>0, "Tax" => "12.00"),
2 => array("Value"=>1, "Tax" => "13.00"),
5 => array("Value"=>1, "Tax" => "14.00"),
6 => array("Value"=>2, "Tax" => "15.00"),
7 => array("Value"=>2, "Tax" => "16.00"),
8 => array("Value"=>2, "Tax" => "17.00"),
9 => array("Value"=>0, "Tax" => "18.00"),
10 => array("Value"=>0, "Tax" => "19.00"),
);
And my actual code
$previtem= NULL;
$newArray = array_filter(
$myArray,
function ($currentItem) use (&$previtem) {
$p["Value"] = $previtem["Value"];
$previtem["Value"] = $currentItem["Value"];
return $currentItem["Value"]!== $p["Value"] ;
}
);
Thanks

This should do what you are looking for.
function array_filter($a) {
$na = array();
$first = true;
$p = null;
$wantlast = false;
foreach ($a as $v) {
if ($wantlast) {
($v != $p) ? $na[] = $p: null;
}
$wantlast = ($v == 2) ? true : false;
if (!$wantlast) {
(($v != $p) || ($first))? $na[] = $v : null;
}
$p = $v;
$first = false;
}
return $na;
}

$myArray = array(
0 => 0,
1 => 0,
2 => 1,
5 => 1,
6 => 2,
7 => 2,
8 => 2,
9 => 0,
10 => 0,
);
$previtem= NULL;
$newArray = array_filter(
$myArray,
function ($currentItem, $key) use (&$previtem,$myArray) {
$p = $previtem;
if($currentItem != 2){
$previtem = $currentItem;
}else{
$lastkey = array_search(2,(array_reverse($myArray, true)));
if($key != $lastkey)
$currentItem = $previtem;
}
return $currentItem!== $p ;
}, ARRAY_FILTER_USE_BOTH
);
echo "<pre>";
print_r($newArray);

Related

PHP adding values inside multidimensional array

Please find below the code sample for adding repeated values inside inner array. Can anyone suggest an alternative way to add the values faster? The code will work with smaller arrays, but I want to add big arrays that contain huge amount of data. Also I want to increase execution time.
<?php
$testArry = array();
$testArry[0] = array(
"text" => "AB",
"count" => 2
);
$testArry[1] = array(
"text" => "AB",
"count" => 5
);
$testArry[2] = array(
"text" => "BC",
"count" => 1
);
$testArry[3] = array(
"text" => "BD",
"count" => 1
);
$testArry[4] = array(
"text" => "BC",
"count" => 7
);
$testArry[5] = array(
"text" => "AB",
"count" => 6
);
$testArry[6] = array(
"text" => "AB",
"count" => 2
);
$testArry[7] = array(
"text" => "BD",
"count" => 111
);
$match_key = array();
$final = array();
foreach ($testArry as $current_key => $current_array) {
$match_key = array();
foreach ($testArry as $search_key => $search_array) {
$key = '';
if ($search_array['text'] == $current_array['text']) {
$match_key[] = $search_key;
$key = $search_array['text'];
if (isset($final[$key])) {
$final[$key] += $search_array['count'];
} else {
$final[$key] = $search_array['count'];
}
}
}
for ($j = 0; $j < count($match_key); $j++) {
unset($testArry[$match_key[$j]]);
}
}
print_r($final);
?>
Anyway to add memory during the execution time?
Thank you.
One array_walk will be enough to solve your problem,
$final = [];
array_walk($testArry, function($item) use(&$final){
$final[$item['text']] = (!empty($final[$item['text']]) ? $final[$item['text']] : 0) + $item['count'];
});
print_r($final);
Output
Array
(
[AB] => 15
[BC] => 8
[BD] => 112
)
Demo
array_walk — Apply a user supplied function to every member of an array
array_map() - Applies the callback to the elements of the given arrays
array_key_exists() - Checks if the given key or index exists in the array
You can use array_walk and array_key_exists to iterate through the array element and sum the one which has text index same
$res = [];
array_map(function($v) use (&$res){
array_key_exists($v['text'], $res) ? ($res[$v['text']] += $v['count']) : ($res[$v['text']] = $v['count']);
}, $testArry);

Sum values in array and simplify key names

This is almost similar to my other question which is related to the same project I'm working on.. Link to my other question
but in this case the array is different as follow:
Array
(
[2014-08-01 11:27:03] => 2
[2014-08-01 11:52:57] => 2
[2014-08-01 11:54:49] => 2
[2014-08-02 11:59:54] => 4
[2014-08-02 12:02:41] => 2
[2014-08-05 12:09:38] => 4
[2014-08-07 12:23:12] => 3
[2014-08-07 12:25:18] => 3
// and so on...
)
That is my output array and in order to get that array I had to do some miracles... anyway, so based on that array I have to sum the value for each key date and build an array something like this...
Array
(
[2014-08-01] => 6
[2014-08-02] => 6
[2014-08-05] => 4
[2014-08-07] => 6
// and so on...
)
That last array will be use to build graphs with morrisonJS, what I have is this:
$res_meno = array();
foreach ($sunArr as $keys => $values) {
$arrays= explode(" ",$sumArr[$keys]);
$res_meno[] = $arrays[0];
}
$vals_char2 = array_count_values($res_meno);
That is my attempt to build my last array but is not working...
any help would be greatly appreciated!
Thank you for taking the time.
Try this code
<?php
$arr = array(
"2014-08-01 11:27:03" => 2,
"2014-08-01 11:52:57" => 2,
"2014-08-01 11:54:49" => 2,
"2014-08-02 11:59:54" => 4,
"2014-08-02 12:02:41" => 2,
"2014-08-05 12:09:38" => 4,
"2014-08-07 12:23:12" => 3,
"2014-08-07 12:25:18" => 3
);
$new_array = array();
foreach($arr as $k => $v){
$date = reset(explode(" ", $k));
if(isset($new_array[$date])){
$new_array[$date] += $v;
}
else{
$new_array[$date] = $v;
}
}
print_r($new_array);
?>
DEMO
$sunArr = array
(
"2014-08-01 11:27:03" => 2,
"2014-08-01 11:52:57" => 2,
"2014-08-01 11:54:49" => 2,
"2014-08-02 11:59:54" => 4,
"2014-08-02 12:02:41" => 2,
"2014-08-05 12:09:38" => 4,
"2014-08-07 12:23:12" => 3,
"2014-08-07 12:25:18" => 3,
);
$res_meno = array();
foreach ($sunArr as $keys => $values) {
$arrays= explode(" ",$keys);
if(isset($res_meno[$arrays[0]]))
{
$res_meno[$arrays[0]] = $res_meno[$arrays[0]] + $values;
}
else
{
$res_meno[$arrays[0]] = $values;
}
}
print_r($res_meno);
exit;
Try this, i think it might fix the problem
Try this PHP Code You Can test here
$sunArr = Array
(
'2014-08-01 11:27:03' => 2,
'2014-08-01 11:52:57' => 2,
'2014-08-01 11:54:49' => 2,
'2014-08-02 11:59:54' => 4,
'2014-08-02 12:02:41' => 2,
'2014-08-05 12:09:3' => 4,
'2014-08-07 12:23:12' => 3,
'2014-08-07 12:25:18' => 3
);
$key = 0;
$res_meno = array();
foreach ($sunArr as $keys => $values)
{
$ar= explode(" ", $keys);
if( $key == $ar[0] )
{
$res_meno[$key] = $sunArr[$keys] + $res_meno[$key];
}
else
{
$key = $ar[0];
$res_meno[$key] = $values;
}
}
echo '<pre>';
print_r($res_meno);
die;
Where does the first array come from? If it's from a SQL database, it's better to create a query that returns the aggregated array.
Otherwise no need to use array_key_values for that:
$res_meno = array();
foreach ($sumArr as $keys => $values) {
$key = substr($keys, 0, 10);
$res_meno[$key] = (empty($res_meno[$key]) ? 0 : $res_meno[$key]) + $values;
}
Here is a solution which uses a callback. Not to use loops is often better!
$sunArr = array(
'2014-08-01 11:27:03' => 3,
'2014-08-01 11:27:05' => 5,
'2013-09-01 11:01:05' => 1
);
$res = array();
function map($item, $key, &$result)
{
$result[current(explode(" ", $key))] += $item;
}
array_walk($sunArr, "map", &$res);
var_dump($res);
You can test it here on codepad.

Deleting Elements In An Array Only When They Are Next To Each Other

I have an array that is composed of information that looks like the following:
['Jay', 'Jay', 'Jay', 'Spiders', 'Dogs', 'Cats', 'John', 'John', 'John', 'Dogs', 'Cows', 'Snakes']
What I'm trying to do is remove duplicate entries but only if they occur right next to each other.
The correct result should look like the following:
['Jay', 'Spiders', 'Dogs', 'Cats', 'John', 'Dogs', 'Cows', 'Snakes']
I'm using PHP but any kind of logic would be able to help me out with this problem.
Here is some code I've tried so far:
$clean_pull = array();
$counter = 0;
$prev_value = NULL;
foreach($pull_list as $value) {
if ($counter == 0) {
$prev_value = $value;
$clean_pull[] = $value;
}
else {
if ($value != $pre_value) {
$pre_value = value;
}
}
echo $value . '<br>';
}
Francis, when I run the following code:
$lastval = end($pull_list);
for ($i=count($pull_list)-2; $i >= 0; $i--){
$thisval = $pull_list[$i];
if ($thisval===$lastval) {
unset($pull_list[$i]);
}
$lastval = $thisval;
}
# optional: reindex the array:
array_splice($pull_list, 0, 0);
var_export($pull_list);
, I get these results:
array ( 0 => 'NJ Lefler', 1 => 'Deadpool', 2 => 'NJ Lefler', 3 => 'Captain Universe: The Hero Who Could Be You', 4 => 'NJ Lefler', 5 => 'The Movement', 6 => 'NJ Lefler', 7 => 'The Dream Merchant', 8 => 'Nolan Lefler', 9 => 'Deadpool', 10 => 'Nolan Lefler', 11 => 'Captain Universe: The Hero Who Could Be You', 12 => 'Nolan Lefler', 13 => 'The Movement', 14 => 'Tom Smith', 15 => 'Deadpool', 16 => 'Tom Smith', 17 => 'Captain Universe: The Hero Who Could Be You', )
Your approach (a $prev_value variable) should work fine and you don't need a counter.
Your use of $counter is why your code doesn't work--the first half of the if statement is always executed because $counter is never incremented; and the second half just compares values. The only thing you need to do is compare the current value with the previous value and include the current value only if it differs (or remove it only if it's the same).
It's much easier to see this algorithm if you use functional reduction. Here is an example using array_reduce:
$a = array('Jay', 'Jay', 'Jay', 'Spiders', 'Dogs', 'Cats', 'John', 'John', 'John', 'Dogs', 'Cows', 'Snakes');
$na = array_reduce($a, function($acc, $item){
if (end($acc)!==$item) {
$acc[] = $item;
}
return $acc;
}, array());
var_export($na);
Note this comparison of var_export($a) (your original array) and var_export($na) (the result produced by the code):
$a = array ( $na = array (
0 => 'Jay', 0 => 'Jay',
1 => 'Jay', 1 => 'Spiders',
2 => 'Jay', 2 => 'Dogs',
3 => 'Spiders', 3 => 'Cats',
4 => 'Dogs', 4 => 'John',
5 => 'Cats', 5 => 'Dogs',
6 => 'John', 6 => 'Cows',
7 => 'John', 7 => 'Snakes',
8 => 'John', )
9 => 'Dogs',
10 => 'Cows',
11 => 'Snakes',
)
The array_reduce() method does exactly the same thing as the following code:
$na = array();
foreach ($a as $item) {
if (end($na)!==$item) {
$na[] = $item;
}
}
Instead of returning a copy of an array, you can also modify the array in-place using the same algorithm but starting from the end of the array:
$lastval = end($a);
for ($i=count($a)-2; $i >= 0; $i--){
$thisval = $a[$i];
if ($thisval===$lastval) {
unset($a[$i]);
}
$lastval = $thisval;
}
# optional: reindex the array:
array_splice($a, 0, 0);
var_export($a);
Keep track of the last element in the array, and skip adding the next element to your new array if you just added it.
Or, you can just check the last element in the array, and see if it's not the current element in your array:
$array = ['Jay', 'Jay', 'Jay', 'Spiders', 'Dogs', 'Cats', 'John', 'John', 'John', 'Dogs', 'Cows', 'Snakes'];
$new = array( array_shift( $array));
foreach( $array as $el) {
if( !($new[count($new) - 1] === $el)) {
$new[] = $el;
}
}
Assuming the array isn't so large that having a second one will cause a problem, the approach you described should work. Does it look like this?
$last = null;
$result = [];
foreach($arr as $item)
if($item !== $last)
$result[] = $last = $item;
Re: edit:
$pre_value and $prev_value aren't the same thing
$counter doesn't change
It looks like you tried to combine a counter approach and a "last" approach somehow.
Define a global variable glob.
pass the array:
if (array[i] == glob) then
remove array[i]
else
glob = array[i];
keep array[i];

Split Array by Value

I'm working on a leader board that pulls the top scorers into first, second, and third place based on points. Right now I'm working with a sorted array that looks like this (but could be of infinite length with infinite point values):
$scores = Array
(
["bob"] => 20
["Jane"] => 20
["Jill"] => 15
["John"] => 10
["Jacob"] => 5
)
I imagine I could use a simple slice or chunk, but I'd like to allow for ties, and ignore any points that don't fit into the top three places, like so:
$first = Array
(
["bob"] => 20
["Jane"] => 20
)
$second = Array
(
["Jill"] => 15
)
$third = Array
(
["John"] => 10
)
Any ideas?
$arr = array(
"Jacob" => 5,
"bob" => 20,
"Jane" => 20,
"Jill" => 15,
"John" => 10,
);
arsort($arr);
$output = array();
foreach($arr as $name=>$score)
{
$output[$score][$name] = $score;
if (count($output)>3)
{
array_pop($output);
break;
}
}
$output = array_values($output);
var_dump($output);
$first will be in $output[0], $second in $output[1] and so on.. Code is limited to 3 first places.
ps: updated to deal with tie on the third place
I would do something like:
function chunk_top_n($scores, $limit)
{
arsort($scores);
$current_score = null;
$rank = array();
$n = 0;
foreach ($scores as $person => $score)
{
if ($current_score != $score)
{
if ($n++ == $limit) break;
$current_score = $score;
$rank[] = array();
$p = &$rank[$n - 1];
}
$p[$person] = $score;
}
return $rank;
}
It sorts the array, then creates numbered groups. It breaks as soon as the limit has been reached.
You can do it with less code if you use the score as the key of the array, but the benefit of the above approach is it creates the array exactly how you want it the first time through.
You could also pass $scores by reference if you don't mind the original getting sorted.
Here's my go at it:
<?php
function array_split_value($array)
{
$result = array();
$indexes = array();
foreach ($array as $key => $value)
{
if (!in_array($value, $indexes))
{
$indexes[] = $value;
$result[] = array($key => $value);
}
else
{
$index_search = array_search($value, $indexes);
$result[$index_search] = array_merge($result[$index_search], array($key => $value));
}
}
return $result;
}
$scores = Array(
'bob' => 20,
'Jane' => 20,
'Jill' => 15,
'John' => 10,
'Jacob' => 5
);
echo '<pre>';
print_r(array_split_value($scores));
echo '</pre>';
?>

PHP: Get two nearest neighbors from array?

I found this thread about picking the closest/nearest value from an array based upon a known value. What about if one wants to pick the two nearest values from an array looking at the same say?
$rebates = array(
1 => 0,
3 => 10,
5 => 25,
10 => 35)
$rebates = array(
1 => 0,
3 => 10,
5 => 25,
10 => 35);
function getArrayNeighborsByKey($array, $findKey) {
if ( ! array_key_exists($array, $findKey)) {
return FALSE;
}
$select = $prevous = $next = NULL;
foreach($array as $key => $value) {
$thisValue = array($key => $value);
if ($key === $findKey) {
$select = $thisValue;
continue;
}
if ($select !== NULL) {
$next = $thisValue;
break;
}
$previous = $thisValue;
}
return array(
'prev' => $previous,
'current' => $select,
'next' => $next
);
}
See it!
By "two nearest" you mean the two smaller than or equal to the value of $items?
Anyway, starting from the answer to that other thread, which is
$percent = $rebates[max(array_intersect(array_keys($rebates),range(0,$items)))];
You can go to
$two_nearest = array_slice(array_intersect(array_keys($rebates),range(0,$items)), -2);
$most_near = $rebates[$two_nearest[1]];
$less_near = $rebates[$two_nearest[0]];
This can probably be reduced to an one-liner using array_map, but I think it's overdone already.
$rebates = array(
1 => 0,
3 => 10,
5 => 25,
10 => 35)
$distances = array();
foreach($rebates as $key=>$item) {
if ($key == 5) continue;
$distances = abs($rebates[5] - $item);
}
sort($distances, SORT_NUMERIC)
Now you have an array with all the items in the array with their distance to $rebates[5] sorted. So you can get the two closest ones.
Or three closest ones. Whatever.
Just keep in mind that 2 items can have the same distance.

Categories