I find a very simple solution to count values in multidimentional arrays (example for 2 levels) :
foreach ($array as $a) {
foreach ($a as $b) {
$count_values[$b]++;
}
}
(PHP 4, PHP 5)
array_count_values — Подсчитывает количество всех значений массива
$array
)
Функция array_count_values() возвращает массив,
ключами которого являются значения массива array
,
а значениями - частота повторения значений array
.
array
Массив подсчитываемых значений
Возвращает ассоциативный массив со значениями array
в качестве ключей и их количества в качестве значений.
Пример #1 Пример использования array_count_values()
<?php
$array = array(1, "hello", 1, "world", "hello");
print_r(array_count_values($array));
?>
Результат выполнения данного примера:
Array ( [1] => 2 [hello] => 2 [world] => 1 )
I find a very simple solution to count values in multidimentional arrays (example for 2 levels) :
foreach ($array as $a) {
foreach ($a as $b) {
$count_values[$b]++;
}
}
In order to apply array_map with callback checking for localised values like city name, country name, you have to provide some sort of comparison array.
Here's an example of array_count_values for Polish city names. Just switch array_keys / array_values in order to obtain lowercase / uppercase.
$arr = array('Warsaw', 'Warsaw', 'Wrocław', 'Poznań', 'KrakÓw', 'waRsaw', 'Gdańsk', 'poznań', 'WROCŁAW', 'kraków', 'GDAŃSK');
$lowertoupperpolish = array(
'ą' => 'Ą',
'ć' => 'Ć',
'ę' => 'Ę',
'ł' => 'Ł',
'ń' => 'Ń',
'ó' => 'Ó',
'ś' => 'Ś',
'ż' => 'Ż',
'ź' => 'Ź',
);
function lowers($n) {
global $lowertoupperpolish;
return strtolower(str_replace(array_values($lowertoupperpolish), array_keys($lowertoupperpolish), $n));
}
$result = array_count_values(array_map('lowers', $arr));
print_r($result); ...prints:
Array ( [warsaw] => 3 [wrocław] => 2 [poznań] => 2 [kraków] => 2 [gdańsk] => 2 )
Yet Another case-insensitive version of array_count_values()
<?php
$ar = array('J. Karjalainen', 'J. Karjalainen', 60, '60', 'J. Karjalainen', 'j. karjalainen', 'Fastway', 'FASTWAY', 'Fastway', 'fastway', 'YUP');
$ar = array_count_values(array_map('strtolower', $ar));
?>
I couldn't find a function for counting the values with case-insensitive matching, so I wrote a quick and dirty solution myself:
<pre><?php
function array_icount_values($array) {
$ret_array = array();
foreach($array as $value) {
foreach($ret_array as $key2 => $value2) {
if(strtolower($key2) == strtolower($value)) {
$ret_array[$key2]++;
continue 2;
}
}
$ret_array[$value] = 1;
}
return $ret_array;
}
$ar = array('J. Karjalainen', 'J. Karjalainen', 60, '60', 'J. Karjalainen', 'j. karjalainen', 'Fastway', 'FASTWAY', 'Fastway', 'fastway', 'YUP');
$ar2 = array_count_values($ar); // Normal matching
$ar = array_icount_values($ar); // Case-insensitive matching
print_r($ar2);
print_r($ar);
?></pre>
This prints:
Array
(
[J. Karjalainen] => 3
[60] => 2
[j. karjalainen] => 1
[Fastway] => 2
[FASTWAY] => 1
[fastway] => 1
[YUP] => 1
)
Array
(
[J. Karjalainen] => 4
[60] => 2
[Fastway] => 4
[YUP] => 1
)
I don't know how efficient it is, but it seems to work. Needed this function in one of my scripts and thought I would share it.
Here is a Version with one or more arrays, which have similar values in it:
Use $lower=true/false to ignore/set case Sensitiv.
<?php
$ar1[] = array("red","green","yellow","blue");
$ar1[] = array("green","yellow","brown","red","white","yellow");
$ar1[] = array("red","green","brown","blue","black","yellow");
#$ar1= array("red","green","brown","blue","black","red","green"); // Possible with one or multiple Array
$res = array_icount_values ($ar1);
print_r($res);
function array_icount_values($arr,$lower=true) {
$arr2=array();
if(!is_array($arr['0'])){$arr=array($arr);}
foreach($arr as $k=> $v){
foreach($v as $v2){
if($lower==true) {$v2=strtolower($v2);}
if(!isset($arr2[$v2])){
$arr2[$v2]=1;
}else{
$arr2[$v2]++;
}
}
}
return $arr2;
}
/*
Will print:
Array
(
[red] => 3
[green] => 3
[yellow] => 4
[blue] => 2
[brown] => 2
[white] => 1
[black] => 1
)
*/
?>
array_count_values function does not work on multidimentional arrays.
If $score[][] is a bidimentional array, the command
"array_count_values ($score)" return the error message "Warning: Can only count STRING and INTEGER values!".
<?
function array_icount_values($array) {
$ret_array = array();
foreach($array as $value) $ret_array[strtolower($value)]++;
return $ret_array;
}
$ar = array('J. Karjalainen', 'J. Karjalainen', 60, '60', 'J. Karjalainen', 'j. karjalainen', 'Fastway', 'FASTWAY', 'Fastway', 'fastway', 'YUP');
$ar = array_icount_values($ar);
?>
this prints:
Array
(
[j. karjalainen] => 4
[60] => 2
[fastway] => 4
[yup] => 1
)
The case-insensitive version:
<?php
function array_count_values_ci($array) {
$newArray = array();
foreach ($array as $values) {
if (!array_key_exists(strtolower($values), $newArray)) {
$newArray[strtolower($values)] = 0;
}
$newArray[strtolower($values)] += 1;
}
return $newArray;
}
?>
You might use serialize() to serialize your objects before analyzing their frequency. :)
byron at byronrode dot co dot za, here are some benchmarks.
<?php
$haystack = Array();
for ($i = 0; $i < 1000000; $i++) {
$haystack[] = rand(1, 2000);
}
$needle = rand(1, 2000);
echo "__array_count_values()__\n";
$start = microtime(true);
$startmem = memory_get_usage();
$counts = array_count_values($haystack);
$mem = memory_get_usage()-$startmem;
echo 'Count:'.$counts[$needle]."\n";
echo 'Time:'.(microtime(true) - $start)."\n";
echo 'Memory:'.$mem."\n\n";
echo "__array_keys()__\n";
$start = microtime(true);
$startmem = memory_get_usage();
$keys = array_keys($haystack, $needle);
$mem = memory_get_usage()-$startmem;
echo 'Count:'.count($keys)."\n";
echo 'Time:'.(microtime(true) - $start)."\n";
echo 'Memory:'.$mem."\n\n";
echo '__$needle_array[]__'."\n";
$start = microtime(true);
$startmem = memory_get_usage();
$x = count($haystack);
for($i = 0; $i < $x; $i++){
if($haystack[$i] == $needle){
$needle_array[] = $haystack[$i];
}
}
$mem = memory_get_usage()-$startmem;
$number_of_instances = count($needle_array);
echo 'Count:'.$number_of_instances."\n";
echo 'Time:'.(microtime(true) - $start)."\n";
echo 'Memory:'.$mem."\n\n";
echo '__$number_of_instances++__'."\n";
$start = microtime(true);
$startmem = memory_get_usage();
$x = count($haystack);
$number_of_instances = 0;
for($i = 0; $i < $x; $i++){
if($haystack[$i] == $needle){
$number_of_instances++;
}
}
$mem = memory_get_usage()-$startmem;
echo 'Count:'.$number_of_instances."\n";
echo 'Time:'.(microtime(true) - $start)."\n";
echo 'Memory:'.$mem."\n\n";
?>
[www]mytemp$ php array_count_test.php
__array_count_values()__
Count:515
Time:0.0607650279999
Memory:120328
__array_keys()__
Count:515
Time:0.0869138240814
Memory:33016
__$needle_array[]__
Count:515
Time:0.259949922562
Memory:24792
__$number_of_instances++__
Count:515
Time:0.258481025696
Memory:0
However, when you use an array of strings by calling md5(rand(1, 2000)), the performance boosts become less significant:
__array_count_values()__
Count:499
Time:0.491794109344
Memory:184328
__array_keys()__
Count:499
Time:0.36399102211
Memory:30072
__$needle_array[]__
Count:499
Time:0.568728923798
Memory:22104
__$number_of_instances++__
Count:499
Time:0.574353933334
Memory:0
Results are similar for string->string haystacks with foreach traversal.
alwaysdrunk's comment only works if you can trust the client web browser. Using this function doesn't validate that every necessary field exists -- only that every field that was submitted has a value in it. Thus if an attacker wished to force a null value into one of the fields, he could (rather easily) construct a modified form without the field and submit THAT.
Besides, you really ought to be validating each field anyway if you're taking user input.