arrayslistscalascala-collectionszipper

Apply function to one element only in list or array in Scala


For any given list or array, for instance

val list = (1 to 3).toList
val array = (1 to 3).toArray

and a given function that maps from and onto the collection type, for instance

def f(v: Int): Int = v + 10

how to apply f to the ith element of list or array so that

list.myApply(f, ith = 2)
res: List(1,12,3)

and also

array.myApply(f, ith = 2)
res: Array(1,12,3)

Solution

  • tl;dr

    import scala.collection.SeqLike
    import scala.collection.generic.CanBuildFrom
    
    implicit class Seq_[A, Repr, 
        S : ({type L[X] = X => SeqLike[A, Repr]})#L](seq: S) {
    
      def myApply[B >: A, That](f: A => B, ith: Int)
          (implicit bf: CanBuildFrom[Repr, B, That]): That =
        seq.updated(ith - 1, f(seq(ith - 1)))
    }
    

    Discussion

    A naive approximation:

    implicit class Seq_[A](seq: Seq[A]) {
      def myApply(f: A => A, ith: Int): Seq[A] =
        seq.updated(ith - 1, f(seq(ith - 1)))
    }
    

    Example usage:

    scala> (1 to 3).toList.myApply(_ + 10, ith = 2)
    res: Seq[Int] = List(1, 12, 3)
    

    Attempted actual solution:

    implicit class Seq_[A, Repr <: SeqLike[A, Repr]](seq: Repr) {
      def myApply[B >: A, That](f: A => B, ith: Int)
                               (implicit bf: CanBuildFrom[Repr, B, That]): That =
        seq.updated(ith - 1, f(seq(ith - 1)))
    }
    

    Unfortunately, the implicit doesn't work. I'm not sure why.

    scala> Seq_[Int, List[Int]]((1 to 3).toList).myApply(_ + 10, ith = 2)
    res: List[Int] = List(1, 12, 3)
    
    scala> Seq_[Int, List[Int]]((1 to 3).toList).myApply(_.toString + "*", ith = 2)
    res: List[Any] = List(1, 2*, 3)
    

    Edit: Fixed it!

    implicit class Seq_[A, Repr](seq: SeqLike[A, Repr]) {
      def myApply[B >: A, That](f: A => B, ith: Int)
                               (implicit bf: CanBuildFrom[Repr, B, That]): That =
        seq.updated(ith - 1, f(seq(ith - 1)))
    }
    

    Example:

    scala> (1 to 3).toList.myApply(_ + 10, ith = 2)
    res: List[Int] = List(1, 12, 3)
    
    scala> (1 to 3).toVector.myApply(Math.pow(2, _), ith = 3)
    res: scala.collection.immutable.Vector[AnyVal] = Vector(1, 2, 8.0)
    

    But I just realized you also wanted it to work for Array, which isn't SeqLike, so let me think some more...

    Ah, Predef has an implicit conversion from Array to ArrayOps, which is a subtype of SeqLike, so we just need to use a view bound.

    implicit class Seq_[A, Repr <% SeqLike[A, Repr]](seq: Repr) {
      def myApply[B >: A, That](f: A => B, ith: Int)
                               (implicit bf: CanBuildFrom[Repr, B, That]): That =
        seq.updated(ith - 1, f(seq(ith - 1)))
    }
    

    And finally we have the right behavior:

    scala> (1 to 3).toList.myApply(_ + 10, ith = 2)
    res: List[Int] = List(1, 12, 3)
    
    scala> (1 to 3).toArray.myApply(Math.pow(2, _), ith = 3)
    res: Array[AnyVal] = Array(1, 2, 8.0)
    

    Edit again - samthebest informs me that view bounds are deprecated, so using this guide we can replace it with a very ugly-looking context bound.

    implicit class Seq_[A, Repr, 
        S : ({type L[X] = X => SeqLike[A, Repr]})#L](seq: S) {
    
      def myApply[B >: A, That](f: A => B, ith: Int)
          (implicit bf: CanBuildFrom[Repr, B, That]): That =
        seq.updated(ith - 1, f(seq(ith - 1)))
    }