Идиоматическое решение Scala императивного кода

Каковы некоторые идеи для выражения этой функции в "идиоматическом" Scala. Или, точнее, есть ли способ удалить локальные переменные, не жертвуя удобочитаемостью?

def solve(threshold: Int)(f: Int => Int): Int = {
  var sum = 0
  var curr = 0
  while(sum < threshold) {
   sum += f(curr)
   curr += 1
  }
  curr
}

Единственное, что я мог придумать, - это, но, на мой взгляд, он длиннее и менее читабелен.

def solve2(threshold: Int)(f: Int => Int): Int = {
  val resultIterator = Iterator.iterate (0, 0) { case (curr, sum) =>
    (curr + 1, sum + f(curr))
  }
  (resultIterator find (_._2 >= threshold)).get._1
}

5 ответов

Решение

Самый прямой подход - превратить цикл while во вложенную хвостовую рекурсивную функцию.

def solve(threshold: Int)(f: Int => Int): Int = {
    def solveLoop(sum: Int, curr: Int): Int = if (sum < threshold) {
        solveLoop(sum + f(curr), curr + 1)
    } else {
        curr
    }
    solveLoop(0,0)
}

Это стандартный "функциональный" способ зацикливания.

def solve(threshold: Int)(f: Int => Int): Int = {
  Iterator.from(0).map(f).scanLeft(0)(_ + _).indexWhere(threshold <=)
}

На мой взгляд, версия цикла гораздо понятнее.

Вы могли бы

def solve(threshold: Int, i: Int = 0)(f: Int => Int) = {
  if (threshold <= 0) i else solve(threshold - f(i), i+1)(f)
}

но я не уверен, что это на самом деле яснее. Обратите внимание, что на самом деле это больше символов, чем компактная версия цикла while:

def solve(threshold: Int)(f: Int => Int) = {
  var s,i = 0; while (s < threshold) { s += f(i); i += 1 }; i
}

Циклы с изменяемыми переменными не всегда плохи, "идиоматичны" или нет. Просто сохраняйте изменяемое состояние в функции, и все, что кто-либо еще видит, это функция без сохранения состояния, которую можно вызвать.

Кстати, хотя sum разумное имя переменной, curr сомнительно Что случилось с i? Он широко используется в качестве индексной переменной, и в любом случае наличие переменной вообще является неприятностью; Дело в том, что вы берете что-то и увеличиваете это, что бы это ни было, на один шаг каждый раз, а затем возвращаете это. Именно этот поток логики, а не имя, говорит вам (и другим), для чего он нужен.

Вот как я бы сделал это в Haskell:

solve threshold f = snd $ until test step (0, 0)
  where test (sum, i) = sum >= threshold
        step (sum, i) = (sum + f i, succ i)

Это четко обозначает test, stepи начальные значения, так же, как императивная версия. Я не уверен, есть ли у Scala until в libs где-то, но это тривиально, чтобы определить:

def until[A](test: A => Boolean)(f: A => A)(v: A): A = {
  if (test(v)) {
    v
  } else {
    until(test)(f)(f(v))
  }
}

def solve(threshold: Int)(f: Int => Int): Int = {
  def test = (sum: Int, i: Int) => sum >= threshold
  def step = (sum: Int, i: Int) => (sum + f(i), i + 1)
  until(test.tupled)(step.tupled)((0, 0))._2
}

Мне всегда интересно, когда люди говорят о "идиоматической" скале. Потому что, на мой взгляд, у каждого свое восприятие идиоматизма. Если вы ищете функциональное решение, я хотел бы предложить вам взглянуть на "сущность шаблона итератора". На самом деле в scala есть очень хороший пост на эту тему, проверьте это здесь: http://etorreborre.blogspot.com/2011/06/essence-of-iterator-pattern.html

Другие вопросы по тегам