17
\$\begingroup\$

Sometimes a long absolute path, in e.g. a command-line parameter to a linux tool, can be shortened, using current working directory as reference:

$ pwd
/home/heh

$ cat /home/heh/mydir/myfile
my stuff

$ cat mydir/myfile
my stuff

In this challenge, you should make a function or a program that receives two parameters:

  1. Absolute path, using the linux format (starts with /)
  2. Current directory, using the same format

The output is the shorter of the following:

  • Input 1 unchanged
  • Relative path that refers to the same file/directory as the absolute path

Fine points:

  • If your operating system is compatible with linux, you can use the system's current directory instead of receiving it as input
  • You can assume the inputs contain only alphanumeric characters (and path separators)
  • You can assume the input absolute path doesn't have a path separator / at the end
  • You can assume the input current directory has a path separator / at the end
  • You cannot assume that the absolute path refers to an existing file, or that any part of it is an accessible directory; however, the current directory can be assumed valid
  • You can assume there are no symlinks anywhere near either path - because I don't want to require any special way of dealing with symlinks
  • No need to support the case where either of the inputs is the root directory
  • "The current directory" should be output as . (an empty string is not valid)

Test cases (input1, input2, output):

/home/user/mydir/myfile
/home/user
mydir/myfile

/var/users/admin/secret/passwd
/var/users/joe/hack
../../admin/secret/passwd

/home/user/myfile
/tmp/someplace
/home/user/myfile

/dir1/dir2
/dir1/dir2/dir3/dir4
../..

/dir1/dir2
/dir1/dir2
.
\$\endgroup\$
16
  • 1
    \$\begingroup\$ "You can assume the input current directory has a path separator / at the end". However, in your examples, this is not the case. \$\endgroup\$
    – Shaggy
    Apr 19, 2017 at 16:30
  • 1
    \$\begingroup\$ I like it this way, but some people like it the other way \$\endgroup\$
    – anatolyg
    Apr 19, 2017 at 16:33
  • \$\begingroup\$ Closely related. \$\endgroup\$ Apr 19, 2017 at 17:01
  • \$\begingroup\$ What should happen if absolute and relative path have the same length? \$\endgroup\$
    – Dennis
    Apr 19, 2017 at 19:00
  • 1
    \$\begingroup\$ This is missing some critical test cases: /home/test /home/user/mydir/myfile /home/test and /a/b /a/b/d/e /a/b \$\endgroup\$ Apr 20, 2017 at 16:38

9 Answers 9

9
\$\begingroup\$

JavaScript (ES6), 107 106 bytes

Takes the absolute path a and the current path c in currying syntax (a)(c).

a=>c=>(A=a.split`/`,s='',c.split`/`.map(d=>!s&A[0]==d?A.shift():s+='../'),s+=A.join`/`)[a.length]?a:s||'.'

Test cases

let f =

a=>c=>(A=a.split`/`,s='',c.split`/`.map(d=>!s&A[0]==d?A.shift():s+='../'),s+=A.join`/`)[a.length]?a:s||'.'

console.log(f
  ('/home/user/mydir/myfile')
  ('/home/user')
);

console.log(f
  ('/var/users/admin/secret/passwd')
  ('/var/users/joe/hack')
);

console.log(f
  ('/home/user/myfile')
  ('/tmp/someplace')
);

console.log(f
  ('/dir1/dir2')
  ('/dir1/dir2/dir3/dir4')
);

console.log(f
  ('/dir1/dir2')
  ('/dir1/dir2')
);

\$\endgroup\$
2
  • \$\begingroup\$ A very nice trick with [a.length] ! May I borrow it to improve my Node.js answer ? \$\endgroup\$
    – zeppelin
    Apr 19, 2017 at 21:33
  • \$\begingroup\$ @zeppelin Sure. Go for it! \$\endgroup\$
    – Arnauld
    Apr 19, 2017 at 21:55
8
\$\begingroup\$

Retina, 85 83 82 bytes

1 byte saved thanks to @MartinEnder

^(..+)(.*;)\1
%$2
(%?)(.*);(.*)
$1$3;$2
\w+(?=.*;)
..
%;/

;
/
.*//
/
%/?|/$

^$
.

Try it online!

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

Julia 0.5, 32 bytes

!,~=relpath,endof
t->~t<~!t?t:!t

This uses the current working directory as base and cannot be tested on TIO at the moment.

Example run

Warning: This will alter your file system.

$ sudo julia --quiet
julia> function test(target,base)
       mkpath(base)
       cd(base)
       shorten(target)
       end
test (generic function with 1 method)
julia> !,~=relpath,endof
(relpath,endof)

julia> shorten = t->~t<~!t?t:!t
(::#1) (generic function with 1 method)

julia> test("/home/user/mydir/myfile","/home/user")
"mydir/myfile"

julia> test("/var/users/admin/secret/passwd","/var/users/joe/hack")
"../../admin/secret/passwd"

julia> test("/home/user/myfile","/tmp/someplace")
"/home/user/myfile"

julia> test("/dir1/dir2","/dir1/dir2/dir3/dir4")
"../.."

julia> test("/dir1/dir2","/dir1/dir2")
"."

Alternate version, 35 bytes (dyadic)

^,~=relpath,endof
t-b=~t<~t^b?t:t^b

This takes the base directory as input, so it can be tested without modifying the file system.

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ Redefining Base.- errors unless explicitly imported, no? \$\endgroup\$ Apr 20, 2017 at 23:43
  • \$\begingroup\$ In 0.5, it may error, but only if you use - before redefining it. In 0.4, it prints a warning whether you use it before the redefinition or not. \$\endgroup\$
    – Dennis
    Apr 21, 2017 at 0:59
5
\$\begingroup\$

ES6 (Node.js REPL), 56, 54, 46, 45 bytes

  • Use empty string, instead of "." to denote the current directory (on input), -1 byte
  • Borrowed the [f.length] trick from @Arnauld's answer, -6 bytes
  • Use the current directory instead of an explicit directory parameter, -2 bytes
  • Removed superfluous parentheses, -2 bytes

Golfed

f=>(r=path.relative("",f))[f.length]?f:r||"."

Test

> F=f=>(r=path.relative("",f))[f.length]?f:r||"."
[Function: F]

> F("/home/user/mydir/myfile")
'mydir/myfile'

> F("/var/users/admin/secret/passwd")
'../../admin/secret/passwd'

> F("/home/user/myfile")
'/home/user/myfile'

> F("/dir1/dir2")
'../..'

> F("/dir1/dir2")
'.'
\$\endgroup\$
2
  • \$\begingroup\$ Do we not allow node.js functions? \$\endgroup\$
    – Downgoat
    Apr 20, 2017 at 14:06
  • \$\begingroup\$ @Downgoat Javascript lambdas are widely accepted, as a form of answer, so I don't see why Node.js should be handled differently. \$\endgroup\$
    – zeppelin
    Apr 20, 2017 at 14:27
4
\$\begingroup\$

Python 2, 135 144 bytes

i=0
a,c=input()
b,d=a.split('/')*(a!=c),c.split('/')
while b[:i+1]==d[:i+1]:i+=1
print'.'[i:]or min('/'.join(['..']*len(d[i:])+b[i:]),a,key=len)

Try it Online!

Kind of long, but I wanted to do a solution without built-in path functions.

Edit: 9 bytes added to account for test case provided by Nathan Merrill

\$\endgroup\$
3
\$\begingroup\$

Zsh + realpath, 58 bytes

r=`realpath -m --relative-to=$*`
(($#2<$#r))&&r=$2
echo $r

Try it online!

Bash version, 62 bytes

r=`realpath -m --relative-to=$*`
((${#2}<${#r}))&&r=$2
echo $r

Try it online!

\$\endgroup\$
1
  • \$\begingroup\$ Why not post it in two different answers? Every language matters! \$\endgroup\$
    – gaborsch
    Apr 20, 2017 at 12:36
2
\$\begingroup\$

Python 3 – 53 bytes

Using os.path:

import os
lambda x:min(x,os.path.relpath(x),key=len)

Full program (61 bytes):

import os
x=input();print(min(x,os.path.relpath(x),key=len))
\$\endgroup\$
2
  • \$\begingroup\$ Oo, good point(s). Python's in the lead now, yay! \$\endgroup\$
    – matsjoyce
    Apr 20, 2017 at 13:43
  • \$\begingroup\$ @anatolyg Ha, I knew I'd miss at least one test case... 😒 All fixed now. \$\endgroup\$
    – matsjoyce
    Apr 20, 2017 at 16:27
1
\$\begingroup\$

PHP, 204 Bytes

[,$l,$p]=$argv;$z=($d=array_diff_assoc)($y=($w=explode)("/",$p),$x=$w("/",$l));$m=str_pad("",3*count($z)-1,"../");$j=join("/",$r=$d($x,$y));echo$l!=$p?strlen($u=$m&&$j?"$m/$j":$m.$j)<strlen($l)?$u:$l:".";

Testcases

Expanded

[,$l,$p]=$argv;
$z=($d=array_diff_assoc)($y=($w=explode)("/",$p),$x=$w("/",$l));
$m=str_pad("",3*count($z)-1,"../");
$j=join("/",$r=$d($x,$y));
echo$l!=$p
    ?strlen($u=$m&&$j?"$m/$j":$m.$j)<strlen($l)
      ?$u
      :$l
    :".";

if an Output ../../ instead of ../.. is allowed it can be shorten to 175 Bytes

[,$l,$p]=$argv;$z=($d=array_diff_assoc)($y=($w=explode)("/",$p),$x=$w("/",$l));echo$l!=$p?strlen($m=str_pad("",3*count($z),"../").join("/",$r=$d($x,$y)))<strlen($l)?$m:$l:".";
\$\endgroup\$
0
\$\begingroup\$

C# - 66 bytes

Using a .NET builtin and forcing to be a valid path:

(f,t)=>f==t?".":new Uri("/"+t).MakeRelativeUri(new Uri("/"+f))+"";

Where f,t and output are string.

Try it online!

\$\endgroup\$

Your Answer

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

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