1

I have tried for a long time but couldn't find a way to merge an array in to a new one. Mostly I get lost in looping and matching.;(

I would like to recieve a php 5 method that can do the following:

Example 1

Lets say there is an array with url's like:

Array(
'a',
'a/b/c',
'a/b/c/d/e',
'a/y',
'b/z',
'b/z/q/',
)

Every last folder of the url's is the folder where a user has the right to view.

I would like to send the array to a method that returns a new array like:

Array[](
'a/c/e'
'a/y'
'z/q'
)

The method has combined some elements of the origninal array into one element. This because there is a match in allowed ending folders.

Example 2

Array(
'projects/projectA/books'
'projects/projectA/books/cooking/book1'
'projects/projectA/walls/wall'
'projects/projectX/walls/wall'
'projects/projectZ/'
'projects/projectZ/Wood/Cheese/Bacon'
)

I would like to get a an array like:

Array[](
'books/book1'
'wall'
'wall'
'projectZ/Bacon'
)

Then it would be great (specialy in case of the 'wall' values) to have some references to the full path's of the original array.

4
  • can you let us know the exact format of your desired output? it's confusing right now Commented Aug 17, 2017 at 17:25
  • Output is edited. Commented Aug 17, 2017 at 17:28
  • your two inputs and there corresponding outputs don't have same logic. first one output is not having same logic what second-one have. first-one output need to be:-Array( 'a/c/e/y' 'z/q' ) Commented Aug 18, 2017 at 7:57
  • 'y' isn't a subfolder from 'b', 'c' or 'e'. 'y' is a subfolder from 'a'. Commented Aug 19, 2017 at 17:06

3 Answers 3

3

Do it like below:-

<?php

$array = Array(
    'projects/projectA/books',
    'projects/projectA/books/cooking/book1',
    'projects/projectA/walls/wall',
    'projects/projectX/walls/wall',
    'projects/projectZ/',
    'projects/projectZ/Wood/Cheese/Bacon'
);// original array

$final_array =array(); // new array variable

foreach($array as $key=>$arr){ // iterate over original array
   $exploded_string = end(array_filter(explode('/',$arr))); // get last-value from the url string

    foreach($array as $ar){ // iterate again the original array to compare this string withh each array element
       $new_exploded_string = end(array_filter(explode('/',$ar))); // get the new-last-values from url string again
        if($arr !== $ar && strpos($ar,$exploded_string) !==false){ // if both old and new url strings are not equal and old-last-value find into url string
            if($exploded_string == $new_exploded_string ){  // if both new-last-value and old-last-value are equal
                $final_array[] = $exploded_string;
            }else{
                $final_array[] = $exploded_string.'/'.$new_exploded_string ;
            }
        }
    }
}

print_r($final_array);

Output:-https://eval.in/846738

Sign up to request clarification or add additional context in comments.

Comments

2

Well, there isn't a single built-in function for this ;)

$items = array(
    'projects/projectA/books',
    'projects/projectA/books/cooking/book1',
    'projects/projectA/walls/wall',
    'projects/projectX/walls/wall',
    'projects/projectZ/',
    'projects/projectZ/Wood/Cheese/Bacon',

    'hold/mold/gold/sold/fold',
    'hold/mold/gold',
    'raja/maza/saza',
    'raja/maza',
    'mohit/yenky/client/project',

);
echo '$items = ' . nl2br(htmlspecialchars(print_r($items, true))); //Debug

// Sort, so the shorter basePath comes before the longer subPath
usort($items, function($a, $b) {
    if (strlen($a) == strlen($b)) {
        return 0;
    } else {
        return strlen($a) > strlen($b) ? 1 : -1;
    }
});

$result = array();
while($basePath = array_shift($items)) { // As long as there is a next item
    $basePath = rtrim($basePath, '/'); // Right trim extra /
    foreach($items as $idx => $subPath) {
        if (strpos($subPath, $basePath . '/') === 0) {
            // $subPath begins with $basePath
            $result[] = preg_replace('#.*/#', '', $basePath) . '/' . preg_replace('#.*/#', '', rtrim($subPath, '/'));
            unset($items[$idx]); // Remove item from array, so it won't be matched again
            continue 2; // Continue with next while($basePath = array_shift($items))
        }
    }

    // No subPath found, otherwise continue would have called (skipping below code)
    $result[] = preg_replace('#.*/#', '', $basePath);
}

echo '$result = ' . nl2br(htmlspecialchars(print_r($result, true))); //Debug

PHPFiddle: http://phpfiddle.org/main/code/ugq9-hy0i

5 Comments

Thanks. Almost. I've posted a reply.
@RalphDelnoy I've updated my answer to fix this (added the usort and new example-data). Note: you shouldn't use an answer as a reply (as it isn't an answer to your own question), instead edit your question to add (more) example-data
Okay willl notice. Thanks by the way, Iam still testing. Iam studing your method and is there a way to add have a second array that contains the full paths of the new array? So that in case of 'wall' I know witch wall?
For example replace $result[] = preg_replace(..); with something like $result[] = array('short' => preg_replace(..), 'full' => $basePath),
Oh no.. Doesnt work with a new example. (edited array is in orininal post)
2

You can avoid using nested loops (and, actually, you should avoid):

sort($array);
$carry = array_shift($array);
$result = [];
$i = 0;

$lastItem = array_reduce($array, function ($carry, $item) use (&$result, &$i) {
    $result[$i] = isset($result[$i]) 
        ? array_merge($result[$i], [basename($carry)]) 
        : [basename($carry)];

    if (strpos($item, $carry) !== 0) {
        $i += 1;
    }

    return $item;
}, $carry);

if (!empty($lastItem)) {
    $result[$i] = isset($result[$i]) 
        ? array_merge($result[$i], [basename($lastItem)]) 
        : [basename($lastItem)];
}

$result = array_map(function ($item) {
    return implode('/', $item);
}, $result);

Here is working demo.

We use array_reduce here to get access to the previously processed item. Also, PHP has function basename, that retrieves the basename. So you can use it and do not reinvent the wheel.

3 Comments

Thanks, looks like it works! Super! Is there a way to mark every new element with the full path. So that in case of 'wall' I know witch wall?
Here is demo. I have extracted adding to results logic to the anonymous function, so you can change only it. Now the full path is specified in the parentheses after the basename.
Oh no.. Doesnt work with a new example. (edited array is in orininal post)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.