| Bytes | Lang | Time | Link |
|---|---|---|---|
| 053 | Perl 5 pl | 241121T211222Z | Xcali |
| 088 | JavaScript Node.js | 241121T025112Z | l4m2 |
| 046 | CJam | 140702T163739Z | Dennis |
| 066 | Bash | 140626T152459Z | jimmy230 |
| 089 | Ruby | 140626T051556Z | Jwosty |
| 144 | Groovy | 140628T012954Z | Michael |
| 063 | J | 140627T182121Z | algorith |
| 120 | Python 3 | 140626T072603Z | grc |
| 106 | C | 140627T110126Z | kwokkie |
| 8994 | Ruby >= 1.9 | 140626T090200Z | Ventero |
| 104 | Javascript | 140626T100401Z | edc65 |
| 151 | PHP | 140626T091219Z | Martijn |
| 155 | JavaScript | 140626T062152Z | Matt |
| 116 | Bash + coreutils | 140626T005441Z | Digital |
JavaScript (Node.js), 88 bytes
s=>d=>s.split(x=/\/|\\/).map(a=>a==d[0]?d.shift():d.unshift`..`,d=d.split(x))&&d.join`/`
from edc65's and fixed some problem
JavaScript (Node.js), 98 bytes
f=(x,y,[[X,a,b],[Y,c,d]]=[x,y].map(e=>/(.*?)[\/\\](.*)|/.exec(e)))=>x?a==c?f(b,d):'../'+f(b,y):[y]
Extra / at last when output is ../../
CJam, 46 bytes
ll]{'\/'/f/:~}/W{)__3$=4$@==}g@,1$-"../"*o>'/*
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 “/”. ";
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.
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
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}"
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.
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
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);}
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
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
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
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
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 -l`s
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.