给定一个数组:
$foo = Array(
99=>'Lowest Numbers',
123=>'Low Numbers',
456=>'High Numbers',
777=>'Highest Numbers',
);
…对于值'144',我希望返回最近的低值和最近的高值,而不必遍历数组中的每个元素,因为实际数组相当大。
'144'的预期结果应该是123=>'Low Numbers'
我目前有以下代码:
function name($color,$fuzzy=false) {
global $resene;
$long = 0;
if(is_array($color)) {
$long = Color::hex2lng(Color::rgb2hex($color));
} else {
$long = Color::hex2lng($color);
}
if(isset($resene[$long])) {
echo $resene[$long];
} else {
if($fuzzy) {
$resene[$long] = '';
ksort($resene);
// This is where I am having problems
// The array is sorted, so it should be a simple
// matter of getting the previous and next value
// somehow since we know the position we want to
// start at exists because it has been added.
echo 'No color name found';
}
}
}
基本上,这个概念非常简单,因为我们将没有找到的值注入到数组中,我们知道它存在。对键进行排序,现在确保最接近的两个键是与正在搜索的数字最接近的匹配。
上面的函数实际上是一个基于十六进制或RGB颜色的搜索,转换为基数10(长值)。
数组中的键是非增量的,这意味着它不是0、1、2、3,即使我"翻转"数组,键也会是字符串,同样,没有增量来获得最接近的键。
我正在考虑拆分或其他东西,然而,这似乎是根据数组中有多少元素而不是基于键来拆分数组。
这实际上是使它工作的最后一步——不必遍历每个元素。
任何帮助都将是非常感激的。
这是我写的静态函数的一个Pastbin,它返回一个颜色数组(),使用颜色的长值作为键,值是颜色的字符串名称。
颜色索引数组
正如作者指出的那样,您可能不需要太担心性能问题,但是您可以尝试一种二进制搜索的变体。这里没有办法跳过搜索,因为你不能切到ksort()。这是我想出来的一个草稿:
//$resene is your input ksort()-ed array, $long is the key which position and neighbours you're trying to find
$keys = array_keys($resene);
$min = reset($keys);
$s = key($keys); // = 0
$max = end($keys);
$e = key($keys); // = count($resene)
do {
$guess = $s + (int)(($long - $min)/($max - $min)*($e - $s));
if ($keys[$guess] > $long) {
$e = $guess - 1;
$max = $keys[$e];
$min = $keys[++$s];
} elseif ($keys[$guess] < $long) {
$s = $guess + 1;
$min = $keys[$s];
$max = $keys[--$e];
}
} while ($keys[$guess] != $long && $e != $s);
echo 'Me = '.$keys[$guess].'; prev = '.$keys[$guess - 1].'; next = '.$keys[$guess + 1];
我在一个包含从0到5,000,000的20,000个随机数的数组上运行了一些测试,并使用该数组的随机目标值,我在3-4个循环中获得了命中。当然,不要忘记检查prev/next是否存在。
如果您可以使用普通索引数组并对其使用普通sort()以避免与array_keys()混淆,那么它将更好地工作。我猜你试图在这里使用键只是为了获得一些速度,你没有任何有用的数组值?如果是这样,您应该切换到索引数组。
如果你不使用k/sort(),而是使用类似这样的东西来找到要插入新值的位置,你可以让它更好地工作。然后你可以使用array_splice()来插入它,并且你已经知道它的位置,因此,prev/next。
在查看了示例中的方法2之后,您想要做的事情变得更加清楚了。我很好奇我能在PHP中得到多少索引,所以这里有一个函数,它给出了和你的相同的结果:
function fast_nearest($array, $value, $exact=false) {
if (isset($array[$value])) {
// If exact match found, and searching for exact (not nearest), return result.
return array($value => $array[$value], 'exact' => true);
} elseif ($exact || empty($array)) {
return false;
}
// else
$keys = array_keys($array);
$min = $keys[0];
$s = 0;
$max = end($keys);
$e = key($keys);
if ($s == $e) {
// only one element, it's closest
return array_merge($array, array('exact' => false));
} elseif ($value < $min) {
return array($min => $array[$min], 'exact' => false);
} elseif ($value > $max) {
return array($max => $array[$max], 'exact' => false);
}
$result = false;
do {
$guess = $s + (int)(($value - $min) / ($max - $min) * ($e - $s));
if ($guess < $s) {
// oops, off the scale; we found it
$result = $keys[$s];
} elseif ($guess > $e) {
$result = $keys[$e];
} elseif ($keys[$guess] > $value && $keys[$guess - 1] < $value) {
// found range
$result = (($value - $keys[$guess - 1]) < ($keys[$guess] - $value)
? $keys[$guess - 1]
: $keys[$guess]);
} elseif ($keys[$guess] < $value && $keys[$guess + 1] > $value) {
$result = (($value - $keys[$guess]) < ($keys[$guess + 1] - $value)
? $keys[$guess]
: $keys[$guess + 1]);
} elseif ($keys[$guess] > $value) {
// narrowing search area
$e = $guess - 1;
$max = $keys[$e];
} elseif ($keys[$guess] < $value) {
$s = $guess + 1;
$min = $keys[$s];
}
} while ($e != $s && $result === false);
if ($result === false) {
throw new Exception("Math laws don't work in this universe.");
}
return array($result => $array[$result], 'exact' => false);
}
我在顶部编译了分散在函数中的大多数退出场景,并且我还将项目插入到数组中,因为它不会在函数外持续存在。您可以很容易地在找到的位置添加array_splice()
。
我对两个函数(你的和我的)运行了速度测试,以比较从1到1,000,000,000的随机数数组(是的,两个函数都有相同的输入):
- 20000件:
-
fast_nearest()
- 7.3 ms平均1000次运行 -
nearest()
- 207 ms相同1000次运行的平均值
-
- 200000件:
-
fast_nearest()
- 70 ms平均10次运行(抱歉,1000次等待时间太长了) -
nearest()
- 2,798 ms 10次运行的平均值
-
- 2000000件:
-
fast_nearest()
- 937 ms 2次运行的平均值 -
nearest()
- 22156 ms 2次运行的平均值
-
显然,两者都不能很好地处理巨大的数组,所以如果你必须操作这么多的数据-我建议使用类似数据库服务器的适当索引,PHP不是合适的工具。
此处:
$input = 142;
$offset = 0;
while(true){
if(isset($foo[$input - $offset])){
$found = array($input - $offset => $foo[$input - $offset]);
break;
}
if(isset($foo[$input + $offset])){
$found = array($input - $offset => $foo[$input + $offset]);
break;
}
$offset++;
}
它应该比标准循环
便宜一点数组到底有多大?为什么速度很重要?
编辑: NVM。你的问题是错误的。我刚刚在一个包含100K个元素的关联数组上对ksort()
进行了计时测试。它需要0.07秒。而一个完整的foreach循环,需要0.01秒!