在Scala中重写循环到声明式样式的命令

时间:2017-08-05 17:24:17

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

如何使用内置的高阶函数或尾递归将以下循环(模式)重写为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
}

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

4 个答案:

答案 0 :(得分:1)

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),并且{参数中{1}}和dropapply

如果你a)停止使用链接列表,你可以获得更好的性能(O(n)适用于LIFO,顺序访问,但List在一般情况下更好),并且b)使这个一点点丑陋的

Vector

如果您确实需要,可以使用一些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) IndexedSeq参数将此代码的任一版本推广到所有implicit,但我不会覆盖

答案 1 :(得分:1)

回归尝试:

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

<%= form_tag update_pretests_objective_seminars_path do %>
    <input type="hidden" name="seminar_id" value="<%= @seminar.id %>">

    <table>
        <% @os.each do |os| %>
            <% obj = os.objective %>
            <tr>
                <td><%= check_box_tag 'pretest_on[]', os.id, os.pretest > 0, {:id => "pretest_on_#{obj.id}"} %></td>
                <td><%= obj.name %></td>
            </tr>
        <% end %>
    </table>

    <%= submit_tag "Update Pretests" %>
<% end %>

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

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)
}

然后你可以使用像:

这样的功能
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)
      }
    }
  }
}

同样,有一个缺点,因为连接列表相对昂贵。 然而,对于理解而言,内部的变量辅助也可能是非常低效的(因为dotty Scala Wart: Convoluted de-sugaring of for-comprehensions的改进任务建议)。

<强>更新

现在我已经进入了这个阶段,我根本无法放手:(

关于'请注意,此处使用+1步骤,但一般情况下,它可能是+ n。'

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

list.mapNext(doSomething)

答案 2 :(得分:1)

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

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 :(得分:1)

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

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

func()

这是获得我们所追求的目标的一种方法。

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) 中有map()的事实表明可能会使用flatMap()理解来完善它。

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) 强制转换将我们带回原始集合类型,根据需要对集合进行进一步处理,这可能实际上不是必需的。