Scala - operowanie na pierwszych n elementach listy

0

Hej
Zacząłem się uczyć wczoraj scali i napisałem funkcje do obliczania wartości wyrażenia zapisanego w odwróconej notacji polskiej:


  def main(args: Array[String]): Unit = {
        println(eval(List("1","2","*","3","+")))
}
  def eval(t: List[String]): Int = {
    def eval(t: List[String], valueStack: List[Int]): Int = t match {
      case Nil => valueStack.head
      case "+" :: _ => eval(t.tail, (valueStack.head + valueStack.drop(1).head) :: valueStack.drop(2));
      case "-" :: _ => eval(t.tail, (valueStack.head - valueStack.drop(1).head) :: valueStack.drop(2));
      case "*" :: _ => eval(t.tail, (valueStack.head * valueStack.drop(1).head) :: valueStack.drop(2));
      case "*" :: _ => eval(t.tail, (valueStack.head / valueStack.drop(1).head) :: valueStack.drop(2));
      case n :: _ => eval(t.tail, n.toInt :: valueStack)
    }
    eval(t, Nil)
  }

Pytanie jak to zrobić 'ładniej'. Chodzi mi o ten kawałek gdzie dodaje dwa pierwsze elementy do siebie i robię z nich nową głowe listy. Czy też może jakąś sprytną strukturą klas to załatwić?

EDIT

Wymyśliłem takie rozwiązanie. Lepsze?

  def eval(t: List[String]): Int = {
    def eval(t: List[String], valueStack: List[Int]): Int = t match {
      case Nil => valueStack.head
      case "+" :: _ => eval(t.tail, operateOnFirstDwo[Int](valueStack, _ + _))
      case "-" :: _ => eval(t.tail, operateOnFirstDwo[Int](valueStack, _ - _))
      case "*" :: _ => eval(t.tail, operateOnFirstDwo[Int](valueStack, _ * _))
      case "/" :: _ => eval(t.tail, operateOnFirstDwo[Int](valueStack, _ / _))
      case n :: _ => eval(t.tail, n.toInt :: valueStack)
    }
    eval(t, Nil)
  }

  def operateOnFirstDwo[T](list:List[T], f: (T,T)=>T):List[T] = {
    f(list.head, list.drop(1).head) :: list.drop(2)
  }

1

zamiast

case "+" :: _ => eval(t.tail, operateOnFirstDwo[Int](valueStack, _ + _))

możesz napisać

 case "+" :: tail => eval(tail, operateOnFirstDwo[Int](valueStack, _ + _))

taka drobnostka

0

Działa Ci to? Przetestowałeś?

1

"Wydaje się działać" A to dobre!:)
Ok, to co Masz dla:

'7 8 + 3 2 + /'
'1 2 + 3 4 + *'
"1 2 + 3 * 4 5 - 6 7 + * -"

Powinno być, odpowiednio: 3, 21, 22.

0

@lion137:
dzięki, masz rację. Powinno być

  def operateOnFirstDwo[T](list:List[T], f: (T,T)=>T):List[T] = {
    f(list.drop(1).head,list.head) :: list.drop(2)
  }
0

Po co robić list.drop(x).head skoro można zrobić wprost list(x)?

1

Nieco bardziej odpicowana wersja:

import scala.annotation.tailrec
import scala.util.Try

object Evaluator {
  def main(args: Array[String]): Unit = {
    println(eval(List("1", "2", "*", "3", "+")))
  }

  private val ops = Map[String, (Int, Int) => Int](
    "+" -> (_ + _),
    "-" -> (_ - _),
    "*" -> (_ * _),
    "/" -> (_ / _),
  )

  def eval(tokens: List[String]): Int = {
    @tailrec
    def go(tokens: List[String], values: List[Int]): Int = {
      (tokens, values) match {
        case (Nil, List(result)) =>
          result
        case (op :: tokensTail, arg1 :: arg2 :: valuesTail) if ops.contains(op) =>
          val result = ops(op)(arg1, arg2)
          go(tokensTail, result :: valuesTail)
        case (n :: tokensTail, _) if Try(n.toInt).isSuccess =>
          go(tokensTail, n.toInt :: values)
        case _ =>
          sys.error(s"invalid state. tokens = $tokens, values = $values")
      }
    }
    go(tokens, Nil)
  }
}

Powinna niezawodnie raportować błędy zamiast się wykrzaczać. Zaraportuje błąd także w przypadku, gdy na końcu przetwarzania na stosie wartości będzie więcej niż jedna wartość (wynik powinien być jeden).

0

@Wibowit: Wydaje mi się, że w Scali już nie dawno nie trzeba dekoratora @tailrec

0

Generalnie chyba nigdy nie trzeba było, ale jeśli ta adnotacja jest to kompilacja wywala się, gdy kompilator nie może zastosować optymalizacji.

A method annotation which verifies that the method will be compiled with tail call optimization.

If it is present, the compiler will issue an error if the method cannot be optimized into a loop.

1 użytkowników online, w tym zalogowanych: 0, gości: 1