2
\$\begingroup\$

I made the following function to create the storage path for files from the the first 5 characters of the file's hash:

function create_path($hash)
{
  $splitted_hash = str_split(strtolower(substr($hash, 0, 5)));
  $path = "";
  foreach($splitted_hash as $char) {
        $path.="/$char";
  }
  return "$path/";
}
// will output something like "a/h/q/6/d/"

But is there a better way to do that? This seems a bit cumbersome.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ s/splitted/split/g \$\endgroup\$
    – 2e0byo
    Nov 21, 2022 at 21:31
  • 3
    \$\begingroup\$ Please do not edit the question, especially the code, after an answer has been posted. Changing the question may cause answer invalidation. Everyone needs to be able to see what the reviewer was referring to. What to do after the question has been answered. \$\endgroup\$
    – pacmaninbw
    Nov 21, 2022 at 23:57
  • 1
    \$\begingroup\$ Oh don't worry, all I edited was the variable name to match with the answer after I also edited the answer \$\endgroup\$
    – pileup
    Nov 22, 2022 at 7:23

4 Answers 4

4
\$\begingroup\$

Php has a join (also known as implode) so you can simply do

function createPath($hash)
{
  return join("/", str_split(strtolower(substr($hash, 0, 5)))) . "/";
}

Note the concatenation for the final "/".

\$\endgroup\$
3
  • \$\begingroup\$ Oh nice, thank you! In my case I prefer to have forward slash at the end because it's a directory so I will just do it in 2 lines (Unless you have a nicer code like the one you wrote above), and do return "$path/"; \$\endgroup\$
    – pileup
    Nov 21, 2022 at 21:40
  • 1
    \$\begingroup\$ Can't you concatenate in the return line? My php is rusty, hang on \$\endgroup\$
    – 2e0byo
    Nov 21, 2022 at 21:41
  • 1
    \$\begingroup\$ See update answer. \$\endgroup\$
    – 2e0byo
    Nov 21, 2022 at 21:44
3
\$\begingroup\$

To win the prize for ugliest pattern, you can match individual alphanumeric characters with \w (which also technically matches underscores) then forget them with \K so that you don't need to mention them as a backreference in the replacement string.

Later in the same pattern if there are 5 characters before the current position, you can match the remainder of the string and replace it with a slash.

Code: (Demo)

function create_path(string $hash): string
{
    return preg_replace('#\w\K((?<=\w{5}).+)?#', '/', strtolower($hash));
}

echo create_path('aHq6dc93d7b');
// will output "a/h/q/6/d/"

If you want to make one more step away from professional looking code, you can access each character by its offset.

Code: (Demo)

function create_path(string $hash): string
{
    return strtolower("{$hash[0]}/{$hash[1]}/{$hash[2]}/{$hash[3]}/{$hash[4]}/");
}

echo create_path('aHq6dc93d7b');
// will output "a/h/q/6/d/"
\$\endgroup\$
3
  • \$\begingroup\$ As a code pessimisation this is amazing... \$\endgroup\$
    – 2e0byo
    Nov 22, 2022 at 10:24
  • \$\begingroup\$ @2e0byo you mean the above is faster? I just checked it on 50,000,000 loop and it did it in 6 seconds, whereas the join did it in 13 seconds, so that's pretty amazing indeed \$\endgroup\$
    – pileup
    Nov 23, 2022 at 21:15
  • \$\begingroup\$ I meant it's unreadable! If this is really a bottleneck, maybe, but I struggle to believe it could be... \$\endgroup\$
    – 2e0byo
    Nov 23, 2022 at 21:32
3
\$\begingroup\$

Use consistent spacing, Follow PHP standards for readability

There are recommended standards for PHP in the PHP Standards Recommendations - i.e. P.S.R.s. Between PSR-1 Basic Coding Standard and PSR-12: Extended Coding Style many common conventions for idiomatic PHP are outlined. In the original code the first couple lines have spaces around the binary operator =:

 $splitted_hash = str_split(strtolower(substr($hash, 0, 5)));
 $path = "";

there are no spaces around the operator .= within the loop:

foreach($splitted_hash as $char) {
      $path.="/$char";
}

Adding a space before and after the assignment operator, as well as keywords like foreach, is a common convention for readability.

foreach ($splitted_hash as $char) {
    $path .= "/$char";
}

Simpler approaches to adding slashes

One could use regular expressions to replace each character with the slash followed by the character. The sample below uses preg_replace() to match any character that is not a white space character - with the Generic character type \S.

function create_path($hash)
{
    return preg_replace('#\S#u', '/$0', strtolower(substr($hash, 0, 5))) .'/';
}

online demo

Other generic character types could be used - e.g. \w for any word character - in general i.e. A-Z, a-z, 0-9 and _, though it can vary depending on the locale:

A "word" character is an underscore or any character that is a letter or digit. By default, the definition of letters and digits is controlled by PCRE's low-valued character tables, and may vary if locale-specific matching is taking place (see "Locale support" in the pcreapi page). For example, in a French locale such as "fr_FR" in Unix-like systems, or "french" in Windows, some character codes greater than 127 are used for accented letters, and these are then matched by \w. The use of locales with Unicode is discouraged. 1

\$\endgroup\$
0
2
\$\begingroup\$

If performance is a goal, and the number of subdirectories is always the same, why don't you simply do that :

function create_path($hash)
{
  return "$hash[0]/$hash[1]/$hash[2]/$hash[3]/$hash[4]";
}

I made a benchmark on 10,000,000 function call, your code gives this :
real 0m4.059s
user 0m4.016s
sys 0m0.047s

The former gives this :
real 0m1.204s
user 0m1.172s
sys 0m0.016s

After a few runs of each solution.

\$\endgroup\$
3
  • \$\begingroup\$ That's nice, and I just realized also @mickmackusa did something similar. I just tested it in a loop of 50,000,000 times and it did it in 6 seconds, whereas the other answer with join did it in 13 seconds, that's amazing. It looks so simple that it's unthinkable of :D, I'm probably going to use this one! \$\endgroup\$
    – pileup
    Nov 23, 2022 at 21:17
  • \$\begingroup\$ "did something similar"? Um, this is a benchmark on one of the snippets in my review with the strtolower() call removed. We kinda want reviewers to at least look at the previously posted reviews to avoid posting redundant advice. This answer could have been a comment under my answer. \$\endgroup\$ Nov 23, 2022 at 21:33
  • \$\begingroup\$ Oops sorry for this one i'm new and didn't check all your answers. My bad. \$\endgroup\$
    – James
    Nov 25, 2022 at 6:37

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.