Apply function to one element only in list or array in Scala
For any given list or array like
val list = (1 to 3).toList
val array = (1 to 3).toArray
and a given function that maps from and to a collection type, for example
def f(v: Int): Int = v + 10
how to apply f
to the i-th element list
or array
to
list.myApply(f, ith = 2)
res: List(1,12,3)
and
array.myApply(f, ith = 2)
res: Array(1,12,3)
source to share
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
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)))
}
Usage example:
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, 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!
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 that you also wanted it to work for Array
, which it isn't SeqLike
, so let me think some more ...
Ah, Predef
has an implicit conversion from Array
to ArrayOps
, which is a subtype SeqLike
, so we just need to use an estimate.
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)))
}
Finally, we have the correct 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 restrictions are deprecated, so using this guide we can replace it with a very ugly context related.
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)))
}
source to share
Someone just asked about the 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)
Summarizing 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 exit, as Chris suggests:
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)
Since Chris said:
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.
source to share
It is very difficult to add additional methods to existing collections with implicits. If using external method OK, this is the solution. Almost there, but not quite. Example in 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 ArrayBuffer instead of Array. For all other types, Seq works correctly. I've tried many other combinations but nothing fixes this problem.
val a : Seq[Int] = Array(1, 2)
a.zipWithIndex
This zipWithIndex returns an ArrayBuffer, but if used val a: Array[Int]
, it zipWithIndex
returns an Array.
Causal Java set bindings in the collections I think.
source to share