2011-10-10 47 views
4

从Scala的列表中删除对象的第一次出现的最佳方法是什么?我应该如何从Scala中的列表中删除第一次出现的对象?

来自Java,我习惯于有一个List.remove(Object o)方法,从列表中删除第一次出现的元素。现在我在Scala工作,我期望该方法返回一个新的不可变的List,而不是改变给定的列表。我也可能期望remove()方法取得一个谓词而不是一个对象。总之,我希望找到一种方法是这样的:

/** 
* Removes the first element of the given list that matches the given 
* predicate, if any. To remove a specific object <code>x</code> from 
* the list, use <code>(_ == x)</code> as the predicate. 
* 
* @param toRemove 
*   a predicate indicating which element to remove 
* @return a new list with the selected object removed, or the same 
*   list if no objects satisfy the given predicate 
*/ 
def removeFirst(toRemove: E => Boolean): List[E] 

当然,我可以实现这个方法自己几种不同的方式,但他们都不在我跳出来为显然是最好的。我宁愿不将我的列表转换为Java列表(甚至是一个Scala可变列表),然后再返回,尽管这肯定会起作用。我可以使用List.indexWhere(p: (A) ⇒ Boolean)

def removeFirst[E](list: List[E], toRemove: (E) => Boolean): List[E] = { 
    val i = list.indexWhere(toRemove) 
    if (i == -1) 
    list 
    else 
    list.slice(0, i) ++ list.slice(i+1, list.size) 
} 

但是,使用指数与链表通常是不走的最有效方式。

我可以写这样一个更有效的方法:

def removeFirst[T](list: List[T], toRemove: (T) => Boolean): List[T] = { 
    def search(toProcess: List[T], processed: List[T]): List[T] = 
    toProcess match { 
     case Nil => list 
     case head :: tail => 
     if (toRemove(head)) 
      processed.reverse ++ tail 
     else 
      search(tail, head :: processed) 
    } 
    search(list, Nil) 
} 

不过,这不完全是简洁。看起来很奇怪,没有一种方法可以让我高效而简洁地做到这一点。那么,我是否错过了一些东西,还是我的最后一个解决方案确实如此好?

+0

拍摄,我搜索这个问题一会儿之前问它,但我只发现了一个重复后,我发布它:http://stackoverflow.com/questions/5636717/what-is-an-idiomatic-scala-从一个不可改变的列表中删除一个元素 –

回答

14

你可以用span来清理代码。

scala> def removeFirst[T](list: List[T])(pred: (T) => Boolean): List[T] = { 
    | val (before, atAndAfter) = list span (x => !pred(x)) 
    | before ::: atAndAfter.drop(1) 
    | } 
removeFirst: [T](list: List[T])(pred: T => Boolean)List[T] 

scala> removeFirst(List(1, 2, 3, 4, 3, 4)) { _ == 3 } 
res1: List[Int] = List(1, 2, 4, 3, 4) 

Scala Collections API overview是了解一些鲜为人知的方法的好地方。

+0

但是这不起作用。我认为你必须否定谓词。 – Debilski

+0

哦谢谢,更新。 – retronym

+0

啊,是的,我认为跨度是我一直在寻找的。谢谢! –

2

这是可变性的一点点走一段很长的路要走的情况下:

def withoutFirst[A](xs: List[A])(p: A => Boolean) = { 
    var found = false 
    xs.filter(x => found || !p(x) || { found=true; false }) 
} 

这是很容易推广到丢弃第一n项目匹配谓语。 (i<1 || { i = i-1; false }

你也可以写自己的过滤器,但在这一点上你几乎可以肯定最好使用span因为这个版本将会使栈溢出,如果列表很长:

def withoutFirst[A](xs: List[A])(p: A => Boolean): List[A] = xs match { 
    case x :: rest => if (p(x)) rest else x :: withoutFirst(rest)(p) 
    case _ => Nil 
} 

和任何否则比span更复杂,没有任何明确的好处。

+0

我不喜欢这个解决方案,因为即使'found'为真,筛选器也会检查所有值。如果搜索到的值被找到,我更喜欢tailrec内部方法,它可以追加列表的尾部。 – sschaef

+0

@Antoras - 点列表。我已经展示了如何用递归方法来做到这一点;我会留下tailrecization为动机的练习。 –