#scala #pattern-matching
#скала #сопоставление с образцом
Вопрос:
Это пример кода scala-
def foo(n : Int, ls : List[Int]) : Int =
ls match {
case Nil => n
case hd :: tl => hd*foo(n-1,tl)
}
Если я пройду, foo(7,List(1,2,-2,2))
это даст мне -24, Но я не понимаю, как это работает, кто-нибудь может помочь мне понять, как здесь работает рекурсия?
Комментарии:
1. Это rec, но не tailrec для меня
Ответ №1:
Поскольку я не совсем уверен, о чем вы просите, это может быть слишком сложно.
В match
:
case Nil
будет соответствовать (и только соответствовать) пустому спискуcase hd :: tl
разрушит список (точный механизм того, как / почему это работает, выходит за рамки этого ответа), при этом значениеhd
является первым элементом списка, а значениеtl
является списком, содержащим каждый элемент после первого
Итак, продолжая работу с моделью замещения оценки, мы в конечном итоге получаем:
foo(7, List(1, 2, -2, 2))
: список (ls
) непустой, поэтому второе предложение match совпадает, и результат1 * foo(6, List(2, -2, 2))
: список непустой, поэтому второе предложение match совпадает, и результат (после упрощения, поскольку умножение является ассоциативным)1 * 2 * foo(5, List(-2, 2))
: список непустой, поэтому второе предложение match совпадает, и результат1 * 2 * -2 * foo(4, List(2))
: список непустой, поэтому второе предложение match совпадает, и результат1 * 2 * -2 * 2 * foo(3, Nil)
: список пуст, поэтому первое предложение match совпадает, и результат1 * 2 * -2 * 2 * 3
: который при умножении равен-24
Для некоторых людей логику этой функции было бы лучше выразить как
def foo(n: Int, ls: List[Int]): Int =
if (ls.isEmpty) n
else ls.head * foo(n - 1, ls.tail)
Это также может быть выражено после небольшой алгебраической манипуляции как
(n - ls.length) * ls.product
Хотя для List
это будет медленнее, чем рекурсивные реализации (поскольку .length
и .product
каждая из них будет полностью пересекать список).
Комментарии:
1. Существуют также реализации, которые являются хвостово-рекурсивными (т. Е. Компилятор превратит их в цикл) и которые используют
foldLeft
: оба требуют вспомогательного значения.
Ответ №2:
Я добавил println к вашему первоначальному ответу, который печатает трассировку стека выполнения точно так, как Леви объяснил в своем ответе. Этот трюк должен помочь с другими рекурсивными примерами понять поток выполнения:
def printMul(xs: List[Int]) = if (xs.nonEmpty) xs.mkString(" * ") " * " else ""
def foo(n: Int, ls: List[Int], prev: List[Int] = Nil): Int =
ls match {
case Nil =>
println(s"${printMul(prev)}$n")
n
case hd :: tl =>
println(s"${printMul(prev)}$hd * foo(${n - 1}, $tl)")
hd * foo(n - 1, tl, prev : hd)
}
// ====== Output ======
/*
1 * foo(6, List(2, -2, 2))
1 * 2 * foo(5, List(-2, 2))
1 * 2 * -2 * foo(4, List(2))
1 * 2 * -2 * 2 * foo(3, List())
1 * 2 * -2 * 2 * 3
*/
Ответ №3:
foo
завершается, когда список, переданный в качестве параметра, ls
является Nil
(пустым).
При каждом foo
вызове ls
его head ( hd
) и tail ( tl
) извлекаются с помощью сопоставления с шаблоном. Заголовок умножается на следующий foo
вызов, который принимает только конец списка (а также вычитает n
значение на 1.
Когда foo(7,List(1,2,-2,2))
вычисляется, следующим шагом является 1 * foo(6, List(2, -2, 2))
и так далее…