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\$
  • 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 '17 at 16:30
  • 1
    \$\begingroup\$ I like it this way, but some people like it the other way \$\endgroup\$ – anatolyg Apr 19 '17 at 16:33
  • \$\begingroup\$ Closely related. \$\endgroup\$ – AdmBorkBork Apr 19 '17 at 17:01
  • \$\begingroup\$ What should happen if absolute and relative path have the same length? \$\endgroup\$ – Dennis Apr 19 '17 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\$ – Nathan Merrill Apr 20 '17 at 16:38
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\$
  • \$\begingroup\$ Redefining Base.- errors unless explicitly imported, no? \$\endgroup\$ – Julian Wolf Apr 20 '17 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 '17 at 0:59
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\$
  • \$\begingroup\$ A very nice trick with [a.length] ! May I borrow it to improve my Node.js answer ? \$\endgroup\$ – zeppelin Apr 19 '17 at 21:33
  • \$\begingroup\$ @zeppelin Sure. Go for it! \$\endgroup\$ – Arnauld Apr 19 '17 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\$
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\$
  • \$\begingroup\$ Do we not allow node.js functions? \$\endgroup\$ – Downgoat Apr 20 '17 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 '17 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\$
  • \$\begingroup\$ Why not post it in two different answers? Every language matters! \$\endgroup\$ – gaborsch Apr 20 '17 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\$
  • \$\begingroup\$ Oo, good point(s). Python's in the lead now, yay! \$\endgroup\$ – matsjoyce Apr 20 '17 at 13:43
  • \$\begingroup\$ @anatolyg Ha, I knew I'd miss at least one test case... 😒 All fixed now. \$\endgroup\$ – matsjoyce Apr 20 '17 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, privacy policy and cookie policy

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