Shorten an absolute path

17

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
.

anatolyg

Posted 2017-04-19T16:24:30.243

Reputation: 10 719

1"You can assume the input current directory has a path separator / at the end". However, in your examples, this is not the case. – Shaggy – 2017-04-19T16:30:59.503

1I like it this way, but some people like it the other way – anatolyg – 2017-04-19T16:33:01.207

Closely related. – AdmBorkBork – 2017-04-19T17:01:52.257

What should happen if absolute and relative path have the same length? – Dennis – 2017-04-19T19:00:49.857

If same length, either output is good. – anatolyg – 2017-04-19T19:12:38.260

You cannot assume that the absolute path refers to an existing file and you can use the system's current directory instead of receiving it as input seem to contradict each other. – Dennis – 2017-04-19T19:32:28.807

Do we have to deal with symlinks? Can we if it's shorter. – Dennis – 2017-04-19T19:40:27.020

@Dennis The absolute path (input 1) may refer to a non-existent file, while the current directory (input 2) is always valid. I guess I'll add the latter to the list of possible assumptions. – anatolyg – 2017-04-19T19:45:25.107

Regarding symlinks - I don't want to decide either way. If there are symlinks, any behavior is acceptable. – anatolyg – 2017-04-19T19:47:02.380

Do the inputs need to be strings? – Tutleman – 2017-04-19T20:26:01.163

Can we make any assumptions on the current directory content ? E.g. to assume it will be empty ? – zeppelin – 2017-04-19T20:32:22.843

@Tutleman I think anything else than strings would be too cheesy. However, if the "get current directory" function returns something other than string (some kind of "filesystem path object"), you can use this format for current directory. – anatolyg – 2017-04-19T20:44:35.540

@zeppelin Of course, no! – anatolyg – 2017-04-19T20:45:56.433

I now want someone to write a program that anytime someone uses an absolute path that could be shorter, echos to the command line, "You know...you could have typed this [short path] instead." Just because computers can always be just a little bit more passive-aggressive. – Draco18s no longer trusts SE – 2017-04-20T15:25:29.110

1This is missing some critical test cases: /home/test /home/user/mydir/myfile /home/test and /a/b /a/b/d/e /a/b – Nathan Merrill – 2017-04-20T16:38:14.687

What do you mean by shorter? – ngenisis – 2017-04-20T23:04:30.150

Answers

7

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!

Dennis

Posted 2017-04-19T16:24:30.243

Reputation: 196 637

Redefining Base.- errors unless explicitly imported, no? – Julian Wolf – 2017-04-20T23:43:31.267

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. – Dennis – 2017-04-21T00:59:28.980

9

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')
);

Arnauld

Posted 2017-04-19T16:24:30.243

Reputation: 111 334

A very nice trick with [a.length] ! May I borrow it to improve my Node.js answer ? – zeppelin – 2017-04-19T21:33:15.610

@zeppelin Sure. Go for it! – Arnauld – 2017-04-19T21:55:09.127

8

Retina, 85 83 82 bytes

1 byte saved thanks to @MartinEnder

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

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

^$
.

Try it online!

user41805

Posted 2017-04-19T16:24:30.243

Reputation: 16 320

5

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")
'.'

zeppelin

Posted 2017-04-19T16:24:30.243

Reputation: 7 884

Do we not allow node.js functions? – Downgoat – 2017-04-20T14:06:05.360

@Downgoat Javascript lambdas are widely accepted, as a form of answer, so I don't see why Node.js should be handled differently. – zeppelin – 2017-04-20T14:27:00.650

4

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

math junkie

Posted 2017-04-19T16:24:30.243

Reputation: 2 490

3

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!

Dennis

Posted 2017-04-19T16:24:30.243

Reputation: 196 637

Why not post it in two different answers? Every language matters! – gaborsch – 2017-04-20T12:36:08.847

2

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))

matsjoyce

Posted 2017-04-19T16:24:30.243

Reputation: 1 319

Oo, good point(s). Python's in the lead now, yay! – matsjoyce – 2017-04-20T13:43:27.357

@anatolyg Ha, I knew I'd miss at least one test case... All fixed now. – matsjoyce – 2017-04-20T16:27:01.747

1

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:".";

Jörg Hülsermann

Posted 2017-04-19T16:24:30.243

Reputation: 13 026

0

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!

aloisdg moving to codidact.com

Posted 2017-04-19T16:24:30.243

Reputation: 1 767