To get a sequence with one value updated, one can use
seq.updated(index, value)
I want to set a new value for a range of elements. Is there a library function for that? I currently use the following function:
def updatedSlice[A](seq: List[A], ind: Iterable[Int], value: A): List[A] =
if (ind.isEmpty) seq
else updatedSlice(seq.updated(ind.head, value), ind.tail, value)
Besides the need of writing function, this seems to be inefficient, and also works only for lists, rather than arbitrary subclasses of Seq and Strings. So,
is there a method that performs it?
how can I parametrize the function to take (and return) some subclass of Seq[A]?
To my knowledge there's no combinator that directly provides this functionality.
For the Seq part, well, it works only for List because you're taking a List as a parameter. Take a Seq, return a Seq and you already have one less problem.
Moreover, your implementation throws an IndexOutOfBounds exception if ind contains an index greater or equal to the seq length.
Here's an alternative implementation (which uses Set for a O(1) contains)
def updatedAtIndexes[A](seq: Seq[A], ind: Set[Int], value: A): Seq[A] = seq.zipWithIndex.map {
case (el, i) if ind.contains(i) => value
case (el, _) => el
}
Example
updatedAtIndexes(List(1, 2, 3, 4, 5), Set(0, 2), 42) // List(42, 2, 42, 4)
You can even make it prettier with a simple implicit class:
implicit class MyPimpedSeq[A](seq: Seq[A]) {
def updatedAtIndexes(ind: Set[Int], value: A): Seq[A] = seq.zipWithIndex.map {
case (el, i) if ind.contains(i) => value
case (el, _) => el
}
}
Examples
List(1, 2, 3, 4).updatedAtIndexes(Set(0, 2), 42) // List(42, 2, 42, 4)
Vector(1, 2, 3).updatedAtIndexes(Set(1, 2, 3), 42) // Vector(1, 42, 42)
No one at a computer has said:
scala> (1 to 10).toSeq patch (3, (1 to 5), 3)
res0: scala.collection.immutable.IndexedSeq[Int] = Vector(1, 2, 3, 1, 2, 3, 4, 5, 7, 8, 9, 10)
Save your green checks for #Marth.
Note they're still working on it.
https://issues.scala-lang.org/browse/SI-8474
Which says something about less-frequently-used API.
Update: I glanced at the question a second time and saw that I misread it, oh well:
scala> implicit class x[A](as: Seq[A]) {
| def updatedAt(is: collection.Traversable[Int], a: A) = {
| (as /: is) { case (xx, i) => xx updated (i, a) } } }
defined class x
scala> (1 to 10) updatedAt (Seq(3,6,9), 0)
res9: Seq[Int] = Vector(1, 2, 3, 0, 5, 6, 0, 8, 9, 0)
Just a relaxing round of golf.
Update: s/relaxing/annoying
Looks like it needs more type params, but I don't have a time slice for it.
scala> implicit class slicer[A, B[_] <: Seq[_]](as: B[A]) {
| def updatedAt[That<:B[_]](is: Traversable[Int], a: A)(implicit cbf: CanBuildFrom[B[A], A, That]) =
| (as /: is) { case (x,i) => x updated[A,That] (i,a) }}
<console>:15: error: type arguments [A,That] conform to the bounds of none of the overloaded alternatives of
value updated: [B >: _$1, That](index: Int, elem: B)(implicit bf: scala.collection.generic.CanBuildFrom[Seq[_$1],B,That])That <and> [B >: A, That](index: Int, elem: B)(implicit bf: scala.collection.generic.CanBuildFrom[Repr,B,That])That
(as /: is) { case (x,i) => x updated[A,That] (i,a) }}
^
Who even knew updated was overloaded?
My new favorite Odersky quote:
I played with it until it got too tedious.
I want to do something like this (simplified quite heavily):
((1, 2, 3, 4, 5, 6), (6, 5, 4, 3, 2, 1)).zipped map (_ + _)
Ignore the actual values of the integers (although it's important that these are 6-tuples, actually :)). Essentially, I want to use this fairly regularly in a function which maintains a Map[String, (Int, Int, Int, Int, Int, Int)] when an existing element is updated.
As it is, Scala spits this out at me:
<console>:6: error: could not find implicit value for parameter w1: ((Int, Int, Int, Int, Int, Int)) => scala.collection.TraversableLike[El1,Repr1]
((1, 2, 3, 4, 5, 6), (6, 5, 4, 3, 2, 1)).zipped
If I use Seqs instead of tuples, everything works fine, but I want to enforce an arity of 6 in the type system (I'll probably type Record = (Int, Int, Int, Int, Int, Int) as a quick refactor shortly).
Can anyone offer some advice on what I'm doing wrong/why Scala won't deal with the code above? I thought it might work if I used a 2- or 3-arity tuple, seeing as Scala defines Tuple2 and Tuple3s (I understand that scaling tuple functions across an arbitrary n-arity is difficult), but I get the same error.
Thanks in advance for any help offered :).
You only want to map over tuples which have identical types--otherwise the map wouldn't make sense--but Tuple doesn't contain that in its type signature. But if you're willing to do a little work, you can set it up so that tuples work the way you requested:
Groundwork:
class TupTup6[A,B](a: (A,A,A,A,A,A), b: (B,B,B,B,B,B)) {
def op[C](f:(A,B)=>C) = ( f(a._1,b._1), f(a._2,b._2), f(a._3,b._3),
f(a._4,b._4), f(a._5,b._5), f(a._6,b._6) )
}
implicit def enable_tuptup6[A,B](ab: ((A,A,A,A,A,A),(B,B,B,B,B,B))) = {
new TupTup6(ab._1,ab._2)
}
Usage:
scala> ((1,2,3,4,5,6) , (6,5,4,3,2,1)) op { _ + _ }
res0: (Int, Int, Int, Int, Int, Int) = (7,7,7,7,7,7)
I received this little inspiration.
class TupleZipper[T <: Product](t1: T) {
private def listify(p: Product) = p.productIterator.toList
def zipWith(t2: T) = (listify(t1), listify(t2)).zipped
}
implicit def mkZipper[T <: Product](t1: T) = new TupleZipper(t1)
// ha ha, it's arity magic
scala> ((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2))
<console>:8: error: type mismatch;
found : (Int, Int, Int, Int, Int)
required: (Int, Int, Int, Int, Int, Int)
((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2))
^
scala> ((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2, 1))
res1: (List[Any], List[Any])#Zipped[List[Any],Any,List[Any],Any] = scala.Tuple2$Zipped#42e934e
scala> res1 map ((x, y) => x.asInstanceOf[Int] + y.asInstanceOf[Int])
res2: List[Int] = List(7, 7, 7, 7, 7, 7)
Yes, a bunch of Anys comes out the other end. Not real thrilling but not a lot you can do when you try to force yourself on Tuples this way.
Edit: oh, and of course the type system gives you the full monty here.
scala> ((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2, "abc"))
<console>:8: error: type mismatch;
found : java.lang.String("abc")
required: Int
((1, 2, 3, 4, 5, 6)) zipWith ((6, 5, 4, 3, 2, "abc"))
^
import scala.collection._
type Record = (Int, Int, Int, Int, Int, Int)
implicit def toIterable(r: Record) = new Iterable[Int]{
def iterator = r.productIterator.asInstanceOf[Iterator[Int]]
}
implicit def cbf[From <: Iterable[Int]] = new generic.CanBuildFrom[From, Int, Record] {
def apply(from: From) = apply
def apply = new mutable.Builder[Int, Record] {
var array = Array.ofDim[Int](6)
var i = 0
def +=(elem: Int) = {
array(i) += elem
i += 1
this
}
def clear() = i = 0
def result() = (array(0), array(1), array(2), array(3), array(4), array(5))
}
}
usage:
scala> ((1, 2, 3, 4, 5, 6), (6, 5, 4, 3, 2, 1)).zipped.map{_ + _}
res1: (Int, Int, Int, Int, Int, Int) = (7,7,7,7,7,7)
Tuple2#zipped won't help you out here, it works when the contained elements are TraversableLike/IterableLike - which Tuples aren't.
You'll probably want to define your own sumRecords function that takes two Records and returns their sum:
def sumRecord(a:Record, b:Record) = new Record(
a._1 + b._1,
a._2 + b._2,
a._3 + b._3,
a._4 + b._4,
a._5 + b._5,
a._6 + b._6
)
Then to use it with a Pair[Record, Record]:
val p : Pair[Record, Record] = ...
val summed = sumRecord(p._1, p._2)
Sure, there are abstractions available; but as Record is going to be fixed throughout your design, then they have little value.
short solution:
type Record = (Int, Int, Int, Int, Int, Int)
implicit def toList(r: Record) = r.productIterator.asInstanceOf[Iterator[Int]].toList
implicit def toTuple(l: List[Int]): Record = (l(0), l(1), l(2), l(3), l(4), l(5))
usage:
scala> ((1,2,3,4,5,6), (6,5,4,3,2,1)).zipped map {_ + _}: Record
res0: (Int, Int, Int, Int, Int, Int) = (7,7,7,7,7,7)
You can now easily achieve this with shapeless, this way:
import shapeless._
import shapeless.syntax.std.tuple._
val a = (1, 2, 3, 4, 5, 6)
val b = (6, 5, 4, 3, 2, 1)
object sum extends Poly1 {
implicit def f = use((t: (Int, Int)) => t._1 + t._2)
}
val r = a.zip(b) map sum // r is a (Int, Int, Int, Int, Int, Int)
The drawback is the weird syntax you have to use to express the sum function, but everything is type-safe and type-checked.
As update to Rex Kerr answer, starting from Scala 2.10 you can use implicit classes: syntactic sugar that makes that solution even shorter.
implicit class TupTup6[A,B](x: ((A,A,A,A,A,A),(B,B,B,B,B,B))) {
def op[C](f:(A,B)=>C) = (
f(x._1._1,x._2._1),
f(x._1._2,x._2._2),
f(x._1._3,x._2._3),
f(x._1._4,x._2._4),
f(x._1._5,x._2._5),
f(x._1._6,x._2._6) )
}
You get the error because you treat the tuple as a collection.
Is it possible for you to use lists instead of tuples? Then the calculation is simple:
scala> List(1,2,3,4,5,6).zip(List(1,2,3,4,5,6)).map(x => x._1 + x._2 )
res6: List[Int] = List(2, 4, 6, 8, 10, 12)