提问人:Pekka 提问时间:7/18/2010 最后编辑:Mechanical snailPekka 更新时间:6/15/2018 访问量:3201
俄罗斯方块数组
Tetris-ing an array
问:
请考虑以下数组:
/www/htdocs/1/sites/lib/abcdedd
/www/htdocs/1/sites/conf/xyz
/www/htdocs/1/sites/conf/abc/def
/www/htdocs/1/sites/htdocs/xyz
/www/htdocs/1/sites/lib2/abcdedd
检测公共基本路径的最短和最优雅的方法是什么 - 在这种情况下
/www/htdocs/1/sites/
并将其从数组中的所有元素中删除?
lib/abcdedd
conf/xyz
conf/abc/def
htdocs/xyz
lib2/abcdedd
答:
$common = PHP_INT_MAX;
foreach ($a as $item) {
$common = min($common, str_common($a[0], $item, $common));
}
$result = array();
foreach ($a as $item) {
$result[] = substr($item, $common);
}
print_r($result);
function str_common($a, $b, $max)
{
$pos = 0;
$last_slash = 0;
$len = min(strlen($a), strlen($b), $max + 1);
while ($pos < $len) {
if ($a{$pos} != $b{$pos}) return $last_slash;
if ($a{$pos} == '/') $last_slash = $pos;
$pos++;
}
return $last_slash;
}
评论
/usr/lib
/usr/lib2
/usr/lib
/usr/
将它们加载到尝试数据结构中。从父节点开始,查看哪个节点的子节点计数大于 1。找到该魔术节点后,只需拆除父节点结构并将当前节点作为根节点即可。
评论
我将基于 / 的值,然后用于检测公共元素并确保它们在数组中具有正确的相应索引。生成的数组可以重新组合以生成公共路径。explode
array_intersect_assoc
function getCommonPath($pathArray)
{
$pathElements = array();
foreach($pathArray as $path)
{
$pathElements[] = explode("/",$path);
}
$commonPath = $pathElements[0];
for($i=1;$i<count($pathElements);$i++)
{
$commonPath = array_intersect_assoc($commonPath,$pathElements[$i]);
}
if(is_array($commonPath) return implode("/",$commonPath);
else return null;
}
function removeCommonPath($pathArray)
{
$commonPath = getCommonPath($pathArray());
for($i=0;$i<count($pathArray);$i++)
{
$pathArray[$i] = substr($pathArray[$i],str_len($commonPath));
}
return $pathArray;
}
这是未经测试的,但是,这个想法是,数组只包含路径的元素,这些元素包含在已与之比较的所有路径数组中。当循环完成时,我们只需将其与 / 重新组合即可获得 true$commonPath
$commonPath
更新正如 Felix Kling 所指出的,不会考虑具有共同元素但顺序不同的路径......为了解决这个问题,我用array_intersect
array_intersect_assoc
array_intersect
更新添加了代码以从数组中删除公共路径(或俄罗斯方块!
评论
/a/b/c/d
/d/c/b/a
一种幼稚的方法是将路径分解,并连续比较数组中的每个元素。因此,例如,第一个元素在所有数组中都是空的,因此它将被删除,下一个元素将是 ,它在所有数组中都是相同的,因此它被删除,等等。/
www
类似的东西(未经测试)
$exploded_paths = array();
foreach($paths as $path) {
$exploded_paths[] = explode('/', $path);
}
$equal = true;
$ref = &$exploded_paths[0]; // compare against the first path for simplicity
while($equal) {
foreach($exploded_paths as $path_parts) {
if($path_parts[0] !== $ref[0]) {
$equal = false;
break;
}
}
if($equal) {
foreach($exploded_paths as &$path_parts) {
array_shift($path_parts); // remove the first element
}
}
}
之后,你只需要再次内爆元素:$exploded_paths
function impl($arr) {
return '/' . implode('/', $arr);
}
$paths = array_map('impl', $exploded_paths);
这给了我:
Array
(
[0] => /lib/abcdedd
[1] => /conf/xyz
[2] => /conf/abc/def
[3] => /htdocs/xyz
[4] => /conf/xyz
)
这可能不能很好地扩展;)
$values = array('/www/htdocs/1/sites/lib/abcdedd',
'/www/htdocs/1/sites/conf/xyz',
'/www/htdocs/1/sites/conf/abc/def',
'/www/htdocs/1/sites/htdocs/xyz',
'/www/htdocs/1/sites/lib2/abcdedd'
);
function splitArrayValues($r) {
return explode('/',$r);
}
function stripCommon($values) {
$testValues = array_map('splitArrayValues',$values);
$i = 0;
foreach($testValues[0] as $key => $value) {
foreach($testValues as $arraySetValues) {
if ($arraySetValues[$key] != $value) break 2;
}
$i++;
}
$returnArray = array();
foreach($testValues as $value) {
$returnArray[] = implode('/',array_slice($value,$i));
}
return $returnArray;
}
$newValues = stripCommon($values);
echo '<pre>';
var_dump($newValues);
echo '</pre>';
编辑我使用array_walk重建数组的原始方法的变体
$values = array('/www/htdocs/1/sites/lib/abcdedd',
'/www/htdocs/1/sites/conf/xyz',
'/www/htdocs/1/sites/conf/abc/def',
'/www/htdocs/1/sites/htdocs/xyz',
'/www/htdocs/1/sites/lib2/abcdedd'
);
function splitArrayValues($r) {
return explode('/',$r);
}
function rejoinArrayValues(&$r,$d,$i) {
$r = implode('/',array_slice($r,$i));
}
function stripCommon($values) {
$testValues = array_map('splitArrayValues',$values);
$i = 0;
foreach($testValues[0] as $key => $value) {
foreach($testValues as $arraySetValues) {
if ($arraySetValues[$key] != $value) break 2;
}
$i++;
}
array_walk($testValues, 'rejoinArrayValues', $i);
return $testValues;
}
$newValues = stripCommon($values);
echo '<pre>';
var_dump($newValues);
echo '</pre>';
编辑
最有效和最优雅的答案可能涉及从每个提供的答案中获取函数和方法
这样做的优点是没有线性时间复杂度;但是,在大多数情况下,排序绝对不会是花费更多时间的操作。
基本上,这里的聪明部分(至少我找不到它的缺点)是,在排序后,您只需要将第一条路径与最后一条路径进行比较。
sort($a);
$a = array_map(function ($el) { return explode("/", $el); }, $a);
$first = reset($a);
$last = end($a);
for ($eqdepth = 0; $first[$eqdepth] === $last[$eqdepth]; $eqdepth++) {}
array_walk($a,
function (&$el) use ($eqdepth) {
for ($i = 0; $i < $eqdepth; $i++) {
array_shift($el);
}
});
$res = array_map(function ($el) { return implode("/", $el); }, $a);
编写一个将两个字符串作为输入的函数。然后以任意顺序将其应用于字符串,以将它们减少到其通用前缀。由于它是关联和交换的,因此顺序对结果无关紧要。longest_common_prefix
这与其他二进制运算相同,例如加法或最大公约数。
评论
$arrMain = array(
'/www/htdocs/1/sites/lib/abcdedd',
'/www/htdocs/1/sites/conf/xyz',
'/www/htdocs/1/sites/conf/abc/def',
'/www/htdocs/1/sites/htdocs/xyz',
'/www/htdocs/1/sites/lib2/abcdedd'
);
function explodePath( $strPath ){
return explode("/", $strPath);
}
function removePath( $strPath)
{
global $strCommon;
return str_replace( $strCommon, '', $strPath );
}
$arrExplodedPaths = array_map( 'explodePath', $arrMain ) ;
//Check for common and skip first 1
$strCommon = '';
for( $i=1; $i< count( $arrExplodedPaths[0] ); $i++)
{
for( $j = 0; $j < count( $arrExplodedPaths); $j++ )
{
if( $arrExplodedPaths[0][ $i ] !== $arrExplodedPaths[ $j ][ $i ] )
{
break 2;
}
}
$strCommon .= '/'.$arrExplodedPaths[0][$i];
}
print_r( array_map( 'removePath', $arrMain ) );
这很好用...与 Mark Baker 类似,但使用 str_replace
好的,我不确定这是否防弹,但我认为它有效:
echo array_reduce($array, function($reducedValue, $arrayValue) {
if($reducedValue === NULL) return $arrayValue;
for($i = 0; $i < strlen($reducedValue); $i++) {
if(!isset($arrayValue[$i]) || $arrayValue[$i] !== $reducedValue[$i]) {
return substr($reducedValue, 0, $i);
}
}
return $reducedValue;
});
这会将数组中的第一个值作为引用字符串。然后,它将遍历引用字符串,并将每个字符与同一位置的第二个字符串的字符进行比较。如果 char 不匹配,则引用字符串将缩短到 char 的位置,并比较下一个字符串。然后,该函数将返回最短的匹配字符串。
性能取决于给定的字符串。引用字符串越早变短,代码完成的速度就越快。不过,我真的不知道如何将其放入公式中。
我发现Artefacto对琴弦进行排序的方法提高了性能。添加
asort($array);
$array = array(array_shift($array), array_pop($array));
之前将显着提高性能。array_reduce
另请注意,这将返回最长的匹配初始子字符串,该子字符串更通用,但不会为您提供通用路径。你必须运行
substr($result, 0, strrpos($result, '/'));
在结果上。然后,您可以使用结果删除这些值
print_r(array_map(function($v) use ($path){
return str_replace($path, '', $v);
}, $array));
这应该给出:
[0] => /lib/abcdedd
[1] => /conf/xyz/
[2] => /conf/abc/def
[3] => /htdocs/xyz
[4] => /lib2/abcdedd
欢迎反馈。
如果仅从字符串比较的角度来看,则可以简化该问题。这可能比数组拆分更快:
$longest = $tetris[0]; # or array_pop()
foreach ($tetris as $cmp) {
while (strncmp($longest+"/", $cmp, strlen($longest)+1) !== 0) {
$longest = substr($longest, 0, strrpos($longest, "/"));
}
}
评论
可能太天真和菜鸟了,但它有效。我用过这个算法:
<?php
function strlcs($str1, $str2){
$str1Len = strlen($str1);
$str2Len = strlen($str2);
$ret = array();
if($str1Len == 0 || $str2Len == 0)
return $ret; //no similarities
$CSL = array(); //Common Sequence Length array
$intLargestSize = 0;
//initialize the CSL array to assume there are no similarities
for($i=0; $i<$str1Len; $i++){
$CSL[$i] = array();
for($j=0; $j<$str2Len; $j++){
$CSL[$i][$j] = 0;
}
}
for($i=0; $i<$str1Len; $i++){
for($j=0; $j<$str2Len; $j++){
//check every combination of characters
if( $str1[$i] == $str2[$j] ){
//these are the same in both strings
if($i == 0 || $j == 0)
//it's the first character, so it's clearly only 1 character long
$CSL[$i][$j] = 1;
else
//it's one character longer than the string from the previous character
$CSL[$i][$j] = $CSL[$i-1][$j-1] + 1;
if( $CSL[$i][$j] > $intLargestSize ){
//remember this as the largest
$intLargestSize = $CSL[$i][$j];
//wipe any previous results
$ret = array();
//and then fall through to remember this new value
}
if( $CSL[$i][$j] == $intLargestSize )
//remember the largest string(s)
$ret[] = substr($str1, $i-$intLargestSize+1, $intLargestSize);
}
//else, $CSL should be set to 0, which it was already initialized to
}
}
//return the list of matches
return $ret;
}
$arr = array(
'/www/htdocs/1/sites/lib/abcdedd',
'/www/htdocs/1/sites/conf/xyz',
'/www/htdocs/1/sites/conf/abc/def',
'/www/htdocs/1/sites/htdocs/xyz',
'/www/htdocs/1/sites/lib2/abcdedd'
);
// find the common substring
$longestCommonSubstring = strlcs( $arr[0], $arr[1] );
// remvoe the common substring
foreach ($arr as $k => $v) {
$arr[$k] = str_replace($longestCommonSubstring[0], '', $v);
}
var_dump($arr);
输出:
array(5) {
[0]=>
string(11) "lib/abcdedd"
[1]=>
string(8) "conf/xyz"
[2]=>
string(12) "conf/abc/def"
[3]=>
string(10) "htdocs/xyz"
[4]=>
string(12) "lib2/abcdedd"
}
:)
评论
/www/htdocs/1/sites/conf/
也许移植 Python 使用的算法会起作用?os.path.commonprefix(m)
def commonprefix(m):
"Given a list of pathnames, returns the longest common leading component"
if not m: return ''
s1 = min(m)
s2 = max(m)
n = min(len(s1), len(s2))
for i in xrange(n):
if s1[i] != s2[i]:
return s1[:i]
return s1[:n]
也就是说,呃......类似的东西
function commonprefix($m) {
if(!$m) return "";
$s1 = min($m);
$s2 = max($m);
$n = min(strlen($s1), strlen($s2));
for($i=0;$i<$n;$i++) if($s1[$i] != $s2[$i]) return substr($s1, 0, $i);
return substr($s1, 0, $n);
}
之后,您只需使用公共前缀的长度作为起始偏移量来减去原始列表的每个元素。
您可以以最快的方式删除前缀,每个字符只读取一次:
function findLongestWord($lines, $delim = "/")
{
$max = 0;
$len = strlen($lines[0]);
// read first string once
for($i = 0; $i < $len; $i++) {
for($n = 1; $n < count($lines); $n++) {
if($lines[0][$i] != $lines[$n][$i]) {
// we've found a difference between current token
// stop search:
return $max;
}
}
if($lines[0][$i] == $delim) {
// we've found a complete token:
$max = $i + 1;
}
}
return $max;
}
$max = findLongestWord($lines);
// cut prefix of len "max"
for($n = 0; $n < count($lines); $n++) {
$lines[$n] = substr(lines[$n], $max, $len);
}
评论
我会把我的帽子扔进擂台......
function longestCommonPrefix($a, $b) {
$i = 0;
$end = min(strlen($a), strlen($b));
while ($i < $end && $a[$i] == $b[$i]) $i++;
return substr($a, 0, $i);
}
function longestCommonPrefixFromArray(array $strings) {
$count = count($strings);
if (!$count) return '';
$prefix = reset($strings);
for ($i = 1; $i < $count; $i++)
$prefix = longestCommonPrefix($prefix, $strings[$i]);
return $prefix;
}
function stripPrefix(&$string, $foo, $length) {
$string = substr($string, $length);
}
用法:
$paths = array(
'/www/htdocs/1/sites/lib/abcdedd',
'/www/htdocs/1/sites/conf/xyz',
'/www/htdocs/1/sites/conf/abc/def',
'/www/htdocs/1/sites/htdocs/xyz',
'/www/htdocs/1/sites/lib2/abcdedd',
);
$longComPref = longestCommonPrefixFromArray($paths);
array_walk($paths, 'stripPrefix', strlen($longComPref));
print_r($paths);
好吧,这里已经有一些解决方案,但是,仅仅因为它很有趣:
$values = array(
'/www/htdocs/1/sites/lib/abcdedd',
'/www/htdocs/1/sites/conf/xyz',
'/www/htdocs/1/sites/conf/abc/def',
'/www/htdocs/1/sites/htdocs/xyz',
'/www/htdocs/1/sites/lib2/abcdedd'
);
function findCommon($values){
$common = false;
foreach($values as &$p){
$p = explode('/', $p);
if(!$common){
$common = $p;
} else {
$common = array_intersect_assoc($common, $p);
}
}
return $common;
}
function removeCommon($values, $common){
foreach($values as &$p){
$p = explode('/', $p);
$p = array_diff_assoc($p, $common);
$p = implode('/', $p);
}
return $values;
}
echo '<pre>';
print_r(removeCommon($values, findCommon($values)));
echo '</pre>';
输出:
Array
(
[0] => lib/abcdedd
[1] => conf/xyz
[2] => conf/abc/def
[3] => htdocs/xyz
[4] => lib2/abcdedd
)
好吧,考虑到您可以在这种情况下使用来查找字符串的公共部分。每当你对两个相同的字节进行异或时,你都会得到一个 null 字节作为输出。因此,我们可以利用它来发挥我们的优势:XOR
$first = $array[0];
$length = strlen($first);
$count = count($array);
for ($i = 1; $i < $count; $i++) {
$length = min($length, strspn($array[$i] ^ $first, chr(0)));
}
在该单循环之后,变量将等于字符串数组之间最长的公共基部。然后,我们可以从第一个元素中提取公共部分:$length
$common = substr($array[0], 0, $length);
你有它。作为功能:
function commonPrefix(array $strings) {
$first = $strings[0];
$length = strlen($first);
$count = count($strings);
for ($i = 1; $i < $count; $i++) {
$length = min($length, strspn($strings[$i] ^ $first, chr(0)));
}
return substr($first, 0, $length);
}
请注意,它确实使用了不止一次迭代,但这些迭代是在库中完成的,因此在解释型语言中,这将带来巨大的效率提升......
现在,如果你只想要完整的路径,我们需要截断到最后一个字符。所以:/
$prefix = preg_replace('#/[^/]*$', '', commonPrefix($paths));
现在,它可能会过度切割两根弦,例如 和 将被切割成 .但是,除了添加另一轮迭代来确定下一个字符是字符串末尾还是字符串末尾之外,我看不到解决这个问题的方法....../foo/bar
/foo/bar/baz
/foo
/
下一个:确定跨浏览器图像的原始大小?
评论