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

混江龙づ霸主 提交于 2020-01-12 10:09:07

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!