How does one replace the first matching item in a list in Scala?

Let's say you have:

List(('a', 1), ('b', 1), ('c', 1), ('b', 1))

and you want to replace the first ('b', 1) with ('b', 2), and you don't want it to (a) waste time evaluating past the first match and (b) update any further matching tuples.

Is there a relatively concise way of doing this in Scala (i.e., without taking the list apart and re-concatenating it). Something like an imaginary function mapFirst that returns the list with the first matching value incremented:

testList.mapFirst { case ('b', num) => ('b', num + 1) }

Upvotes: 2

Views: 528

Answers (3)

Shrey
Shrey

Reputation: 2404

Using span to find first element only. It shouldn't throw an exception even when case is not satified. Need less to say, you can specify as many cases as you like.

  implicit class MyRichieList[A](val l: List[A]) {

    def mapFirst(pf : PartialFunction[A, A]) = 
        l.span(!pf.isDefinedAt(_)) match {
           case (x, Nil) => x
           case (x, y :: ys) => (x :+ pf(y)) ++ ys
     }

  }

  val testList = List(('a', 1), ('b', 1), ('c', 1), ('b', 1))
  testList.mapFirst { 
      case ('b', n) => ('b', n + 1) 
      case ('a', 9) => ('z', 9) 
  }
  // result --> List((a,1), (b,2), (c,1), (b,1))

Upvotes: 1

Kigyo
Kigyo

Reputation: 5768

You don't have to take the whole List apart i guess. (Only until the element is found)

def replaceFirst[A](a : List[A], repl : A, replwith : A) : List[A] = a match {
  case Nil => Nil
  case head :: tail => if(head == repl) replwith :: tail else head :: replaceFirst(tail, repl, replwith) 
}

The call for example:

replaceFirst(List(('a', 1), ('b', 1), ('c', 1), ('b', 1)), ('b', 1), ('b', 2))

Result:

List((a,1), (b,2), (c,1), (b,1))

A way with a partial function and implicits (which looks more like your mapFirst):

implicit class MyRichList[A](val list: List[A]) {
    def mapFirst(func: PartialFunction[A, A]) = {
      def mapFirst2[A](a: List[A], func: PartialFunction[A, A]): List[A] = a match {
        case Nil => Nil
        case head :: tail => if (func.isDefinedAt(head)) func.apply(head) :: tail else head :: mapFirst2(tail, func)
      }
      mapFirst2(list, func)
    }
}

And use it like this:

List(('a', 1), ('b', 1), ('c', 1), ('b', 1)).mapFirst {case ('b', num) => ('b', num + 1)}

Upvotes: 3

Ashalynd
Ashalynd

Reputation: 12563

You can emulate such function relatively easily. The quickest (implementation-wise, not necessarily performance-wise) I could think of was something like this:

def replaceFirst[A](a:List[A], condition: (A)=>Boolean, transform:(A)=>(A)) = {
     val cutoff =a.indexWhere(condition)
     val (h,t) = a.splitAt(cutoff)
     h ++ (transform(t.head) :: t.tail)
}

scala> replaceFirst(List(1,2,3,4,5),{x:Int => x%2==0}, { x:Int=> x*2 })
res4: List[Int] = List(1, 4, 3, 4, 5)

scala> replaceFirst(List(('a',1),('b',2),('c',3),('b',4)), {m:(Char,Int) => m._1=='b'},{m:(Char,Int) => (m._1,m._2*2)})
res6: List[(Char, Int)] = List((a,1), (b,4), (c,3), (b,4))

Upvotes: 2

Related Questions