Apply function to one element only in list or arra

2020-06-27 20:43发布

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条回答
2楼-- · 2020-06-27 21:11

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.

查看更多
beautiful°
3楼-- · 2020-06-27 21:11

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.

查看更多
beautiful°
4楼-- · 2020-06-27 21:22

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)))
}
查看更多
登录 后发表回答