Say I have a list as follows:
val l = List( (1, 2, "hi"), (1, 3, "hello"), (2, 3, "world"), (1, 2, "hello") )
I want to make the elements of l distinct ignoring the 3rd element of the tuple. That is, two elements of l are considered same if their first two components are same.
So makeDistinct(l) should return
List( (1, 2, "hi"), (1, 3, "hello"), (2, 3, "world") )
What is the most Scala-like and generic way to do implement makeDistinct
EDIT: We are free to choose which to drop, and ordering need not be preserved.
If you want to do this with lists, use groupBy:
l.groupBy(x => (x._1, x._2)).map(kv => kv._2.head).toList
If you really want to be generic for all collection types:
scala> import scala.collection.generic.CanBuildFrom
import scala.collection.generic.CanBuildFrom
scala> def distinct[A, B, C, CC[X] <: Traversable[X]](xs: CC[(A, B, C)])(implicit cbf: CanBuildFrom[Nothing, (A, B, C), CC[(A, B, C)]]): CC[(A, B, C)] = xs.groupBy(x => (x._1, x._2)).map(kv => kv._2.head).to[CC]
warning: there were 1 feature warnings; re-run with -feature for details
distinct: [A, B, C, CC[X] <: Traversable[X]](xs: CC[(A, B, C)])(implicit cbf: scala.collection.generic.CanBuildFrom[Nothing,(A, B, C),CC[(A, B, C)]])CC[(A, B, C)]
scala> distinct(List((1, 2, "ok"), (1, 3, "ee"), (1, 2, "notok")))
res0: List[(Int, Int, String)] = List((1,3,ee), (1,2,ok))
You can use Ordering:
scala> SortedSet(l: _*)(Ordering[(Int, Int)].on(x => (x._1, x._2))).toList
res33: List[(Int, Int, String)] = List((1,2,hello), (1,3,hello), (2,3,world))
The only problem is that the last found element is preserved. For the first one you need to reverse the list:
scala> SortedSet(l.reverse: _*)(Ordering[(Int, Int)].on(x => (x._1, x._2))).toList
res34: List[(Int, Int, String)] = List((1,2,hi), (1,3,hello), (2,3,world))
The reverse is not optimal but maybe it is possible to create the list directly in reversed order, which would avoid the construction of an unnecessary intermediate list.
Related
I'm trying to write a function that takes multiple lists as input and returns a list of strings representation of every combination between those lists.
Sample Input :
val integers = List(1,2,3,4)
val characters = List('a','b','c')
val strings = List("apple","grapefruit")
Sample Output :
("1-a-apple", "1-a-grapefruit", .....)
Here is what I have so far :
def main(args: Array[String]): Unit = {
val integers = List(1,2,3,4)
val characters = List('a','b','c')
val strings = List("apple","grapefruit")
val lists = List(integers, characters, strings)
println(generator(lists).toString())
}
//using list api
def generator(x: List[List[Any]]): List[List[Any]] = x match {
case Nil => List(Nil)
case h :: _ => h.flatMap(i => generator(x.tail).map(i :: _))
}
And here is the output of my code :
List(List(1, a, apple), List(1, a, grapefruit), List(1, b, apple),
List(1, b, grapefruit), List(1, c, apple), List(1, c, grapefruit),
List(2, a, apple), List(2, a, grapefruit), List(2, b, apple), List(2,
b, grapefruit), List(2, c, apple), List(2, c, grapefruit), List(3, a,
apple), List(3, a, grapefruit), List(3, b, apple), List(3, b,
grapefruit), List(3, c, apple), List(3, c, grapefruit), List(4, a,
apple), List(4, a, grapefruit), List(4, b, apple), List(4, b,
grapefruit), List(4, c, apple), List(4, c, grapefruit))
So my questions are :
1)How can I get the output to be a list of strings instead of list of list of any?
2)How can I use a for comprehension instead of the list api?
val integers = List(1,2,3,4)
val characters = List('a','b','c')
val strings = List("apple","grapefruit")
for {
int <- integers
chars <- characters
string <- strings
} yield { s"${int}-${chars}-${string}" }
That code outputs
val res4: List[String] = List(1-a-apple, 1-a-grapefruit, 1-b-apple, 1-b-grapefruit, 1-c-apple, 1-c-grapefruit, 2-a-apple, 2-a-grapefruit, 2-b-apple, 2-b-grapefruit, 2-c-apple, 2-c-grapefruit, 3-a-apple, 3-a-grapefruit, 3-b-apple, 3-b-grapefruit, 3-c-apple, 3-c-grapefruit, 4-a-apple, 4-a-grapefruit, 4-b-apple, 4-b-grapefruit, 4-c-apple, 4-c-grapefruit)
Here's I'm using string interpolation (e.g. s" variable is $var1") to turn the items into strings.
I have two Seq.
1 has Seq[String] and another has Seq[(String,Double)]
a -> ["a","b","c"] and
b-> [1,2,3]
I want to create output as
[("a",1),("b",2),("c",3)]
I have a code
a.zip(b) is actually creating a seq of those two elements instead of creating a map
Can anyone suggest how to do that in scala?
you simply need .toMap so that you can transform List[Tuple[String, Int]] to Map[String, Int]
scala> val seq1 = List("a", "b", "c")
seq1: List[String] = List(a, b, c)
scala> val seq2 = List(1, 2, 3)
seq2: List[Int] = List(1, 2, 3)
scala> seq1.zip(seq2)
res0: List[(String, Int)] = List((a,1), (b,2), (c,3))
scala> seq1.zip(seq2).toMap
res1: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3)
also see
How to convert a Seq[A] to a Map[Int, A] using a value of A as the key in the map?
Let say I have a list:
[(A, a), (A, b), (A, c), (B, a), (B, d)]
How do I make that list into:
[(A, [a,b,c]), (B, [a,d])]
with a single function?
Thanks
The groupBy function allows you to achieve this:
scala> val list = List((1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'd'))
list: List[(Int, Char)] = List((1,a), (1,b), (1,c), (2,a), (2,d))
scala> list.groupBy(_._1) // grouping by the first item in the tuple
res0: scala.collection.immutable.Map[Int,List[(Int, Char)]] = Map(2 -> List((2,a), (2,d)), 1 -> List((1,a), (1,b), (1,c)))
Just doing groupBy won't give you the expected format you desire. So i suggest you write a custom method for this.
def groupTuples[A,B](seq: Seq[(A,B)]): List[(A, List[B])] = {
seq.
groupBy(_._1).
mapValues(_.map(_._2).toList).toList
}
Then then invoke it to get the desired result.
val t = Seq((1,"I"),(1,"AM"),(1, "Koby"),(2,"UP"),(2,"UP"),(2,"AND"),(2,"AWAY"))
groupTuples[Int, String](t)
I am confused on how to write the map function, which maps over two lists:
for example:
def map[A,B,C](f: (A, B) => C, lst1: List[A], lst2: List[B]): List[C]
The input would be 2 lists and the output could be a list that adds the integers alternatively
Test example:
assert(map(add, List(1, 2, 3), List(4, 5, 6)) == List(5, 7, 9))
You could use f.tupled to convert f from a function that accepts to arguments (A, B) => C, to a function that accepts one argument as a tuple ((A, B)) => C. Then, you can zip the lists together (make them one list of tuples) and feed them to f using the traditional map.
def map[A,B,C](f: (A, B) => C, lst1: List[A], lst2: List[B]): List[C] =
(lst1 zip lst2) map f.tupled
scala> def add(a: Int, b: Int): Int = a + b
add: (a: Int, b: Int)Int
scala> map(add, List(1, 2, 3), List(4, 5, 6))
res20: List[Int] = List(5, 7, 9)
Keep in mind that if the two lists are not the same size, then zip will truncate the longer list to match the size of the smaller one.
As stated in m-z's answer you can zip the lists, and then map on the list of tuples. If you want to avoid the use of tupled, you can do the destructure explicitly:
def map[A,B,C](f: (A, B) => C, lst1: List[A], lst2: List[B]): List[C] = {
val zipped = lst1 zip lst2
zipped.map { case (a,b) => f(a,b) }
}
I wish to add a list of tuples of integers i.e. given an input list of tuples of arity k, produce a tuple of arity k whose fields are sums of corresponding fields of the tuples in the list.
Input
List( (1,2,3), (2,3,-3), (1,1,1))
Output
(4, 6, 1)
I was trying to use foldLeft, but I am not able to get it to compile. Right now, I am using a for loop, but I was looking for a more concise solution.
This can be done type safely and very concisely using shapeless,
scala> import shapeless._, syntax.std.tuple._
import shapeless._
import syntax.std.tuple._
scala> val l = List((1, 2, 3), (2, 3, -1), (1, 1, 1))
l: List[(Int, Int, Int)] = List((1,2,3), (2,3,-1), (1,1,1))
scala> l.map(_.toList).transpose.map(_.sum)
res0: List[Int] = List(4, 6, 3)
Notice that unlike solutions which rely on casts, this approach is type safe, and any type errors are detected at compile time rather than at runtime,
scala> val l = List((1, 2, 3), (2, "foo", -1), (1, 1, 1))
l: List[(Int, Any, Int)] = List((1,2,3), (2,foo,-1), (1,1,1))
scala> l.map(_.toList).transpose.map(_.sum)
<console>:15: error: could not find implicit value for parameter num: Numeric[Any]
l.map(_.toList).transpose.map(_.sum)
^
scala> val tuples = List( (1,2,3), (2,3,-3), (1,1,1))
tuples: List[(Int, Int, Int)] = List((1,2,3), (2,3,-3), (1,1,1))
scala> tuples.map(t => t.productIterator.toList.map(_.asInstanceOf[Int])).transpose.map(_.sum)
res0: List[Int] = List(4, 6, 1)
Type information is lost when calling productIterator on Tuple3 so you have to convert from Any back to an Int.
If the tuples are always going to contain the same type I would suggest using another collection such as List. The Tuple is better suited for disparate types. When you have the same types and don't lose the type information by using productIterator the solution is more elegant.
scala> val tuples = List(List(1,2,3), List(2,3,-3), List(1,1,1))
tuples: List[List[Int]] = List(List(1, 2, 3), List(2, 3, -3), List(1, 1, 1))
scala> tuples.transpose.map(_.sum)
res1: List[Int] = List(4, 6, 1)
scala> val list = List( (1,2,3), (2,3,-3), (1,1,1))
list: List[(Int, Int, Int)] = List((1,2,3), (2,3,-3), (1,1,1))
scala> list.foldRight( (0, 0, 0) ){ case ((a, b, c), (a1, b1, c1)) => (a + a1, b + b1, c + c1) }
res0: (Int, Int, Int) = (4,6,1)