Is there some nice magic to get from a list of fil...
# getting-started
v
Is there some nice magic to get from a list of files the lowest common denominator?
y
Copy code
files.map { it.canonicalPath }.reduce(String::commonPrefixWith)
Should give you the directory or file that contains them all (I don't know how much I like this though tbh) You can easily write a variant of this by making a
File.commonDenominatorWith(File)
function
v
Yeah, that's similar to what I have so far, thx. 🙂
Copy code
files
    .map { it.canonicalFile.invariantSeparatorsPath }
    .reduce(CharSequence::commonPrefixWith)
    .let(::File)
e
stringified names could end up chopping in the middle of something, e.g.
"/home/user1/foo"
,
"/home/user2/bar"
have
"/home/user"
as the common prefix
🤦🏼 1
v
Ah, good point, thanks. Then
Copy code
files
    .map { it.canonicalFile.invariantSeparatorsPath }
    .reduce(CharSequence::commonPrefixWith)
    .substringBeforeLast('/')
    .let(::File)
If the common path was a directory, the slash will be the last character, otherwise the partly directory will be chopped off together with the last slash.
e
systems with multiple roots (e.g.
C:\
,
D:\
,
\\?\
on Windows) may end up with no common denominator. but if you can ignore that, that seems ok
v
Shouldn't happen in my case, yeah, forgot to mention that premise
e
v
Yeah,
toComponents().segments
would give the segments, had that first as I was playing in the debugger evaluator and didn't see it is internal, but then I still missed how to get the common prefix for two lists.
e
not built in but you could define
Copy code
fun <T> Iterable<T>.commonPrefix(other: Iterable<T>): List<T> {
    val otherIterator = other.iterator()
    return this.takeWhile { otherIterator.hasNext() && it == otherIterator.next() }
}
1
y
Untested code follows:
Copy code
fun <T> commonPrefixOfLists(list1: List<T>, list2: List<T>): List<T> = buildList {
  list1.zip(list2).forEach { (e1, e2) ->
    if (e1 == e2) add(e1)
    else return@buildList
  }
}
Can definitely be improved by defining some sort of
forEachZipped
function. I'm quite fond of the early-return here to short circuit the prefix-finding
e
zip
is kinda overkill since it builds its own list… there's been more than once when I wished it didn't, and was just an iterator, but oh well
Copy code
list1.zip(list2) { e1, e2 ->
would work the same and avoid the intermediate pairs, but still ends up with an extra list
v
The early-out is not just nice, it is necessary, or you would get for
/a/b/c
and
/a/d/c
as result
/a/c
e
I mean that
Copy code
fun <T> commonPrefixOfLists(list1: List<T>, list2: List<T>): List<T> = buildList {
  list1.zip(list2) { (e1, e2) ->
    if (e1 == e2) add(e1)
    else return@buildList
  }
}
would work the same as Youssef's code, without `Pair`s. but it still has an intermediate
List
that isn't needed if you work with the `Iterator`s directly
v
Yeah, sure, that was in response to the "Fond of the early out"
e
ah I see. that's true but I don't like Kotlin's
zip
very much in general :p
v
Yes, got that 🙂
y
This doesn't even create a list!
Copy code
fun <T> commonPrefixOfLists(list1: List<T>, list2: List<T>): List<T> {
  var i = 0
  while(i < list1.length) {
    if (list1[i] != list2[i]) {
      i++
      break
    }
    i++
  }
  return list1.sublist(0, i)
}
e
yeah. only works well on
List&RandomAccess
though :)
❤️ 1
(which tbf is basically all `List`s, only weird ones like
LinkedList
aren't
RandomAccess
, and Kotlin doesn't expose that)