Немного более лаконично
Прежде всего, вот как я бы реализовал этот метод на макушке:
def diff(s1: String, s2: String): List[String] =
(s1, s2).zipped.collect {
case (x, y) if x != y => s"$x != $y"
}.toList ++
s1.drop(s2.length).map(x => s"$x is undefined") ++
s2.drop(s1.length).map(y => s"$y is missing")
Это примерно вдвое меньше символов, чем ваш оригинал.реализация, и, на мой взгляд, это по крайней мере так же читабельно.Можно утверждать, что трюк drop
слишком умный, и, возможно, вы правы, но я думаю, что он хорошо читается, как только вы его получите.
Немного эффективнее
Подобный метод самодостаточен и прост в тестировании, и, если есть вероятность, что он будет использоваться в ситуациях, когда важна производительность, стоит рассмотреть вопрос об обязательной реализации.Вот быстрый набросок того, как я это сделаю:
def diffFast(s1: String, s2: String): IndexedSeq[String] = {
val builder = Vector.newBuilder[String]
def diff(short: String, long: String, status: String) = {
builder.sizeHint(long.length)
var i = 0
while (i < short.length) {
val x = s1.charAt(i)
val y = s2.charAt(i)
if (x != y) builder += s"$x != $y"
i += 1
}
while (i < long.length) {
val x = long.charAt(i)
builder += s"$x is $status"
i += 1
}
}
if (s1.length <= s2.length) diff(s1, s2, "missing")
else diff(s2, s1, "undefined")
builder.result
}
Вы можете сделать это на немного быстрее, указав размер и т. Д. [Обновление: я пошел впереди добавил это], но эта версия, вероятно, довольно близка к оптимальной, и я также нахожу ее вполне читабельной - на мой взгляд, она не так ясна, как моя короткая реализация выше или ваша оригинальная, но я нахожу ее намного лучше, чем рекурсивная реализацияв другом ответе.
Обратите внимание, что это возвращает IndexedSeq
, а не List
.В этом он следует вашей первоначальной реализации, а не подписи в вашем первом предложении.Если вам нужен List
, вы можете просто изменить Vector.newBuilder
на List.newBuilder
, но в большинстве случаев векторная версия будет немного быстрее.
Тесты
Мы можем предположитьо производительности весь день, но так просто запустить несколько быстрых микробенчмарков JMH, что мы могли бы сделать это вместо этого (полный исходный код здесь ).В качестве простого примера я возьму следующую пару строк:
val example1: String = "a" * 1000
val example2: String = "ab" * 100
Мы можем измерить пропускную способность для этого ввода для вашей исходной версии (как текущей, так и возвращающей List
), моей краткой версиимоя быстрая версия (возвращающая и IndexedSeq
и List
), и рекурсивная версия Тима:
Benchmark Mode Cnt Score Error Units
DiffBench.checkConcise thrpt 20 47412.127 ± 550.693 ops/s
DiffBench.checkFast thrpt 20 108661.093 ± 371.827 ops/s
DiffBench.checkFastList thrpt 20 91745.269 ± 157.128 ops/s
DiffBench.checkOrig thrpt 20 8129.848 ± 59.989 ops/s
DiffBench.checkOrigList thrpt 20 7916.637 ± 15.736 ops/s
DiffBench.checkRec thrpt 20 62409.682 ± 580.529 ops/s
Короче говоря: ваша исходная реализация действительно очень плохая, что касается производительности (яЯ догадываюсь больше из-за всех распределений, чем из нескольких обходов), моя краткая реализация конкурирует с (возможно, менее читабельной) рекурсивной и получает примерно в шесть раз большую пропускную способность, чем оригинал, а императивная реализация близка к вдвое быстреелюбой другой.