【问题标题】:Rewriting imperative for loop to declarative style in Scala在 Scala 中将循环的命令式重写为声明式样式
【发布时间】:2017-08-05 17:24:16
【问题描述】:

如何使用内置的高阶函数或尾递归将以下循环(模式)重写为 Scala?

这是迭代模式的示例,您可以在其中对两个列表元素进行计算(例如比较),但前提是第二个元素在原始输入中的第一个元素之后。注意这里使用了+1步,但一般情况下可以是+n。

public List<U> mapNext(List<T> list) {
    List<U> results = new ArrayList();

    for (i = 0; i < list.size - 1; i++) {
        for (j = i + 1; j < list.size; j++) {
            results.add(doSomething(list[i], list[j]))
        }
    }

    return results;
}

到目前为止,我已经在 Scala 中提出了这个:

def mapNext[T, U](list: List[T])(f: (T, T) => U): List[U] = {
  @scala.annotation.tailrec
  def loop(ix: List[T], jx: List[T], res: List[U]): List[U] = (ix, jx) match {
    case (_ :: _ :: is, Nil) => loop(ix, ix.tail, res)
    case (i :: _ :: is, j :: Nil) => loop(ix.tail, Nil, f(i, j) :: res)
    case (i :: _ :: is, j :: js) => loop(ix, js, f(i, j) :: res)
    case _ => res
  }

  loop(list, Nil, Nil).reverse
}

编辑: 对于所有贡献者,我只希望我能接受每个答案作为解决方案:)

【问题讨论】:

  • 然后呢?有什么问题?
  • 它看起来有点笨拙,有点不对劲,我想知道是否有更好、更简洁的方法。
  • 使用提供的.map.foldLeft,改进模式匹配(但这取决于你更好理解的情况)

标签: scala loops functional-programming tail-recursion declarative


【解决方案1】:

复出尝试:

在删除我第一次尝试给出答案后,我对其进行了更多思考,并提出了另一个至少更短的解决方案。

def mapNext[T, U](list: List[T])(f: (T, T) => U): List[U] = {
  @tailrec
  def loop(in: List[T], out: List[U]): List[U] = in match {
    case Nil          => out
    case head :: tail => loop(tail, out ::: tail.map { f(head, _) } )
  }

  loop(list, Nil)
}

我还想推荐丰富我的库模式,以将 mapNext 函数添加到 List api(或对任何其他集合进行一些调整)。

object collection {
  object Implicits {
    implicit class RichList[A](private val underlying: List[A]) extends AnyVal {
      def mapNext[U](f: (A, A) => U): List[U] = {
        @tailrec
        def loop(in: List[A], out: List[U]): List[U] = in match {
          case Nil          => out
          case head :: tail => loop(tail, out ::: tail.map { f(head, _) } )
        }

        loop(underlying, Nil)
      }
    }
  }
}

然后你可以使用如下函数:

list.mapNext(doSomething)

同样,有一个缺点,因为连接列表相对昂贵。 但是,用于理解的变量赋值也可能非常低效(正如 dotty Scala Wart: Convoluted de-sugaring of for-comprehensions 的改进任务所建议的那样)。

更新

既然我已经进入这个,我简直不能放手:(

关于'注意这里使用了+1步,但一般情况下可以是+n。'

我用一些参数扩展了我的建议以涵盖更多情况:

object collection {
  object Implicits {
    implicit class RichList[A](private val underlying: List[A]) extends AnyVal {
      def mapNext[U](f: (A, A) => U): List[U] = {
        @tailrec
        def loop(in: List[A], out: List[U]): List[U] = in match {
          case Nil          => out
          case head :: tail => loop(tail, out ::: tail.map { f(head, _) } )
        }

        loop(underlying, Nil)
      }

      def mapEvery[U](step: Int)(f: A => U) = {
        @tailrec
        def loop(in: List[A], out: List[U]): List[U] = {
          in match {
            case Nil => out.reverse
            case head :: tail => loop(tail.drop(step), f(head) :: out)
          }
        }

        loop(underlying, Nil)
      }
      def mapDrop[U](drop1: Int, drop2: Int, step: Int)(f: (A, A) => U): List[U] = {
        @tailrec
        def loop(in: List[A], out: List[U]): List[U] = in match {
          case Nil          => out
          case head :: tail =>
            loop(tail.drop(drop1), out ::: tail.drop(drop2).mapEvery(step) { f(head, _) } )
        }

        loop(underlying, Nil)
      }
    }
  }
}

【讨论】:

  • 这与 OP 的问题中展示的迭代模式不同。
  • 未删除和(希望)改进
【解决方案2】:

这是我的刺。我认为它的可读性很好。直觉是:对于列表的每个头部,将函数应用于头部和尾部的每个其他成员。然后在列表的尾部递归。

def mapNext[U, T](list: List[U], fun: (U, U) => T): List[T] = list match {
  case Nil => Nil
  case (first :: Nil) => Nil
  case (first :: rest) => rest.map(fun(first, _: U)) ++ mapNext(rest, fun)
}

这是一个示例运行

scala> mapNext(List(1, 2, 3, 4), (x: Int, y: Int) => x + y)
res6: List[Int] = List(3, 4, 5, 5, 6, 7)

这不是明确的尾递归,但可以很容易地添加一个累加器来实现它。

【讨论】:

    【解决方案3】:

    递归当然是一种选择,但标准库提供了一些替代方案,可以实现相同的迭代模式。

    这是一个用于演示目的的非常简单的设置。

    val lst = List("a","b","c","d")
    def doSomething(a:String, b:String) = a+b
    

    这是实现我们所追求的目标的一种方法。

    val resA = lst.tails.toList.init.flatMap(tl=>tl.tail.map(doSomething(tl.head,_)))
    // resA: List[String] = List(ab, ac, ad, bc, bd, cd)
    

    这行得通,但flatMap() 中有一个map() 的事实表明,可以使用for 理解来美化它。

    val resB = for {
      tl <- lst.tails
      if tl.nonEmpty
      h = tl.head
      x <- tl.tail
    } yield doSomething(h, x)  // resB: Iterator[String] = non-empty iterator
    
    resB.toList  // List(ab, ac, ad, bc, bd, cd)
    

    在这两种情况下,toList 转换都用于让我们回到原始集合类型,这实际上可能不是必需的,具体取决于需要对集合进行什么进一步处理。

    【讨论】:

      【解决方案4】:
      list       // [a, b, c, d, ...]
        .indices // [0, 1, 2, 3, ...]
        .flatMap { i =>
          elem = list(i) // Don't redo access every iteration of the below map.
          list.drop(i + 1) // Take only the inputs that come after the one we're working on
            .map(doSomething(elem, _))
        }
      // Or with a monad-comprehension
      for {
        index <- list.indices
        thisElem = list(index)
        thatElem <- list.drop(index + 1)
      } yield doSomething(thisElem, thatElem)
      

      您不是从列表开始,而是从其indices 开始。然后,您使用flatMap,因为每个索引都指向一个元素列表。使用drop 仅获取我们正在处理的元素之后的元素,并将该列表映射到实际运行计算。请注意,这具有可怕的时间复杂度,因为这里的大多数操作,indices/lengthflatMapmap 在列表大小中是 O(n),而 dropapplyO(n)在论据中。

      如果您 a) 停止使用链表(List 适合 LIFO、顺序访问,但 Vector 在一般情况下更好),您可以获得更好的性能,并且 b) 让这有点难看

      val len = vector.length
      (0 until len)
        .flatMap { thisIdx =>
          val thisElem = vector(thisIdx)
          ((thisIdx + 1) until len)
            .map { thatIdx =>
              doSomething(thisElem, vector(thatIdx))
            }
        }
      // Or
      val len = vector.length
      for {
        thisIdx <- 0 until len
        thisElem = vector(thisIdx)
        thatIdx <- (thisIdx + 1) until len
        thatElem = vector(thatIdx)
      } yield doSomething(thisElem, thatElem)
      

      如果你真的需要,你可以通过使用一些implicit CanBuildFrom 参数将这段代码的任一版本推广到所有IndexedSeqs,但我不会介绍。

      【讨论】:

        猜你喜欢
        • 2021-07-03
        • 2020-07-22
        • 2016-02-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-04
        • 1970-01-01
        相关资源
        最近更新 更多