如何使用内置的高阶函数或尾递归将以下循环(模式)重写为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
}
编辑: 对于所有贡献者,我只希望我能接受每个答案作为解决方案:)
答案 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
/ length
,flatMap
,map
在列表大小中都是O(n)
,并且{参数中{1}}和drop
为apply
。
如果你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)
强制转换将我们带回原始集合类型,根据需要对集合进行进一步处理,这可能实际上不是必需的。