Tetris-ing อาร์เรย์


99

พิจารณาอาร์เรย์ต่อไปนี้:

/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

4
สิ่งนี้อาจคุ้มค่าที่จะลอง: en.wikibooks.org/wiki/Algorithm_implementation/Strings/… (ฉันลองแล้วและได้ผล)
Richard Knop

1
โอ้! อินพุตที่ยอดเยี่ยมมากมาย ฉันจะใช้วิธีหนึ่งในการแก้ปัญหาของฉันเอง แต่ฉันรู้สึกว่าถ้าจะเลือกคำตอบที่เป็นที่ยอมรับจริงๆฉันจะต้องเปรียบเทียบวิธีแก้ปัญหา อาจต้องใช้เวลาสักพักกว่าจะทำอย่างนั้นได้ แต่แน่นอนว่าจะต้องทำ
Pekka

ชื่อเรื่องความบันเทิง: D btw: ทำไมฉันไม่พบคุณในรายชื่อผู้ดูแลที่ได้รับการเสนอชื่อ? @Pekka
The Surrican

2
ไม่มีคำตอบที่ยอมรับเป็นเวลาสองปี?
Gordon

1
@Pekka เข้าใกล้สามปีแล้วนับตั้งแต่ไม่มีคำตอบที่ยอมรับ :( และมันเป็นชื่อที่ยอดเยี่ยมมากที่ฉันจำได้เมื่อสักครู่ที่แล้วและ googled "tetrising an array"
Camilo Martin

คำตอบ:


35

เขียนฟังก์ชันlongest_common_prefixที่ใช้สองสตริงเป็นอินพุต จากนั้นนำไปใช้กับสตริงในลำดับใดก็ได้เพื่อลดให้เป็นคำนำหน้าทั่วไป เนื่องจากเป็นแบบเชื่อมโยงและสับเปลี่ยนลำดับจึงไม่สำคัญสำหรับผลลัพธ์

นี่เหมือนกับการดำเนินการไบนารีอื่น ๆ เช่นการบวกหรือตัวหารร่วมที่ยิ่งใหญ่ที่สุด


8
+1. หลังจากเปรียบเทียบ 2 สตริงแรกแล้วให้ใช้ผลลัพธ์ (เส้นทางทั่วไป) เพื่อเปรียบเทียบกับสตริงที่ 3 และอื่น ๆ
Milan Babuškov

23

โหลดลงในโครงสร้างข้อมูล trie เริ่มต้นจากโหนดแม่ดูว่ามีลูกใดมากกว่าหนึ่งโหนด เมื่อคุณพบโหนดเวทย์มนตร์นั้นให้ถอดโครงสร้างโหนดหลักและกำหนดให้โหนดปัจจุบันเป็นรูท


10
การดำเนินการที่โหลดข้อมูลลงในโครงสร้างทรีทรีที่คุณอธิบายไว้จะไม่รวมอัลกอริทึมเพื่อค้นหาคำนำหน้าทั่วไปที่ยาวที่สุดดังนั้นการใช้โครงสร้างต้นไม้จึงไม่จำเป็นจริง ๆ หรือ? เช่นทำไมต้องตรวจสอบต้นไม้สำหรับเด็กหลาย ๆ คนในเมื่อคุณสามารถตรวจพบสิ่งนั้นได้ในขณะที่สร้างต้นไม้ แล้วทำไมต้องเป็นต้นไม้ล่ะ? ฉันหมายถึงถ้าคุณเริ่มต้นด้วยอาร์เรย์แล้ว หากคุณสามารถเปลี่ยนที่เก็บข้อมูลเป็นเพียงการใช้ Trie แทนอาร์เรย์ฉันคิดว่ามันสมเหตุสมผล
Ben Schwehn

2
ฉันคิดว่าถ้าคุณระวังแล้วการแก้ปัญหาของฉันจะมีประสิทธิภาพมากกว่าการสร้าง Trie
starblue

คำตอบนี้ผิด มีวิธีแก้ปัญหาเล็กน้อยที่โพสต์ไว้ในคำตอบของฉันและอื่น ๆ ที่เป็น O (n)
Ari Ronen

@ el.pescado: Tries มีขนาดกำลังสองพร้อมกับความยาวของสตริงต้นทางในกรณีที่เลวร้ายที่สุด
Billy ONeal

10
$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/) ฉัน (หวังว่า) จะแก้ไขทั้งสองอย่าง
Gabe

7

เมื่อพิจารณาว่าคุณสามารถใช้XORในสถานการณ์นี้เพื่อค้นหาส่วนทั่วไปของสตริง ทุกครั้งที่คุณ x หรือสองไบต์ที่เท่ากันคุณจะได้ nullbyte เป็นเอาต์พุต ดังนั้นเราจึงสามารถใช้สิ่งนั้นให้เป็นประโยชน์:

$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แต่ขาดการเพิ่มรอบการวนซ้ำอีกรอบเพื่อตรวจสอบว่าอักขระถัดไปเป็น/ หรือสิ้นสุดสตริงฉันไม่สามารถมองเห็นทางนั้นได้ ...


3

วิธีการที่ไร้เดียงสาคือการระเบิดเส้นทางที่/และต่อเนื่องเปรียบเทียบทุกองค์ประกอบในอาร์เรย์ ดังนั้นเช่นองค์ประกอบแรกจะว่างเปล่าในอาร์เรย์ทั้งหมดดังนั้นองค์ประกอบจะถูกลบออกองค์ประกอบถัดไปจะเป็น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

โอเคฉันไม่แน่ใจว่านี่เป็นแบบกันกระสุน แต่ฉันคิดว่ามันใช้ได้:

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จะเพิ่มประสิทธิภาพอย่างมีนัยสำคัญ

นอกจากนี้ยังทราบว่านี้จะกลับมาจับคู่ substring เริ่มต้นที่ยาวที่สุดซึ่งมีมากขึ้นหลากหลาย แต่เคยชินให้คุณเส้นทางที่พบบ่อย คุณต้องวิ่ง

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

คุณสามารถลบคำนำหน้าด้วยวิธีที่เร็วที่สุดโดยอ่านแต่ละอักขระเพียงครั้งเดียว:

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

อันที่จริงการเปรียบเทียบตามอักขระจะเร็วที่สุด โซลูชันอื่น ๆ ทั้งหมดใช้ตัวดำเนินการ "ราคาแพง" ซึ่งในที่สุดก็จะทำการเปรียบเทียบอักขระ (หลายตัว) มีการกล่าวถึงในพระคัมภีร์ของโจเอลผู้บริสุทธิ์ด้วยซ้ำ !
ม.ค. Fabry

2

สิ่งนี้มีข้อดีคือไม่มีความซับซ้อนของเวลาเชิงเส้น อย่างไรก็ตามในกรณีส่วนใหญ่การจัดเรียงจะไม่เป็นการดำเนินการที่ใช้เวลามากขึ้น

โดยพื้นฐานแล้วส่วนที่ฉลาด (อย่างน้อยฉันก็ไม่พบข้อผิดพลาด) ที่นี่คือหลังจากจัดเรียงแล้วคุณจะต้องเปรียบเทียบเส้นทางแรกกับเส้นทางสุดท้ายเท่านั้น

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
$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>';

แก้ไข

คำตอบที่มีประสิทธิภาพและสง่างามที่สุดน่าจะเกี่ยวข้องกับการใช้ฟังก์ชันและวิธีการจากคำตอบที่ให้ไว้


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

สิ่งนี้ยังไม่ผ่านการทดสอบ แต่แนวคิดก็คือ$commonPathอาร์เรย์จะมีองค์ประกอบของเส้นทางที่มีอยู่ในอาร์เรย์พา ธ ทั้งหมดเท่านั้นที่ถูกเปรียบเทียบกับอาร์เรย์ เมื่อการวนรอบเสร็จสมบูรณ์เราก็รวมกันใหม่ด้วย / เพื่อให้ได้ค่าจริง$commonPath

อัปเดต ตามที่ระบุไว้โดย Felix Kling array_intersectจะไม่พิจารณาเส้นทางที่มีองค์ประกอบทั่วไป แต่อยู่ในลำดับที่ต่างกัน ... เพื่อแก้ปัญหานี้ฉันใช้array_intersect_assocแทนarray_intersect

อัปเดต โค้ดที่เพิ่มเพื่อลบพา ธ ทั่วไป (หรือ tetris it!) ออกจากอาร์เรย์ด้วย


สิ่งนี้อาจใช้ไม่ได้ พิจารณา/a/b/c/dและ/d/c/b/a. องค์ประกอบเดียวกันเส้นทางที่แตกต่างกัน
Felix Kling

@Felix Kling ฉันได้อัปเดตเพื่อใช้ array_intersect_assoc ซึ่งทำการตรวจสอบดัชนีด้วย
Brendan Bullen

1

ปัญหาสามารถทำให้ง่ายขึ้นได้หากมองจากมุมเปรียบเทียบสตริง อาจเร็วกว่าการแยกอาร์เรย์:

$longest = $tetris[0];  # or array_pop()
foreach ($tetris as $cmp) {
        while (strncmp($longest+"/", $cmp, strlen($longest)+1) !== 0) {
                $longest = substr($longest, 0, strrpos($longest, "/"));
        }
}

ซึ่งจะใช้ไม่ได้เช่นกับอาร์เรย์ชุดนี้ ('/ www / htdocs / 1 / sites / conf / abc / def', '/ www / htdocs / 1 / sites / htdocs / xyz', '/ www / htdocs / 1 / sitesjj / lib2 / abcdedd ',)
Artefacto

@Artefacto: คุณพูดถูก ดังนั้นฉันจึงแก้ไขให้รวมเครื่องหมายทับ "/" ในการเปรียบเทียบเสมอ ทำให้ไม่คลุมเครือ.
มาริโอ

1

บางทีการพอร์ตการใช้อัลกอริทึมของ 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

ฉันจะโยนหมวกของฉันใส่แหวน ...

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

1

มีวิธีแก้ปัญหาอยู่แล้ว แต่เพียงเพราะมันสนุก:

$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
)

0
$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

อาจจะไร้เดียงสาเกินไปและไม่น่าเบื่อ แต่ก็ใช้ได้ผล ฉันใช้อัลกอริทึมนี้แล้ว :

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

:)


@Doomsday มีลิงค์ไปยัง wikipedia ในคำตอบของฉัน ... ลองอ่านดูก่อนก่อนแสดงความคิดเห็น
Richard Knop

ฉันคิดว่าสุดท้ายแล้วคุณจะเปรียบเทียบสองเส้นทางแรกเท่านั้น ในตัวอย่างของคุณใช้งานได้ แต่ถ้าคุณลบเส้นทางแรกออกจะพบ/www/htdocs/1/sites/conf/ว่าเป็นการจับคู่ทั่วไป นอกจากนี้อัลกอริทึมจะค้นหาสตริงย่อยที่เริ่มต้นที่ใดก็ได้ในสตริง แต่สำหรับคำถามนี้คุณรู้ว่าคุณสามารถเริ่มต้นที่ตำแหน่ง 0 ซึ่งทำให้ง่ายกว่ามาก
ม.ค. Fabry
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.