# Print the relative path

Description

Given a source path and a destination path, output the relative path to the destination with respect to the source.

Rules

1. The input can come from stdin or as arguments to the program/function.

2. Both Windows and Unix style paths must be supported.

3. The output path can use / and/or \ for the path separator (your choice and combination of both is OK).

4. You can assume a relative path is possible.

5. The use of external programs, built-in or library functions made for computing relative paths is prohibited (e.g. Python's os.path.relpath)

6. This is

Edit: New rule from comments.

7. The relative path must be the shortest relative path possible.

8. Assume the destination path is different from the source path.

Example 1

# In
/usr/share/geany/colorschemes
/usr/share/vim/vim73/ftplugin

# Out
../../vim/vim73/ftplugin


Example 2

# In
C:\Windows\System32\drivers
C:\Windows\System32\WindowsPowerShell\v1.0

# Out
..\WindowsPowerShell\v1.0

• Regarding rule #3 - is a mixture ok? E.g. ../../vim\vim73\ftplugin. – Duncan Jones Jun 26 '14 at 7:55
• Do we have to return the shortest relative path or is it okay to yield any path? – Howard Jun 26 '14 at 10:54
• @Duncan Yes, a mix is ok. – Rynant Jun 26 '14 at 13:06
• @Howard, it must be the shortest relative path. – Rynant Jun 26 '14 at 13:07
• shouldn't the first example be ../vim/vim73/ftplugin? – Martijn Jun 26 '14 at 13:39

# CJam, 46 bytes

ll]{'\/'/f/:~}/W{)__3$=4$@==}g@,1$-"../"*o>'/*  Try it online. ### Examples $ echo '/usr/share/geany/colorschemes
> /usr/share/vim/vim73/ftplugin' | cjam path.cjam; echo
../../vim/vim73/ftplugin
$echo 'C:\Windows\System32\drivers > C:\Windows\System32\WindowsPowerShell\v1.0' | cjam path.cjam; echo ../WindowsPowerShell/v1.0  ### How it works ll] " Read two lines from STDIN and wrap them in an array. "; { " For each line: "; '\/ " Split by “\”. "; '/f/ " Split each chunk by “/”. "; :~ " Flatten the array of chunks. "; }/ " "; W " Push -1 (accumulator). "; { " "; )__ " Increment and duplicate twice. "; 3$=       " Extract the corresponding chunk from the first line.                       ";
4$@= " Extract the corresponding chunk from the second line. "; = " If the are equal, "; }g " repeat the loop. "; @, " Rotate the array of chunks of the first line on top and get its length. "; 1$-         " Subtract the value of the accumulator.                                     ";
"../"*o     " Print the string “../” repeated that many times.                           ";
>           " Remove all chunks with index less than the accumulator of the second line. ";
'/*         " Join the chunks with “/”.                                                  ";

• It has a bug. Try /aa/x with /ab/y. – jimmy23013 Jul 3 '14 at 15:56
• @user23013: Fixed. – Dennis Jul 3 '14 at 20:07

# Bash + coreutils, 116

Here's a shell script to get the ball rolling. Pretty sure there'll be shorter answers:

n=cmp <(echo $1) <(echo$2)|grep -Po "\d+(?=,)"
printf -vs %grep -o /<<<${1:n-1}|wc -ls echo${s// /../}${2:n-1}  Output: $ ./rel.sh /usr/share/geany/colorschemes /usr/share/vim/vim73/ftplugin
../vim/vim73/ftplugin
$./rel.sh /usr/share/geany/colorschemes/ /usr/share/vim/vim73/ftplugin/ ../../vim/vim73/ftplugin/$ ./rel.sh /usr/share/vim/vim73/ftplugin /usr/share/geany/colorschemes
../../geany/colorschemes
$ Note there is no way for the script to tell if the string ftplugin is a file or a directory. You may explicitly provide a directory by appending it with an / as in the example above. Won't handle paths containing whitespace or other funny characters. Not sure if that is a requirement or not. Just a few extra quotes would be needed. # Javascript (E6) 104 Edit Added alert for output R=(s,d)=>alert(s.split(x=/\/|\\/).map(a=>a==d[0]?d.shift()&&'':'../',d=d.split(x)).join('')+d.join('/'))  Ungolfed R (s,d) => // a single espression is returned, no {} or () needed s.split(x=/\/|\\/) // split string at / or \, save regexp in X for later .map( // create a new array from s a => a == d[0] // check if current of s and d equals ? d.shift() && '' // map to '' and cut 1 element of d : '../', // else map to '../' d=d.split(x)) // second param of map is useless, so split d here .join('')+d.join('/') // join map and concat to rest of d adding separators  Test R('C:\\Windows\\System32\\drivers','C:\\Windows\\System32\\WindowsPowerShell\\v1.0')  ../WindowsPowerShell/v1.0 R('/usr/share/geany/colorschemes','/usr/share/vim/vim73/ftplugin')  ../../vim/vim73/ftplugin ## Ruby >= 1.9, 89 94 characters $;=/\\|\//
a,b=$*.map &:split puts"../"*(a.size-r=a.index{a[$.+=1]!=b[$.]}+1)+b[r..-1]*?/  Input via command line arguments. Works for both UNIX- and Windows-style paths, including paths with repeated folder names: $ ruby relpath.rb /usr/share/geany/colorschemes /usr/share/vim/vim73/ftplugin
../../vim/vim73/ftplugin
$ruby relpath.rb 'C:\Windows\System32\drivers' 'C:\Windows\System32\WindowsPowerShell\v1.0' ../WindowsPowerShell/v1.0$ ruby relpath.rb /foo/bar/foo/bar /foo/qux/foo/bar
../../../qux/foo/bar


# J - 63 char

A function taking the old path on the left and the new path on the right.

}.@;@(c=.c&}.(,~(<'/..')"0)@.(~:&{.))&('/'<;.1@,'\/'&charsub)~


This solution comes in three parts, looking like post@loop&pre~. Explained by explosion:

post @ loop & pre ~   NB. the full golf
~   NB. swap the arguments: new on left, old on right
& pre     NB. apply pre to each argument
loop           NB. run the recursive loop on both
post @                NB. apply post to the final result

'/'<;.1@,'\/'&charsub  NB. pre
'\/'&charsub  NB. replace every \ char with /
'/'     ,              NB. prepend a / char
<;.1@               NB. split string on the first char (/)

c=.c&}.(,~(<'/..')"0)@.(~:&{.)  NB. loop
@.(~:&{.)  NB. if the top folders match:
&}.                          NB.   chop off the top folders
c                             NB.   recurse
                         NB. else:
(<'/..')"0            NB.   change remaining old folders to /..
,~                      NB.   append to front of remaining new folders
c=.                              NB. call this loop c to recurse later

}.@;  NB. post
;  NB. turn the list of folders into a string
}.@   NB. chop off the / in the front


Note that we add a leading / to each path before splitting, so that we handle Windows-style paths by making C: into a "folder". This results in an empty folder at the start of Unix-style paths, but that always gets removed by the loop.

See it in action:

   NB. you can use it without a name if you want, we will for brevity
relpath =. }.@;@(c=.c&}.(,~(<'/..')"0)@.(~:&{.))&('/'<;.1@,'\/'&charsub)~
'/usr/share/geany/colorschemes' relpath '/usr/share/vim/vim73/ftplugin'
../../vim/vim73/ftplugin
'C:\Windows\System32\drivers' relpath 'C:\Windows\System32\WindowsPowerShell\v1.0'
../WindowsPowerShell/v1.0


You can also try it yourself at tryj.tk.

# Bash, 69 66

I didn't post this one because I thought someone must be able to do it much better. But apparently it is not that easy.

sed -r 'N;s/(.*[/\])(.*)\n\1/\2\n/'|sed '1s/[^/\]*/../g;N;s!\n!/!'


N makes sed match two lines together. The first expression removes the common prefix ending with / or \. The second expression replaces directory names with .. in the first line. Finally it concatenates the two lines with the separator.

Thanks to Hasturkun for 3 characters.

• Looks interesting! Can you add an explanation? – Digital Trauma Jun 27 '14 at 12:41
• @DigitalTrauma Added. But basically they are just regular expressions. – jimmy23013 Jun 27 '14 at 13:23
• Thanks! I'm going to play with this next time I'm at a terminal – Digital Trauma Jun 27 '14 at 13:27
• You don't really need to run sed twice, you can do this with a single script. – Hasturkun Jul 2 '14 at 12:36
• @Hasturkun But I didn't find a way to have it work with N. Maybe you can edit this answer if you know how. – jimmy23013 Jul 2 '14 at 14:50

# C, 119 106

void p(char*s,char* d){while(*s==*d){s++;d++;}s--;while(*s){if(*s==47||*s==92)printf("../");s++;}puts(d);}

• p(char*s,char*d){for(;*s;)*s++-*d?*s-47||printf("../"):d++;puts(d);} 68 chars w/o backslash – bebe Jun 27 '14 at 11:59
• Thanks! But rule 2 says both must be supported. It is in the output where I can pick one or the other(rule 3). – kwokkie Jun 27 '14 at 12:43

## Python 3, 120

a,b=(i.split('\\/'['/'in i])for i in map(input,'  '))
while[]<a[:1]==b[:1]:del a[0],b[0]
print('../'*len(a)+'/'.join(b))


Example:

$python3 path.py /usr/share/geany/colorschemes /usr/share/vim/vim73/ftplugin ../../vim/vim73/ftplugin  • Might there be a shorter way to do line 1 with exec and string operations? – xnor Jun 27 '14 at 13:29 • @xnor Maybe, but I can't see it. – grc Jun 27 '14 at 14:43 • Might map(input,' ') work for (input(),input())? (I can't test it myself) – xnor Jun 27 '14 at 14:50 • @xnor Yeah that works thanks! – grc Jun 27 '14 at 15:00 # Ruby - 89 r=/\/|\\/ s = ARGV[0].split r d = ARGV[1].split r puts ("../"*(s-d).size)+((d-s).join"/")  Usage: ruby relative.rb working/directory destination/directory  • This fails for arguments like /foo/bar/foo/bar and /foo/qux/foo/bar. – Ventero Jun 26 '14 at 9:27 • And fails for windows style paths – edc65 Jun 26 '14 at 11:01 • @edc65 The rules don't say it's necessary to support both path formats, you can do either one. – nderscore Jun 26 '14 at 18:49 • @nderscore Rule 2 Both Windows and Unix style paths must be supported. – edc65 Jun 26 '14 at 18:51 • @Jwosty: Well, that's the beauty, isn't it? Coming up with a solution that's both short and correct. I have in the past had cases where I had to revise the answer completely because of an overlooked edge case. Now, in this case I do put the blame partly on the task as well, because I believe a solid set of test cases should accompany every task, but well. – Joey Jun 28 '14 at 9:54 # JavaScript - 155 function p(s,d){s=s.split(/\\|\//g);d=d.split(/\\|\//g);o=[];for(i=0;s[i]==d[i];i++);for(j=s.length-i;j--;)o[j]="..";return o.concat(d.slice(i)).join("/")}  Parses either path format but outputs with / separator. console.log(p("/usr/share/geany/colorschemes","/usr/share/vim/vim73/ftplugin")); ../../vim/vim73/ftplugin console.log(p("/usr/share/geany/colorschemes/test/this","/usr/share/vim/vim73/ftplugin/this/is/a/test")); ../../../../vim/vim73/ftplugin/this/is/a/test console.log(p("c:\\windows\\system32\\drivers\\etc\\host","c:\\windows\\system\\drivers\\etc\host")); ../../../../system/drivers/etchost  # PHP, 158 151 function r($a,$b){$a=explode('/',$a);$b=explode('/',$b);foreach($a as $k=>$v){if($v==$b[$k])$b[$k]='..';else break;}unset($b[0]);echo implode('/',$b);}  Ungolfed: function r($a,$b){$a=explode('/',$a);$b=explode('/',$b); foreach($a as $k=>$v){
if($v==$b[$k])$b[$k]='..'; else break; } unset($b[0]);
echo implode('/',$b); } // these lines are not included in count: r('/test/test2/abc','/test/test3/abcd'); // ../test3/abcd r('/test/test2/abc','/test/test2/abcd'); // ../../abcd  • Your answer isn't correct. Try making this dirs and cd form one to another :) – core1024 Jul 3 '14 at 16:15 ## Groovy - 144 chars One solution: x=args[0][1]!=':'?'/':'\\' f={args[it].tokenize x} s=f 0 d=f 1 n=0 while(s[n]==d[n++]); u="..$x"*(s.size-(--n))
println "$u${d.drop(n).join x}"


example output:

bash$groovy P.groovy C:\\Windows\\System32\\drivers C:\\Windows\\System32\\WindowsPowerShell\\v1.0 ..\WindowsPowerShell\v1.0 bash$ groovy P.groovy /usr/share/geany/colorschemes /usr/share/vim/vim73/ftplugin
../../vim/vim73/ftplugin

bash$groovy P.groovy /foo/bar/foo/bar /foo/qux/foo/bar ../../../qux/foo/bar  ungolfed: // fs = file seperator, / or \ fs = args[0][1]!=':'?'/':'\\' s = args[0].tokenize fs d = args[1].tokenize fs // n = # of matching dirs from root + 1 n = 0 while (s[n] == d[n++]) ; // up = the up-prefix. e.g. "../../..", for instance n-- up = "..${fs}" * (s.size-n)

println "$up${d.drop(n).join fs}"
`