问题
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)
回答1:
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)))
}
回答2:
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.
回答3:
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.
来源:https://stackoverflow.com/questions/25800702/apply-function-to-one-element-only-in-list-or-array-in-scala