问题
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