Луг Тетриса массив

Рассмотрите следующий массив:

/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
99
задан Mechanical snail 22 September 2012 в 10:56
поделиться

13 ответов

У этого есть преимущество, заключающееся в отсутствии линейной временной сложности; однако в большинстве случаев сортировка определенно не будет операцией, требующей больше времени.

По сути, самая хитрая часть (по крайней мере, я не мог найти в ней недостатка) заключается в том, что после сортировки вам нужно будет только сравнить первый путь с последним.

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);
2
ответ дан 24 November 2019 в 05:06
поделиться

Проблема может быть упрощена, если просто смотреть с точки зрения сравнения строк. Вероятно, это быстрее, чем разделение массива:

$longest = $tetris[0];  # or array_pop()
foreach ($tetris as $cmp) {
        while (strncmp($longest+"/", $cmp, strlen($longest)+1) !== 0) {
                $longest = substr($longest, 0, strrpos($longest, "/"));
        }
}
1
ответ дан 24 November 2019 в 05:06
поделиться

Хорошо, я не уверен, что это пуленепробиваемый, но я думаю, что это работает:

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;
});

Это примет первое значение в массиве как ссылочную строку. Затем он будет перебирать ссылочную строку и сравнивать каждый символ с символом второй строки в той же позиции. Если символ не соответствует, ссылочная строка будет сокращена до позиции символа, и следующая строка будет сравнена. Тогда функция вернет самую короткую совпадающую строку.

Исполнение зависит от данных струн. Чем раньше станет короче ссылочная строка, тем быстрее завершится код. Я действительно понятия не имею, как выразить это в формуле.

Я обнаружил, что подход 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

Обратная связь приветствуется.

3
ответ дан 24 November 2019 в 05:06
поделиться
$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

0
ответ дан 24 November 2019 в 05:06
поделиться

Я бы взорвал значения на основе / и затем использовал 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;
}

Это не проверено, но идея заключается в том, что массив $commonPath будет содержать только те элементы пути, которые содержались во всех массивах путей, которые сравнивались с ним. После завершения цикла мы просто перекомбинируем его с /, чтобы получить истинный $commonPath

Update. Как указал Феликс Клинг, array_intersect не будет рассматривать пути, имеющие общие элементы, но расположенные в разных порядках... Чтобы решить это, я использовал array_intersect_assoc вместо array_intersect

Update. Добавлен код для удаления общего пути (или тетриса!) из массива.

1
ответ дан 24 November 2019 в 05:06
поделиться

Наверное, тоже наивно и глупо, но это работает. Я использовал этот алгоритм :

<?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"
}

:)

0
ответ дан 24 November 2019 в 05:06
поделиться

Возможно, перенос алгоритма, используемого в 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);
}

После этого вы можете просто подстроить каждый элемент исходного списка с длиной общего префикса в качестве начального смещения.

1
ответ дан 24 November 2019 в 05:06
поделиться

Загрузите их в структуру данных trie. Начиная с родительского узла, посмотрите, у какого из них число детей больше единицы. Как только вы найдете этот волшебный узел, просто демонтируйте структуру родительского узла и сделайте текущий узел корневым.

23
ответ дан 24 November 2019 в 05:06
поделиться
$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;
}
10
ответ дан 24 November 2019 в 05:06
поделиться
$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>';

EDIT Вариант моего исходного метода с использованием 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>';

EDIT

Наиболее эффективный и элегантный ответ, вероятно, будет включать использование функций и методов из каждого из предоставленные ответы

2
ответ дан 24 November 2019 в 05:06
поделиться

Наивный подход заключался бы в том, чтобы разбить пути на / и последовательно сравнить каждый элемент в массивах. Так, например, первый элемент будет пустым во всех массивах, поэтому он будет удален, следующим будет 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
)

Это может плохо масштабироваться;)

3
ответ дан 24 November 2019 в 05:06
поделиться

Напишите функцию longest_common_prefix , которая принимает в качестве входных данных две строки. Затем примените его к строкам в любом порядке, чтобы уменьшить их до их общего префикса. Поскольку он ассоциативен и коммутативен, порядок не имеет значения для результата.

Это то же самое, что и для других двоичных операций, таких как, например, сложение или наибольший общий делитель.

35
ответ дан 24 November 2019 в 05:06
поделиться

Вы можете удалить префикс самым быстрым способом, читая каждый символ только один раз:

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);
}
3
ответ дан 24 November 2019 в 05:06
поделиться
Другие вопросы по тегам:

Похожие вопросы: