I am a bit confused with documentation about this function/with examples of use provided. Does flatten can happen only once? like
List(List(1, 2), List(3, List(4, 5, 6))) -> List(1, 2, 3, List(4, 5, 6))
Or you somehow specify the depth of flattening, so that it could become a List(1, 2, 3, 4, 5, 6) ?
Because, for example, in JS it seems that function can flat() whatever depth you want into a 1D array. Can Scala flatten do that or it's capable of only elevating once?
I am trying to recreate that function by myself and want to mimic the required behavior and understand the reason why it may work differently.
As mentioned in the comments defining such a method in Scala 2 would not be straightforward to do in a typesafe manner. First of all, recursive types are not supported directly in Scala 2, so you'd have to operate on List[Any] and use runtime reflection to distinguish if the element is a list or an integer.
Recently released Scala 3 has many improvements in its type system, so I wondered that maybe it would be possible to implement such a method there? I tried and I think I was able to achieve usable implementation.
First of all, I wondered if it would be possible to implement recursive union type (a similar thing is possible in typescript):
type ListOr[A] = A | List[ListOr[A]]
unfortunately, such type was raising compiler error:
illegal cyclic type reference: alias ... of type ListOr refers back to the type itself
That was disappointing, but after some digging, I found that I could define such recursive type as:
type ListOr[A] = A match {
case AnyVal => AnyVal | List[ListOr[AnyVal]]
case _ => A | List[ListOr[A]]
}
and it was usable:
val ints: ListOr[Int] = List(List(1), 2, 3, List(List(4, List(5)), 6), 7, 8, List(9))
val strings: ListOr[String] = List(List("A", "B", "C"), List(List("D", List("E")), "F"), "G", "H", List("I"), "J")
so now I needed to just implement the flattening function:
//I needed class tag for A to be able to do a match
def deepFlatten[A: ClassTag](s: ListOr[A]): List[A] =
s match
case a: A => List(a)
case ls: List[_ <: ListOr[A]] => ls.flatMap(deepFlatten(_))
and it seemed to be working correctly:
#main
def main =
val i: List[Int] = deepFlatten[Int](ints) //List(1, 2, 3, 4, 5, 6, 7, 8, 9)
val j: List[String] = deepFlatten[String](strings)//List(A, B, C, D, E, F, G, H, I, J)
Obviously, such implementation could be improved (it's not tail-recursive), but it's doing its job.
Since I'm Scala 3 novice I'm not sure if that's the best implementation, but it's definitely possible to implement such arbitrarily deep flattening functions as type-safe.
Scastie with the solution.
This is an attempt of doing something similar using a simple Tree data structure.
final case class Tree[+A](value: A, children: List[Tree[A]] = Nil)
def flattenTree[A](tree: Tree[A]): List[A] = {
#annotation.tailrec
def loop(remainingNodes: List[Tree[A]], acc: List[A]): List[A] =
remainingNodes match {
case Tree(value, children) :: tail =>
loop(
remainingNodes = children reverse_::: tail,
value :: acc
)
case Nil =>
acc
}
loop(remainingNodes = tree :: Nil, acc = List.empty)
}
Which can be used like this:
val tree = Tree(
value = 1,
children = List(
Tree(value = 2),
Tree(
value = 3,
children = List(
Tree(value = 4),
Tree(value = 5),
Tree(value = 6)
)
)
)
)
val flattenedTree = flattenTree(tree)
println(flattenedTree.mkString("[", ", ", "]"))
Which will produce the following output:
[2, 4, 5, 6, 3, 1]
As you can see is a reversed DFS (whose results are also reversed). If the order doesn't matter this is a straightforward and efficient implementation, if order matters then one can play with the code.
Another approach would be to use a data structure like:
sealed trait ListOr[+A] extends Product with Serializable
final case class NestedList[+A](data: List[ListOr[A]]) extends ListOr[A]
final case class SingleValue[+A](value: A) extends ListOr[A]
You can see the code running here.
Related
There is no built-in function or a method of a List that would allow user to add a new element in a certain position of a List. I've wrote a function that does this but I'm not sure that its a good idea to do it this way, even though it works perfectly well:
def insert(list: List[Any], i: Int, value: Any) = {
list.take(i) ++ List(value) ++ list.drop(i)
}
Usage:
scala> insert(List(1,2,3,5), 3, 4)
res62: List[Any] = List(1, 2, 3, 4, 5)
Type Safety
The most glaring thing I see is the lack of type safety / loss of type information. I would make the method generic in the list's element type:
def insert[T](list: List[T], i: Int, value: T) = {
list.take(i) ++ List(value) ++ list.drop(i)
}
Style
If the body only consists of a single expression, there is no need for curly braces:
def insert[T](list: List[T], i: Int, value: T) =
list.take(i) ++ List(value) ++ list.drop(i)
Efficiency
#Marth's comment about using List.splitAt to avoid traversing the list twice is also a good one:
def insert[T](list: List[T], i: Int, value: T) = {
val (front, back) = list.splitAt(i)
front ++ List(value) ++ back
}
Interface
It would probably be convenient to be able to insert more than one value at a time:
def insert[T](list: List[T], i: Int, values: T*) = {
val (front, back) = list.splitAt(i)
front ++ values ++ back
}
Interface, take 2
You could make this an extension method of List:
implicit class ListWithInsert[T](val list: List[T]) extends AnyVal {
def insert(i: Int, values: T*) = {
val (front, back) = list.splitAt(i)
front ++ values ++ back
}
}
List(1, 2, 3, 6).insert(3, 4, 5)
// => List(1, 2, 3, 4, 5, 6)
Closing remarks
Note, however, that inserting into the middle of the list is just not a good fit for a cons list. You'd be much better off with a (mutable) linked list or a dynamic array instead.
You can also use xs.patch(i, ys, r), which replaces r elements of xs starting with i by the patch ys, by using r=0 and by making ys a singleton:
List(1, 2, 3, 5).patch(3, List(4), 0)
In the Scala course by his eminence Martin Odersky himself, he implements it similarly to
def insert(list: List[Any], i: Int, value: Any): List[Any] = list match {
case head :: tail if i > 0 => head :: insert(tail, i-1, value)
case _ => value :: list
}
One traversal at most.
I have run into this scenario several times recently:
a class has an immutable (indexed?) sequence member
a factory member method creates a new instance with the sequence somewhat modified
What's an efficient way to do this?
class A( xs: IndexedSeq[Int] ) {
def another: A = {
val ys = xs.toArray.clone() // !!!
ys(7) = 13
new A(ys)
}
}
I do .toArray so that I can modify this sequence in place and .clone because I'm afraid that if the original xs was an array already, toArray will just return this and I will modify the objects (meant-to-be-immutable) values. However, this obviously makes two copies if xs was not an Array and I would really like to avoid that. Obviously, I could just check its type, but that seems very inelegant and I'm not too sure if I'd have to check against other mutable sequences which can wrap an Array. What do?
scala> val xs: Seq[Int] = Array(1, 2, 3)
ss: Seq[Int] = WrappedArray(1, 2, 3)
scala> val ys = xs.toArray
ys: Array[Int] = Array(1, 2, 3)
scala> ys(1) = 22
scala> ys
res1: Array[Int] = Array(1, 22, 3)
scala> xs
res2: Seq[Int] = WrappedArray(1, 22, 3)
If you don't really need mutability, then the best is just to ask for an immutable sequence; that way you don't need to worry about whether changing the data has side effects.
class A(xs: collection.immutable.IndexedSeq[Int]) {
def another: A = {
val ys = xs.updated(7, 13)
new A(ys)
}
}
I have an expression returning an object, and I want to invoke a method on the resulting object only if a certain boolean condition is true. I want to get the result (whether the object, or the result of invoking the method on the object) in a val.
One way is to use a temporary var, such as in the following example, in which List(3, 1, 2) is the (potentially complicated) expression returning an object, list is the temporary var, and .sorted is the method I want to conditionally invoke:
import scala.util.Random
val condition = Random.nextBoolean
val result = {
var list = List(3, 1, 2);
if (condition) list = list.sorted
list
}
What would be the canonical way to do this, perhaps without using a temporary var?
Note that
if (condition) List(3, 1, 2).sorted else List(3, 1, 2)
is not quite satisfactory because List(3, 1, 2) may in general be a complicated expression that I don't want to repeat.
Here is one method I found that unfortunately involves giving explicit types (and is longer and more complicated than introducing a temporary var as above):
val condition = Random.nextBoolean
val result =
(
if (condition)
{l: List[Int] => l.sorted}
else
identity(_: List[Int])
).apply(List(3, 1, 2))
I suspect there must be a tidier way that I have failed to recognize.
Update: A slightly less ugly method that unfortunately still requires explicit type information:
val condition = Random.nextBoolean
val result = {
l: List[Int] => if (condition) l.sorted else l
}.apply(List(3, 1, 2))
You can use the forward pipe operator:
implicit class PipedObject[A](value: A) {
def |>[B](f: A => B): B = f(value)
}
scala> List(3, 1, 2) |> (xs => if (true) xs.sorted else xs)
res1: List[Int] = List(1, 2, 3)
Starting Scala 2.13, the standard library now provides the chaining operation pipe which can be used to convert/pipe a value with a function of interest, and thus avoids an intermediate variable:
import scala.util.chaining._
List(3, 1, 2).pipe(list => if (condition) list.sorted else list)
I'm looking for a way to treat an Any as an Array or Seq and iterate over it, if possible.
Currently I have some code that looks like this, taking a sequence of Any's and flattening out any Traversable or Array objects contained.
def flattenAsStrings(as: Seq[Any]): Seq[String] = {
val (travValued, other) = as.partition(a => classOf[Traversable[_]] isAssignableFrom(a.getClass))
val (arrayValued, singleValued) = other.partition(a => a.isInstanceOf[Array[_]])
val travStrings = travValued.map(_.asInstanceOf[Traversable[_]].map(_.toString)).flatMap(_.toList)
val arrayStrings = arrayValued.map(_.asInstanceOf[Array[_]].map(_.toString)).flatMap(_.toList)
singleValued.map(_.toString) ++ travStrings ++ arrayStrings
}
It feels like there mustr be a simpler way to do this in Scala, given implicit conversions and whatnot. Anyone?
Basically you want to force each element to a Seq, and then flatten them all at once. Array has an implicit conversion to Seq and both Seq and Traversable have a .toSeq method. So we can do:
val t: Traversable[Int] = List(1, 2, 3)
val a: Array[Int] = Array(4, 5, 6)
val other = "whatever"
val as: Seq[Any] = List(t, a, other)
as.flatMap{
case t: Traversable[_] => t.toSeq
case a: Array[_] => a.toSeq
case other => Seq(other)
}.map{_.toString}
//Seq[java.lang.String] = List(1, 2, 3, 4, 5, 6, whatever)
(as an aside, this is pretty ugly Scala code, you might want to consider refactoring things to get rid of using a Seq[Any] in the first place)
compiling this code in scala 2.7.6:
def flatten1(l: List[Any]): List[Any] = l.flatten
i get the error:
no implicit argument matching parameter type (Any) = > Iterable[Any] was found
why?
If you are expecting to be able to "flatten" List(1, 2, List(3,4), 5) into List(1, 2, 3, 4, 5), then you need something like:
implicit def any2iterable[A](a: A) : Iterable[A] = Some(a)
Along with:
val list: List[Iterable[Int]] = List(1, 2, List(3,4), 5) // providing type of list
// causes implicit
// conversion to be invoked
println(list.flatten( itr => itr )) // List(1, 2, 3, 4, 5)
EDIT: the following was in my original answer until the OP clarified his question in a comment on Mitch's answer
What are you expecting to happen when you flatten a List[Int]? Are you expecting the function to sum the Ints in the List? If so, you should be looking at the new aggegation functions in 2.8.x:
val list = List(1, 2, 3)
println( list.sum ) //6
The documentation:
Concatenate the elements of this list. The elements of this list should be a Iterables. Note: The compiler might not be able to infer the type parameter.
Pay close attention to that second sentence. Any cannot be converted to Iterable[Any]. You could have a list of Ints, and that list cannot be flattened since Int is not iterable. And think about it, if you have List[Int], what are you flattening? You need List[B <: Iterable[Any]], because then you would have two dimensions, which can be flattened to one.