Count unique values in a column of an array - php

I have an array like this:
$arr = [
1 => ['A' => '1', 'C' => 'TEMU3076746'],
2 => ['A' => '2', 'C' => 'FCIU5412720'],
3 => ['A' => '3', 'C' => 'TEMU3076746'],
4 => ['A' => '4', 'C' => 'TEMU3076746'],
5 => ['A' => '5', 'C' => 'FCIU5412720']
];
My goal is to count the distinct values in the C column of the 2-dimensional array.
The total rows in the array is found like this: count($arr) (which is 5).
How can I count the number of rows which contain a unique value in the 'C' column?
If I removed the duplicate values in the C column, there would only be: TEMU3076746 and FCIU5412720
My desired output is therefore 2.

Hope this simplest one will be helpful. Here we are using array_column, array_unique and count.
Try this code snippet here
echo count(
array_unique(
array_column($data,"C")));
Result: 2

combine array_map, array_unique, count
$array = [ /* your array */ ];
$count = count(
array_unique(
array_map(function($element) {
return $element['C'];
}, $array))))
or use array_column as suggested by sahil gulati, array_map can do more stuff which probably isn't needed here.

I had a very similar need and I used a slightly different method.
I have several events where teams are participating and I need to know how many teams there are in each event. In other words, I don't need to only know how many distinct item "C" there are, but how many items TEMU3076746 and FCIU5412720 there are.
The code will then be as is
$nbCs = array_count_values ( array_column ( $array, 'C' ) );
$nbCs will issue an array of values = Array([TEMU3076746] => 3 [FCIU5412720] => 2)
See example in sandbox Sandbox code

$data=array();
$data=[
1 => [
'A' => '1'
'C' => 'TEMU3076746'
]
2 => [
'A' => '2'
'C' => 'FCIU5412720'
]
3 => [
'A' => '3'
'C' => 'TEMU3076746'
]
4 => [
'A' => '4'
'C' => 'TEMU3076746'
]
5 => [
'A' => '5'
'C' => 'FCIU5412720'
]
];
$total_value=count(
array_unique(
array_column($data,"C")));
echo $total_value;

Most concisely, use array_column()'s special ability to assign new first level keys using the targeted column's values. This provides the desired effect of uniqueness because arrays cannot contain duplicate keys on the same level.
Code: (Demo)
echo count(array_column($arr, 'C', 'C')); // 2
To be perfectly clear, array_column($arr, 'C', 'C') produces:
array (
'TEMU3076746' => 'TEMU3076746',
'FCIU5412720' => 'FCIU5412720',
)
This would also work with array_column($arr, null, 'C'), but that create a larger temporary array.
p.s. There is a fringe case that may concern researchers who are seeking unique float values. Assigning new associative keys using float values is inappropriate/error-prone because the keys will lose precision (become truncated to integers).
In that fringe case with floats, fallback to the less performant technique: count(array_unique(array_column($arr, 'B))) Demo

Related

Sort an associative array by value in descending and preserve order when values are same

I want to sort an associative array and there is an inbuilt function to achieve the same viz. arsort(), but the problem with this function is that it doesn't maintain the original key order when values are same.
e.g.
$l = [
'a' => 1,
'b' => 2,
'c' => 2,
'd' => 4,
'e' => 5,
'f' => 5
];
The result which I want is :
$l = [
'e' => 5,
'f' => 5,
'd' => 4,
'b' => 2,
'c' => 2,
'a' => 1
];
arsort() gives the result in descending order but it randomly arranges the element when values are same.
This question is not a duplicate of PHP array multiple sort - by value then by key?. In that question it is asking for same numeric value to be sorted alphabetically but in my question I am asking values to sorted according to the original order if they are same.
There is probably a more efficient way to do this, but I think this should work to maintain the original key order within groups of the same value. I'll start with this array for example:
$l = [ 'a' => 1, 'b' => 2, 'c' => 2, 'd' => 4, 'g' => 5, 'e' => 5, 'f' => 5 ];
Group the array by value:
foreach ($l as $k => $v) {
$groups[$v][] = $k;
}
Because the foreach loop reads the array sequentially, the keys will be inserted in their respective groups in the correct order, and this will yield:
[1 => ['a'], 2 => ['b', 'c'], 4 => ['d'], 5 => ['g', 'e', 'f'] ];
sort the groups in descending order by key:
krsort($groups);
Reassemble the sorted array from the grouped array with a nested loop:
foreach ($groups as $value => $group) {
foreach ($group as $key) {
$sorted[$key] = $value;
}
}
You can use array_multisort. The function can be a bit confusing, and really hard to explain, but it orders multiple arrays, and the first array provided gets sorted based on the order of subsequent arrays.
Try:
array_multisort($l, SORT_DESC, array_keys($l));
See the example here: https://3v4l.org/oV8Od
It sorts the array by values descending, then is updated by the sort on the keys of the array.

randomize value in array for specific key

I'm trying to get a random array for specific key.
this is my code so far,
$convert = array(
'a' => 'Amusing','Amazing',
'b' => 'Beyond',
'c' => 'Clever','Colorful','Calm',
'd' => 'Dangerous','Donkey',
'e' => 'Endangered',
'f' => 'Fancy',
'g' => 'Great',
'h' => 'Helpful','Humorous',
);
$txt="baca";
$txt=strtolower($txt);
$arr=str_split($txt);
foreach ($arr as $alfa) {
echo $alfa." = ".$convert[$alfa]."\n";
}
the output would be :
b = Beyond
a = Amusing
c = Clever
a = Amusing
but I'm trying to get
b = Beyond
a = Amusing
c = Clever
a = Amazing
Unique value for specific array ('a') in this case.
I tried to use array_rand but failed. I would appreciate any advice given..
This:
array(
'a' => 'Amusing','Amazing',
...
)
is equivalent to:
array(
'a' => 'Amusing',
0 => 'Amazing',
...
)
You're not specifying a key for the word "Amazing", so it automatically gets a numeric key. It does not in any way actually belong to the 'a' key, even if you write it on the same line.
What you want is:
array(
'a' => array('Amusing', 'Amazing'),
...
)
And then:
$values = $convert['a'];
echo $values[array_rand($values)];

Array keys get lost as they are considered numeric

Let's say there exists an array:
$array = array(
'1001' => 'a',
'1002' => 'b',
'1003' => 'c',
);
Now let's say someone wants reverse that array:
$array = array_reverse($array);
The problem is, that array_reverse seems to cast all numeric values to integers and then resets the indexes:
0 => 'c' - should be '1003' => 'c'
1 => 'b' - should be '1002' => 'b'
2 => 'a' - should be '1001' => 'a'
What someone may have also tried was this - but without any luck (as expected):
$array[(string) $index] = 'a';
You can even experience this yourself here on codepad.
How can this be solved? Do I have to write my own mapping function, which can handle this or is there any other way?
you just need to use the following code:
array_reverse($array, true)
As per php documentation, to preserve keys you must set 2nd parameter to true

PHP usort() order in case of equality

In the PHP manual for usort(), it states:
If two members compare as equal, their relative order in the sorted array is undefined.
Also,
A new sort algorithm was introduced. The cmp_function doesn't keep the original order for elements comparing as equal.
Then my question is: what happens if two elements are equal (e.g. the user defined function returns 0)?
I am using this function and apparently equal items are arranged randomly in the sorted array.
Be careful not to confuse "undefined" and "random".
A random implementation would indeed be expected to give a different ordering every time. This would imply there was specific code to shuffle the results when they're found to be equal. This would make the algorithm more complex and slower, and rarely be a desirable outcome.
What undefined means is the opposite: absolutely no care has been taken while designing the algorithm to have predictable or stable order. This means the result may be different every time you run it, if that happens to be the side effect of the algorithm on that data.
You can see the core sort implementation in the PHP source code. It consists of a mixture of "quick sort" (divide and conquer) and insertion sort (a simpler algorithm effective on short lists) with hand-optimised routines for lists of 2, 3, 4, and 5 elements.
So the exact behaviour of equal members will depend on factors like the size of the list, where in the list those equal members come, how many equal members there are in one batch, and so on. In some situations, the algorithm will see that they're identical, and not swap them (the ideal, because swapping takes time); in others, it won't directly compare them until they've already been moved relative to other things, so they'll end up in a different order.
I also find the same while sorting my array, then I found some custom made function because php have some limitation to use defined sorting function .
http://php.net/manual/en/function.uasort.php#Vd114535
PHP 7 uses a stable sort algorithm for small arrays (< 16),
but for larger arrays the algorithm is still not stable.
Furthermore PHP makes no guarantee whether sorting with *sort() is
stable or not https://bugs.php.net/bug.php?id=53341.
If I have an array of: ['b', 'a', 'c', 'b'] and I were to sort this I would get: ['a','b','b','c']. Since 'b' == 'b' php can not guarantee that one comes before the other so the sort order is 'undefined', however since they are equal, does this matter?
If you are using a sort function that returns 0 for unequal objects you're facing a different problem altogether.
understand that php does not care about the order if all the compared values are same.
Example:
$temp=array("b"=>"10","c"=>"10","d"=>"10","e"=>"4");
as above array has 4 array length in which 3 are having the same values as shown b,c,d = 10;
arsort() //The arsort() function sorts an associative array in descending order, according to the value
if print_r(arsort($temp)) o/p: => Array ( [b] => 10 [c] => 10 [d] => 10 [e] => 4 )
this means it return array after sorting equal value but keeps the position(order) same for equal values
but
if $temp=array("a"=>"4",b"=>"10","c"=>"10","d"=>"10","e"=>"4");
here in above array b,c,d = 10 are bounded under two extreme left and right arrays having value less then center (b,c,d = 10) values
the arsort of above temp is o/p:
Array ( [c] => 10 [b] => 10 [d] => 10 [a] => 4 [e] => 4 )
it gives the middle part i.e [c] array in center.
this means if similar values or equal values array is bounded by from both side by lower values array or the first value is lower then the order of equal gives middle one from three array values as first in that three .
I was also facing the same problem, where 2 rows have same values and when apply sort function its order gets changed which I didn't want. I want to sort keys based on their values, if they are equal don't change order. So here is my solution-
// sample array
$arr = Array("a" => 0.57,"b" => 1.19,"c" => 0.57,"d" => 0.57,"e" => 0.57,"f" => 0.57,"g" => 0.57,"h" => 0.57,"i" => 0.99,"j" => 1.19,"k" => 1.19);
$multi_arr = [];
foreach ($arr as $k=>$val){
$multi_arr["$val"][] = array($k=>$val);
}
uksort($multi_arr, function ($a, $b) {
return $b > $a ? 1 : -1;
});
$s_arr = [];
foreach ($multi_arr as $k=>$val){
foreach($val as $p_id){
$p_arr = array_keys($p_id);
$s_arr[] = $p_arr[0];
}
}
print_r($s_arr);
output-
Array([0] => b,[1] => j,[2] => k,[3] => i,[4] => a,[5] => c,[6] => d,[7] => e,[8] => f,[9] => g,[10] => h)
This function preserves order for equal values (example from my CMS EFFCORE):
function array_sort_by_number(&$array, $key = 'weight', $order = 'a') {
$increments = [];
foreach ($array as &$c_item) {
$c_value = is_object($c_item) ? $c_item->{$key} : $c_item[$key];
if ($order === 'a') $increments[$c_value] = array_key_exists($c_value, $increments) ? $increments[$c_value] - .0001 : 0;
if ($order === 'd') $increments[$c_value] = array_key_exists($c_value, $increments) ? $increments[$c_value] + .0001 : 0;
if (is_object($c_item)) $c_item->_synthetic_weight = $c_value + $increments[$c_value];
else $c_item['_synthetic_weight'] = $c_value + $increments[$c_value];
}
uasort($array, function ($a, $b) use ($order) {
if ($order === 'a') return (is_object($b) ? $b->_synthetic_weight : $b['_synthetic_weight']) <=> (is_object($a) ? $a->_synthetic_weight : $a['_synthetic_weight']);
if ($order === 'd') return (is_object($a) ? $a->_synthetic_weight : $a['_synthetic_weight']) <=> (is_object($b) ? $b->_synthetic_weight : $b['_synthetic_weight']);
});
foreach ($array as &$c_item) {
if (is_object($c_item)) unset($c_item->_synthetic_weight);
else unset($c_item['_synthetic_weight']);
}
return $array;
}
It works with both arrays and objects.
It adds a synthetic key, sorts by it, and then removes it.
Test
$test = [
'a' => ['weight' => 4],
'b' => ['weight' => 10],
'c' => ['weight' => 10],
'd' => ['weight' => 10],
'e' => ['weight' => 4],
];
$test_result = [
'b' => ['weight' => 10],
'c' => ['weight' => 10],
'd' => ['weight' => 10],
'a' => ['weight' => 4],
'e' => ['weight' => 4],
];
array_sort_by_number($test, 'weight', 'a');
print_R($test);
var_dump($test === $test_result);
$test = [
'a' => ['weight' => 4],
'b' => ['weight' => 10],
'c' => ['weight' => 10],
'd' => ['weight' => 10],
'e' => ['weight' => 4],
];
$test_result = [
'a' => ['weight' => 4],
'e' => ['weight' => 4],
'b' => ['weight' => 10],
'c' => ['weight' => 10],
'd' => ['weight' => 10],
];
array_sort_by_number($test, 'weight', 'd');
print_R($test);
var_dump($test === $test_result);

array values in multidimensional array

I have two arrays
they look like
$a1 = array(
array('num' => 1, 'name' => 'one'),
array('num' => 2, 'name' => 'two'),
array('num' => 3, 'name' => 'three'),
array('num' => 4, 'name' => 'four'),
array('num' => 5, 'name' => 'five')
)
$a2 = array(3,4,5,6,7,8);
I want to end up with an array that looks like
$a3 = array(3,4,5);
so basically where $a1[$i]['num'] is in $a2
I know I could do
$a3 = array();
foreach($a1 as $num)
if(array_search($num['num'], $a2))
$a3[] = $num['num'];
But that seems like a lot of un-needed iterations.
Is there a better way?
Ah Snap...
I just realized I asked this question the wrong way around, I want to end up with an array that looks like
$a3 array(
array('num' => 3, 'name' => 'three'),
array('num' => 4, 'name' => 'four'),
array('num' => 5, 'name' => 'five')
)
You could extract the relevant informations (the 'num' items) from $a1 :
$a1_bis = array();
foreach ($a1 as $a) {
$a1_bis[] = $a['num'];
}
And, then, use array_intersect() to find what is both in $a1_bis and $a2 :
$result = array_intersect($a1_bis, $a2);
var_dump($result);
Which would get you :
array
2 => int 3
3 => int 4
4 => int 5
With this solution :
you are going through $a1 only once
you trust PHP on using a good algorithm to find the intersection between the two arrays (and/or consider that a function developed in C will probably be faster than any equivalent you could code in pure-PHP)
EDIT after the comment : well, considering the result you want, now, I would go with another approach.
First, I would use array_flip() to flip the $a2 array, to allow faster access to its elements (accessing by key is way faster than finding a value) :
$a2_hash = array_flip($a2); // To speed things up :
// accessing by key is way faster than finding
// an item in an array by value
Then, I would use array_filter() to apply a filter to $a1, keeping the items for which num is in the $a2_hash flipped-array :
$result = array_filter($a1, function ($item) use ($a2_hash) {
if (isset($a2_hash[ $item['num'] ])) {
return true;
}
return false;
});
var_dump($result);
Note : I used an anonymous function, which only exist with PHP >= 5.3 ; if you are using PHP < 5.3, this code can be re-worked to suppress the closure.
With that, I get the array you want :
array
2 =>
array
'num' => int 3
'name' => string 'three' (length=5)
3 =>
array
'num' => int 4
'name' => string 'four' (length=4)
4 =>
array
'num' => int 5
'name' => string 'five' (length=4)
Note the keys are not corresponding to anything useful -- if you want them removed, just use the array_values() function on that $result :
$final_result = array_values($result);
But that's probably not necessary :-)

Categories