алгоритм обмена монет в scala с использованием рекурсии

Я пытаюсь запрограммировать проблему размена монет в Scala, используя рекурсию. Код, который я написал, выглядит следующим образом.

def countChange(money: Int, coins: List[Int]): Int = {
  def ways(change: List[Int], size: Int, capacity: Int): Int = {
    if(capacity == 0) 1
    if((capacity < 0) || (size <= 0)) 0

    //println and readLine to check and control each recursive call.

    println("calling ways(",change, change.length-1, capacity,") + ways(",change,   change.length, capacity - change(change.length - 1),")")
    readLine()
    //

    ways(change, change.length-1, capacity) + ways(change, change.length, capacity - change(change.length - 1))
  }
  ways(coins, coins.length, money)
}

При запуске кода он не завершается и продолжает вызывать первый рекурсивный вызов. Где я ошибаюсь?


person Muavia    schedule 27.09.2012    source источник
comment
Дубликат stackoverflow.com/q/15859432/1305344   -  person Jacek Laskowski    schedule 08.04.2013


Ответы (8)


Простое указание значения не означает, что Scala возвращает его; вам либо нужен явный возврат, либо это должен быть последний указанный элемент. Таким образом:

if (capacity == 0) return 1

or

if (capacity == 0) 1
else if (...)
else { ... }
person Rex Kerr    schedule 27.09.2012
comment
вы уверены, что это работает? у меня все еще та же проблема, о которой говорилось ранее - person Muavia; 28.09.2012
comment
@user1050258 user1050258 - Ну, это была не единственная проблема - вы также используете change.length-1 вместо size-1 в разных местах. Вы не обновляете change в своем решении! (Подсказка: если бы вы обновили change, вы бы избежали этой ошибки и не нуждались бы в аргументе size....) - person Rex Kerr; 28.09.2012

Красиво и просто

def countChange(money: Int, coins: List[Int]): Int = {
  if(money == 0)
    1
  else if(money > 0 && !coins.isEmpty)
    countChange(money - coins.head, coins) + countChange(money, coins.tail)
  else
    0
}
person rkenmi    schedule 01.11.2014
comment
Не могли бы вы дать некоторую интуицию, стоящую за этим решением? - person user2253546; 04.04.2017
comment
Идея состоит в том, чтобы израсходовать наши монеты и вычесть их из текущей суммы денег. В конце концов, сумма денег будет либо 0, либо некоторым отрицательным числом (что означает, что эта комбинация монет не удалась), либо некоторым положительным числом (что означает, что мы все еще можем вычесть больше монет, которые у нас есть в настоящее время). countChange(money - coins.head, coins) исчерпывает все комбинации, вычитая из денег первую монету, а countChange(money, coins.tail) исчерпывает все комбинации, используя только все остальные монеты. Они складываются вместе, так как + является синонимом логического оператора ИЛИ. - person rkenmi; 06.05.2017
comment
Решение приятное. Один вопрос о базовом случае: что, если начальные деньги равны 0. Разве нет 0 способов внести изменения за 0 денег? - person cozyss; 13.09.2017
comment
Представьте, что вам дали несколько монет перед вами, и вас попросили сопоставить их, используя огромный мешок монет. Если перед вами была копейка, есть только 1 способ сопоставить ее; с помощью копейки из вашей сумки. Далее, что, если бы перед вами не было монет? Опять же, есть только один способ представить это; не вынимая никаких монет в вашем мешке. Теперь, наконец, что, если бы у вас были отрицательные деньги? Ну, вы не можете представить это физически. Таким образом, существует 0 способов получения этого результата; Мешок с монетами совершенно не имеет значения в этом сценарии. - person rkenmi; 03.11.2017
comment
@rileyss вы можете представить 0 денег, показав 0 монет. Помните, вопрос заключается в том, сколькими способами мы можем представить деньги при определенном номинале монет. Таким образом, использование нуля монет по-прежнему считается одним из способов. - person Janac Meena; 12.03.2019

Вот моя реализация: я протестировал ее, и она отлично работает

def countChange(money: Int, coins: List[Int]): Int = {

    def count(capacity: Int, changes: List[Int]): Int = {
                if(capacity == 0) 
                  1
                else if(capacity < 0) 
                  0
                else if(changes.isEmpty && capacity>=1 )
                  0
                else
                        count(capacity, changes.tail) + count(capacity - changes.head, changes)
    }

    count(money, coins.sortWith(_.compareTo(_) < 0))
}
person mysteriousscent    schedule 27.06.2013
comment
Хороший! Хотя, думаю, смена названий (деньги -> емкость, монеты -> сдачи) усложняет понимание. - person Filip Spiridonov; 08.11.2013
comment
действительно ли нужна сортировка? - person Verneri Åberg; 11.03.2014
comment
У меня есть вопрос, я новичок в scala: как компилятор читает эту строку: count(capacity, changes.tail) + count(capacity - changes.head, changes)? Я знаю, как работает рекурсия, но у меня проблемы с пониманием того, как компилятор выполняет последнюю строку - person mkazma; 13.05.2014
comment
@moe вы можете посмотреть лекцию первой недели этого курса Мартина Одерски class.coursera. org/progfun-004/лекция/4 - person Anand; 30.06.2014

Просто другое решение

def countChange(amount: Int, coins: List[Int]): Int = coins match {
  case _ if amount == 0 => 1
  case h :: t if amount > 0 => countChange(amount - h, h :: t) + countChange(amount, t)
  case _ => 0
}
person Carlos Caldas    schedule 06.11.2016

Эй, я просто подумал, что было бы лучше видеть не только количество, но и их список, поэтому поместите поверх приведенного выше примера, например:

def moneyChanges(money: Int, coins: List[Int]) : Option[List[Seq[Int]]]= {
  var listOfChange=List[Seq[Int]]()
  def changeMoney(capacity: Int, changes: List[Int], listOfCoins: Option[Seq[Int]]): Int = {
    if (capacity == 0) {
      listOfChange = listOfCoins.get :: listOfChange
      1
    } else if (capacity < 0)
      0
    else if (changes.isEmpty && capacity >= 1)
      0
    else {
      changeMoney(capacity, changes.tail, listOfCoins) +
      changeMoney(capacity - changes.head, changes, 
      Some(changes.head +: listOfCoins.getOrElse(Seq())))
    }
  }

  changeMoney(money, coins.sortWith(_.compareTo(_) < 0), None)
  Some(listOfChange)
}
person Suat KARAKUSOGLU    schedule 14.12.2013

вот подход DP, чтобы уменьшить количество пересчетов в рекурсивном подходе

object DP {
  implicit val possibleCoins = List(1, 5, 10, 25, 100)
  import collection.mutable.Map

  def countChange(amount: Int)(implicit possibleCoins: List[Int]) = {
    val min = Map((1 to amount).map (_->Int.MaxValue): _*)
    min(0) = 0
    for {
      i <- 1 to amount
      coin <- possibleCoins
      if coin <= i && min(i - coin) + 1 < min(i)
    } min(i) = min(i-coin) + 1
    min(amount)
  }

  def main(args: Array[String]) = println(countChange(97))
}

см. DP от новичка к продвинутому для алгоритма

person Leonmax    schedule 29.04.2015

Код ниже аналогичен одному из приведенных выше примеров, за исключением того, что я использую регистр соответствия вместо if else

def countChange(money: Int, coins: List[Int]): Int = {
    def change(m: Int, coinList: List[Int], count: Int): Int =
      m match {
        case _ if m < 0 => count
        case _ if coinList.isEmpty => {
          m match {
            case 0 => count + 1
            case _ => count
          }
        }
        case _ => change(m, coinList.tail, count) + change(m - coinList.head, coinList, count)
      }
    change(money, coins, 0)
  }
person Prasad    schedule 23.06.2016

Вот мой код: он не оптимизирован, но работает во всех тестовых случаях.

Идея состоит в том, чтобы вычесть первую монету из списка из денег, пока она не станет 0. Как только она станет 0, она вернет 1, что означает, что возможно одно решение. Чтобы добавить все решения, полученные из разных рекурсий, я использовал foldLeft.

(итерация списка с использованием foldLeft, поэтому сначала идет 1, затем снова идет рекурсия и итерация для (1, 2) списка)

                [4, (1, 2)].  
         /(1 as cn)       \ (2 as cn)
        [3, (1, 2)].                 [2, (2)]
     /(-1)       \(-2)                \
  [2, (1, 2)].     [1, (2)].          [0, (2)]   
   /.(-1)    \(-2) 
 [1, (1, 2)].   [0, (2)]
  /. (-1)  \(-2)
[0, (1, 2)].  [-1, (2)]


def countChange(money: Int, coins: List[Int]): Int = coins.foldLeft(0)((accum, cn) =>
  (money, cn) match  {
    case (money, _) if money < 0 => 0
    case (0, _) => 1
    case (curr_money, curr_coin) => {
      val (before_curr_coin, after_curr_coin) = coins.span(_ != curr_coin)
      accum + countChange(curr_money - curr_coin, after_curr_coin)
    }
})
person pg20    schedule 09.09.2020