14
\$\begingroup\$

The challenge is to write a program which will find the shortest (least number of characters) command which will navigate to a target directory, given a starting directory, assuming a standard linux file system.

Take the following file system as an example, starting from ~:

.
├── target
└── test
    └── starting

If the user is in ~/test/starting and wants to navigate to ~/target, the shortest command is cd ~/target.

However, if the user is in ~/test/starting and wants to navigate to ~/test, the shortest command is cd ..

If the user wants to navigate to ~, the shortest command is cd ~.

If the user wants to navigate to /etc, the shortest command is cd /etc.

INPUT

The input is two strings; the starting directory and the target directory. For example: /etc/kernel and /opt. The method and order these are input is up to you. (eg: array, list, arguments, input, etc)

OUTPUT

Print or return the shortest command which navigates to the target directory.

SCORING

Standard code golf rules.

Some example test cases

Input format is starting target

Input: ~/a/b/c ~/a/z

Output: cd ~/a/z

Input: ~/apple/banana/carrot ~/apple/banana/z

Output: cd ../z

Input: ~/apple/banana/carrot /test

Output: cd /test

Input: /a/b /a/b/c

Output: cd c

Input: /a/b /a/b

Output: cd .

CLARIFICATIONS

  • ~ is equivalent to /home/golfer
  • Assume that inputs are already minimal. For example, you would not get /home/golfer/a as an input, and would instead get ~/a
  • The only aliases you need to worry about are / and ~
  • Shortest path from ~ to /home is ..
  • @loopywalt has pointed out that cd with no argument will navigate to ~. This behaviour will be ignored, so please assume that an argument must always be specified.
\$\endgroup\$
15
  • 1
    \$\begingroup\$ Welcome to Code Golf! A few questions: 1. do we have to handle symlinks and/or hardlinks? 2. do we need to include cd in the output? It seems like clutter that doesn't really add any complexity 3. can we assume things like trailing /s for directories, /home/... instead of ~, etc.? \$\endgroup\$ Commented Nov 28, 2022 at 3:19
  • 5
    \$\begingroup\$ On my box the shortest way to ~ is actually just cd without argument. Not sure whether that is site specific or true in general. \$\endgroup\$
    – loopy walt
    Commented Nov 28, 2022 at 4:17
  • 2
    \$\begingroup\$ @loopywalt seems like if HOME is defined then it will navigate to it by default if no argument is specified. Will update question. Edit: actually I'm going to leave it as is just so that if people have started on a solution it doesn't break it. \$\endgroup\$
    – Ted
    Commented Nov 28, 2022 at 4:20
  • 2
    \$\begingroup\$ @KevinCruijssen you can assume paths will have no spaces and directories will contains only A-Z, a-z and 0-9 \$\endgroup\$
    – Ted
    Commented Nov 28, 2022 at 9:41
  • 4
    \$\begingroup\$ Related \$\endgroup\$
    – Arnauld
    Commented Nov 28, 2022 at 10:11

6 Answers 6

6
\$\begingroup\$

05AB1E, 60 56 bytes

Ëi'.ë'~’/€¨/’:'/©δ¡ζ.γË}¦˜2ôøðδK`®ýsg…../×ì®Ü}Iθ)éн…cd ì

-4 bytes thanks to @JonathanAllan

Inputs as a pair.

Try it online or verify all test cases.

Explanation:

Ëi              # If both paths in the (implicit) input-pair are the same:
  '.           '#  Push "."
 ë              # Else:
  '~      :    '#  In the strings of the (implicit) input-pair, replace all "~"
    ’/€¨/’      #  with dictionary string "/home/"
  '/©          '#  Push "/", and save it in variable `®` (without popping)
     δ          #  Map over the pair, with "/" as argument:
      ¡         #   Split the paths in the strings by this "/"
  ζ.γË}¦˜2ôøðδK #  Remove the matching path-prefixes:
  ζ             #   Zip/transpose; swapping rows/columns,
                #   using a " " as filler-character if the paths are of unequal length
   .γ }         #   Then group the pairs by:
     Ë          #    Check if the two values in the pair are the same
      }¦        #   After the group-by: remove the first group
        ˜       #   Flatten it to a single list
         2ô     #   Split it back into pairs
           ø    #   Zip/transpose these pairs back
             δ  #   Map over each inner list:
            ð K #    Remove all spaces
  `             #  Pop and push both lists separated to the stack
   ®ý           #  Join the second list with "/" delimiter
   s            #  Swap so the first list is at the top of the stack
    g           #  Pop and push its length
     …../×      #  Pop this length, and repeat string "../" that many times
   ì            #  Prepend it in front of the other string
    ®Ü          #  And trim a potentially trailing "/"
 }              # Close the if-else statement
  Iθ            # Push the second string of the input-pair
)               # Wrap all values on the stack into a list
 é              # Sort it by length
  н             # Pop and leave just the first/shortest one
   …cd ì        # Prepend "cd " in front of it
                # (after which the result is output implicitly)

See this 05AB1E tip of mine (section How to use the dictionary?) to understand why ’/€¨/’ is "/home/".

\$\endgroup\$
2
  • 1
    \$\begingroup\$ Perhaps "you would not get /home/golfer/a as an input, and would instead get ~/a" allows you to use something shorter than Šˆer? \$\endgroup\$ Commented Nov 28, 2022 at 19:06
  • 1
    \$\begingroup\$ @JonathanAllan Thanks! Just removing it and using /home/ is indeed a lot shorter. :) \$\endgroup\$ Commented Nov 29, 2022 at 8:22
5
\$\begingroup\$

Python 3, 81 bytes

lambda p:'cd '+min(relpath(*map(expanduser,p)),p[0],key=len)
from os.path import*

Try it online!

This utilizes the os.path.relpath function to get a relative path. The shortest path command is either the relative path or the absolute path, whichever is shorter, so the code just checks which using min. expanduser is used to expand the ~ directory to the user's home directory, which doesn't have to be home/golfer to meet the requirements. This is necessary to handle cases with ~ in the path name.

\$\endgroup\$
3
  • \$\begingroup\$ Uh I noticed that someone posted a 69 byte answer a few minutes before me in python. Sorry I didn't see that one before I answered. \$\endgroup\$
    – Joe
    Commented Nov 28, 2022 at 18:06
  • 1
    \$\begingroup\$ This seems to output cd ../.. instead of cd .. for ['/home','~'] \$\endgroup\$ Commented Nov 28, 2022 at 18:15
  • 1
    \$\begingroup\$ @KevinCruijssen Ok, it should work now. Thanks for noticing that! \$\endgroup\$
    – Joe
    Commented Nov 28, 2022 at 19:21
5
\$\begingroup\$

JavaScript (Node.js), 101 bytes

Expects [ current_dir, target_dir ].

a=>'cd '+((s=require('path').relative(...a.map(s=>(q=s).replace(/~/,'/home/@')))||".")[q.length]?q:s)

Try it online!


JavaScript (Node.js), 66 bytes

Expects (current_dir)(target_dir).

Assumes a *nix system where ~ redirects to /home/some_user. On TIO, ~ is /home/runner/~. So this may fail because of that.

a=>b=>'cd '+((s=require('path').relative(a,b)||".")[b.length]?b:s)

Try it online!

\$\endgroup\$
1
\$\begingroup\$

Retina 0.8.2, 99 bytes

$
¶$%`
¶
/¶
m`^~(?=/.*¶)
/home/golfer
^(.*/)(.*¶)\1
$2
\G.*?/
../
1`¶

/¶
¶
O#$`
$.&
1G`
^$
.
^
cd 

Try it online! Takes input on separate lines but link is to test suite that splits on space for convenience. Explanation:

$
¶$%`

Duplicate the last (to) line.

¶
/¶

Append / to the first two (original from and to) lines.

m`^~(?=/.*¶)
/home/golfer

Expand any leading ~ on the first two lines.

^(.*/)(.*¶)\1
$2

Remove any common prefix (ending in /) on the first two lines.

\G.*?/
../

Change any remaining "from" directories to ...

1`¶

Join those ..s with the remaining "to" directories.

/¶
¶

Remove the trailing /.

O#$`
$.&

Sort numerically by length.

1G`

Take the first i.e. shortest string.

^$
.

If it's the empty string then replace it with ..

^
cd 

Prefix cd to the string.

\$\endgroup\$
1
\$\begingroup\$

C# (Visual C# Interactive Compiler), 66 bytes

(a,b,c)=>"cd "+((c=Path.GetRelativePath(a,b)).Length<b.Length?c:b)

Try it online!

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

Python, 87 bytes

lambda t,s:"cd "+[t,r:=relpath(t:=expanduser(t),s)][len(r)<len(t)];from os.path import*

Attempt This Online!

Expects target, start.

\$\endgroup\$
1
  • 2
    \$\begingroup\$ This seems to output cd /home instead of cd .. for f("/home","~") \$\endgroup\$ Commented Nov 28, 2022 at 18:16

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.