3

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)

3 Answers 3

7

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)))
}
Sign up to request clarification or add additional context in comments.

5 Comments

A "seed" upvote hoping you'll work out the type args. It's so easy to burn an hour on collections.
When it comes to implicits that don't apply, I can never tell whether it's my bug or the compiler's, so it's hard to know when to call it quits.
Fixed, although I still don't know why.
Nice, but I won't reward with upvote until you refactor to use Context Bounds given View Bounds are deprecated :)
Hm, I didn't know view bounds were deprecated. scala.collection doesn't contain an appropriate type class for this, does it? Why does Array have to be such a pariah? :(
3

Someone just asked about patch, so maybe this is a duplicate:

scala> val list = (1 to 3).toList
list: List[Int] = List(1, 2, 3)

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

scala> def g(is: Seq[Int], f: Int => Int, i: Int) = is.patch(i, Seq(f(is(i))), 1)
g: (is: Seq[Int], f: Int => Int, i: Int)Seq[Int]

scala> g(list, f, 1)
res1: Seq[Int] = List(1, 12, 3)

Generalizing a smidgen:

scala> def g(is: collection.Seq[Int], f: Int => Int, i: Int, count: Int = 1) = is.patch(i, is.slice(i, i + count) map f, count)
g: (is: Seq[Int], f: Int => Int, i: Int, count: Int)Seq[Int]

scala> g(list, f, 1)
res2: Seq[Int] = List(1, 12, 3)

scala> g(list, f, 1, 2)
res3: Seq[Int] = List(1, 12, 13)

This was my first go, as Chris prompts:

scala> def g(is: collection.mutable.Seq[Int], f: Int => Int, i: Int) = is(i) = f(is(i))
g: (is: scala.collection.mutable.Seq[Int], f: Int => Int, i: Int)Unit

scala> val as = (1 to 10).toArray
as: Array[Int] = Array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

scala> g(as, f, 1)

scala> as
res7: Array[Int] = Array(1, 12, 3, 4, 5, 6, 7, 8, 9, 10)

So as Chris was saying:

scala> def g(is: collection.Seq[Int], f: Int => Int, i: Int) = is.updated(i, f(is(i)))
g: (is: Seq[Int], f: Int => Int, i: Int)Seq[Int]

And good night, Gracie.

Comments

0

It is very complex to add additional methods to the existing collections with implicits. If using an external method OK, this is a solution. Almost there but not quite. Example in a Scala IDE worksheet

object SeqOps {
  def applyToith(col: Seq[Int], f: Int => Int, ith: Int): Seq[Int] = {
    val indexCol = col.zipWithIndex
    indexCol.map {
      a => if (a._2 == ith) f(a._1) else a._1
    }
  }                                       //> applyToith: (col: Seq[Int], f: Int => Int, ith: Int)Seq[Int]

  def f(i: Int) = i + 10                  //> f: (i: Int)Int
  val l = List(1, 2, 3)                   //> l  : List[Int] = List(1, 2, 3)
  applyToith(l, f _, 0)                   //> res0: Seq[Int] = List(11, 2, 3)

  val a = Array(1, 2, 3)                  //> a  : Array[Int] = Array(1, 2, 3)
  applyToith(a, f _, 1)                   //> res1: Seq[Int] = ArrayBuffer(1, 12, 3)

  val v = Vector(1, 2, 3)                 //> v  : scala.collection.immutable.Vector[Int] = Vector(1, 2, 3)
  applyToith(v, f _, 2)                   //> res2: Seq[Int] = Vector(1, 2, 13)

}

In the case of an array, it returns an ArrayBuffer instead of Array. For all other Seq types works properly. I have tried many other combinations but nothing fixes this issue.

val a : Seq[Int] = Array(1, 2)
a.zipWithIndex

This zipWithIndex returns an ArrayBuffer but if val a: Array[Int] is used, zipWithIndex returns an Array.

The vagaries of retro-fitting Java arrays into collections I guess.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.