I did "mkString" but still can not print list of strings. With input line:
9002194187,2644,54,100,3,4,2,5
I get the following output:
Line: 9002194187,2644,54,100,3,4,2,5
StrArr: 9002194187,2644,54,100,3,4,2,5
Lst: [Ljava.lang.String;#223d2e6c
Lst again: List([Ljava.lang.String;#223d2e6c)
Lst1: [Ljava.lang.String;#223d2e6c
Result: foo
From the code below:
def mkRecord(line: String) : String = {
val klass = "foo"
val strArr = line.split(",") // convert string to array of strings
println("Line: "+line)
println("StrArr: "+strArr.mkString(","))
val lst = List(strArr)
println("Lst: "+lst.mkString(" - "))
println("Lst again: "+lst)
val lst1 = lst.tail ++ List(klass) // attribute list except for the first one, plus new klass attribute
println("Lst1: "+lst.mkString(" , "))
val result = lst1.mkString(",") // attribute string
println("Result: "+ result)
return result
}
Please, help. I am at complete loss (
The constructor for List (actually, the apply method on the List companion object) takes parameters in the form of scala's "varargs" equivalent:
def apply[A](xs: A*): List[A] // some irrelevant details have been elided
In java, this would be written something like:
public static List<A> apply(A... args)
In scala this can be called using any Seq (or subclass), but using a special notation. The line you used:
val lst = List(strArr)
creates a List[Array[String]], with a single entry - the array strArr. To tell the compiler to turn the array into a varargs when passing it to the List apply method, add : _* on the end (the space is optional):
val lst = List(strArr: _*)
This change in your code will result in:
scala> mkRecord(chkStr)
Line: 9002194187,2644,54,100,3,4,2,5
StrArr: 9002194187,2644,54,100,3,4,2,5
Lst: 9002194187 - 2644 - 54 - 100 - 3 - 4 - 2 - 5
Lst again: List(9002194187, 2644, 54, 100, 3, 4, 2, 5)
Lst1: 9002194187 , 2644 , 54 , 100 , 3 , 4 , 2 , 5
Result: 2644,54,100,3,4,2,5,foo
res1: String = 2644,54,100,3,4,2,5,foo
You can turn any array into a list with its toList operator, avoiding this problem (the nature of which Shadowlands has explained). You can do string -> array -> list in one line:
line.split(',').toList
Using a collection's toList method is often going to be faster than extracting all the elements into a sequence and then converting that sequence into a list, not least because you'll be using a method optimised for the source collection. However, that's an optimisation which you can worry about after the priorities of success and clarity.
Related
I'm VERY new to Scala so apologies if anything sounds a bit basic. Working on a uni assignment and can't seem to find any similar questions around.
EDIT: The idea of this function is that I pass through a string of data and separate it up into individual elements. From what I can tell, things are being separated correctly with the lists holding the correct data types and correct information.
So I've created a function that returns a Map[String, List[(Int, String, Float)]]
The function does other things, but to keep it short, once I've build the list this is how I build the map and return it: -
val newMap = Map(name -> newList2.toList)
newMap
I can newMap.foreach to cycle through the map and find all of my elements. This works as expected: -
(Sample Key,List((3,PlaceName1,2.7)))
(Sample Key,List((2,PlaceName1,3.8)))
(Sample Key,List((1,PlaceName1,0.75)))
I am simply trying to call this function and save the map into a new variable. I have tried this two ways: -
val saveMap = separator("1:PlaceName1:0.75,2:PlaceName2:3.8,3:PlaceName3:2.7")
However when I try to cycle through this, I only get the first list element: -
(Sample Key,List((1,PlaceName1,0.75)))
I have also tried to use a mapBuffer in the format of: -
var mapBuffer: Map[String, List[(Int, String, Float)]] = Map()
mapBuffer = separator("1:PlaceName1:0.75,2:PlaceName2:3.8,3:PlaceName3:2.7")
Again, all I get as my return here is: -
mutated mapBuffer
(Sample Key,List((1,PlaceName1,0.75)))
Being new to Scala but with some experience in Java and C#, it's killing me how I'm returning a Map value, saving it into a map value that is built the same, and it's not coming through. Tried every iteration of maps and lists I could find and can't find anything on this from searching.
Is anyone able to offer any assistance?
EDIT:
Here is the whole code for the function and how I am attempting to call it.
def separator(data:String): Map[String, List[(Int, String, Float)]] = {
//Route name will be worked out later. For now, this is a sample.
val sampleRouteName = "First Route"
//Stage list will hold each list entry
val stageList = ListBuffer[(Int, String, Float)]()
//Stage list builder will put all the list entries together
var stageListBuilder = List[(Int, String, Float)]()
if (data.contains(",")) {
//Find index of first comma
val commaIndex = data.indexOf(",")
//Split everything before the comma off
val (firstEntry, restOfPhrase) = data.splitAt(commaIndex)
//Find the index of the colon in the first entry
val colonIndex = firstEntry.indexOf(":")
//Split everything before the colon off to just keep the number
val (number, restOfStage) = firstEntry.splitAt(colonIndex)
//Get rid of the first colon from the rest of the line
val restOfStage2 = restOfStage.replaceFirst(":", "")
//Find the index of the next colon
val colonIndex2 = restOfStage2.indexOf(":")
//Split everything before the colon off to just keep the stage name
val (stageName, restOfStage3) = restOfStage2.splitAt(colonIndex2)
//Get rid of the colon leaving just the stage length
val stageLength = restOfStage3.replaceFirst(":", "")
//Put all of these together into a list line in the builder
stageListBuilder = List((number.toInt,stageName,stageLength.toFloat))
//Add the list line from the builder to the list as an element
stageListBuilder.foreach(line => stageList += line)
//Call recursive function and remove the comma from the start
separator(restOfPhrase.replaceFirst(",", ""))
}
else if (data.length != 0) {
//Find index of first colon
val colonIndex = data.indexOf(":")
//Split everything before the colon off to just keep the number
val (number, restOfStage) = data.splitAt(colonIndex)
//Get rid of the first colon from the rest of the line
val restOfStage2 = restOfStage.replaceFirst(":", "")
//Find the index of the next colon
val colonIndex2 = restOfStage2.indexOf(":")
//Split everything before the colon off to just keep the stage name
val (stageName, restOfStage3) = restOfStage2.splitAt(colonIndex2)
//Get rid of the colon leaving just the stage length
val stageLength = restOfStage3.replaceFirst(":", "")
//Put all of these together into a list line in the builder
stageListBuilder = List((number.toInt,stageName,stageLength.toFloat))
//Add the list line from the builder to the list as an element
stageListBuilder.foreach(line => stageList += line)
}
//This is a map that accurately contains the key (ie. GCU Route) and a list of the routes.
val routeMap = Map(sampleRouteName -> stageList.toList)
//To test that the list has all the elements (CURRENTLY WORKING)
routeMap.foreach(line => println("TEST - " + line))
//Return the map
routeMap
}
//val saveMap = separator("1:City Chambers:0.75,2:Velodrome:3.8,3:People's Palace:2.7")
//Create new map buffer
var mapBuffer: Map[String, List[(Int, String, Float)]] = Map()
//Call separator function
mapBuffer = separator("1:City Chambers:0.75,2:Velodrome:3.8,3:People's Palace:2.7")
//Test that each element is in the list (CURRENTLY NOT WORKING)
mapBuffer.foreach(line => println(line))
As you mentioned you are using same keys as a key of a Map. Let's imagine we have separator method like this:
val str = "1:PlaceName1:0.75,2:PlaceName2:3.8,3:PlaceName3:2.7"
def separator(str: String): List[(String, (Int, String, Float))] =
str.split(",").map(_.split(":")).toList.map(_.toList).collect {
case ii :: s :: d :: Nil =>
"Sample Key" -> (ii.toInt, s, d.toFloat)
}
separator(str).foearch(println)
// result:
(Sample Key,(1,PlaceName1,0.75))
(Sample Key,(2,PlaceName2,3.8))
(Sample Key,(3,PlaceName3,2.7))
if we convert our result to a Map we lose some elements with same key in this case Sample Key:
def separator1(str: String): Map[String, List[(Int, String, Float)]] =
str.split(",").map(_.split(":")).toList.map(_.toList).collect {
case ii :: s :: d :: Nil =>
"Sample Key" -> List((ii.toInt, s, d.toFloat))
}.toMap
separator1(str).foreach(println)
// result:
// (Sample Key,List((3,PlaceName3,2.7)))
So we cannot have the same keys as KEY of a Map. The KEYs of a Map should be a unique.
case class Aname(a: Int, b: String, c: Float)
def separator2(str: String): Map[String, List[Aname]] =
str.split(",").map(_.split(":")).toList.map(_.toList).collect {
case ii :: s :: d :: Nil =>
Aname(ii.toInt, s, d.toFloat)
}.groupBy(_.b)
separator2(str).foreach(println)
// result:
// (PlaceName3,List(Aname(3,PlaceName3,2.7)))
// (PlaceName2,List(Aname(2,PlaceName2,3.8)))
// (PlaceName1,List(Aname(1,PlaceName1,0.75)))
You can play with it here
I am facing a very weird problem while getting elements of a list
Below is the piece of code where I am passing arguments as "bc" and "mn"
val list1 = List("abc", "def", "mnp")
val list2 = List(args(0), args(1))
val header1=list1.filter(x => list2.exists(y => x.contains(y)))
println(header1)
Output-List("abc","mnp")
I am trying to do it in a different way (by passing the same arguments)but getting an empty List
val list1 = List("abc", "def", "mnp")
//val list2 = List(args(0), args(1))
val ipList1= new ListBuffer[Any]
for(i <- 0 to 1){
ipList1 +=args(i)
}
val list2=ipList1.toList
println(list2)
val header1=list1.filter(x => list2.exists(y => x.contains(y)))
println(header1)
Output-List(bc, mn)
List()-->This is the empty List I am getting
Can Someone please tell where I am doing it wrong and How to make it right?
The problem is that x.contains(y) does not mean what you think it means. String has a contains method that checks whether another String is a substring of this String. But in your code y doesn't have type String, but type Any. So the contains method of String isn't called. It's the contains method of WrappedString which treats the String x as though it's a Seq[Char]. That method doesn't check whether any substring is equal to y but whether any character is equal to y.
The solution, obviously, is to use a ListBuffer[String].
The problem is that you are using a ListBuffer[Any] thus the elements lost their type information from String to Any and apparently that changes the semantics of the code.
You may either do this:
val ipList1 = new ListBuffer[String]
for (i <- 0 to 1) {
ipList1 += args(i).toString
}
val list2 = ipList1.toList
Or even better just:
val list2 = args.slice(0, 2).toList
scala> val a = jsonMap.get("L2_ID")
a: Option[Any] = Some(List(24493, 22774, 23609, 20517, 22829, 23646, 22779, 23578, 22765, 23657))
I want to fetch the first element of list i.e 24493. So, tried below code:
scala> var b = a.map(_.toString)
b: Option[String] = Some(List(24493, 22774, 23609, 20517, 22829, 23646, 22779, 23578, 22765, 23657))
scala>
scala> var c = b.map(_.split(",")).toList.flatten
c: List[String] = List(List(24493, " 22774", " 23609", " 20517", " 22829", " 23646", " 22779", " 23578", " 22765", " 23657)")
scala> c(0)
res34: String = List(24493
This is not returning as expected.
I suggest you use pattern matching.
To be defensive, i also added a Try to protect against the case of your json not being a List of numbers.
Code below returns an Option[Int] and you can call .getOrElse(0) on it - or some other default value, if you like.
import scala.util.Try
val first = a match {
case Some(h :: _) => Try(h.toString.toInt).toOption
case _ => None
}
So, you have an Option, and List inside of it. Then scala> var b = a.map(_.toString) converts the contents of the Option (a List) into a String. That's not what you want.
Look at the types of the results of your transformations, they are there to provide pretty good hints for you. b: Option[String], for example, tells you that you have lost the list ...
a.map(_.map(_.toString))
has the type Option[List[String]] on the other hand: you have converted every element of the list to a string.
If you are just looking for the first element, there is no need to convert all of them though. Something like this will do:
a
.flatMap(_.headOption) // Option[Int], containing first element or None if list was empty or id a was None
.map(_.toString) // convert Int inside of Option (if any) to String
.getOrElse("") // get the contents of the Option, or empty string if it was None
If you are certain that it's a Some, and that the list is non-empty, then you can unwrap the option and get the List[Int] using .get. Then you can access the first element of the list using .head:
val x: Option[List[Int]] = ???
x.get.head
If you are not in the REPL, and if you aren't sure whether it's a Some or None, and whether the List has any elements, then use
x.flatMap(_.headOption).getOrElse(yourDefaultValueEg0)
"Stringly-typed" programming is certainly not necessary in a language with such a powerful type system, so converting everything to string and splitting by commas was a seriously flawed approach.
Let's say that I have a Map of strings -> List of Integers. I would like to create a function which takes in as a parameter a List of strings and returns all the integers correlating to all the string in that list. I.e. if the Map X contains the following mappings:
database = [("Bob",[1,2,3]),("John",[1,5,6]),("Trevor",[4,5,7])]
If this function takes in ["Bob","John"] as the list of names, it should return,
[1,2,3,5,6]
Since Bob correlates to 1,2,3 and John correlates to 1,5,6 (same entries for both names aren't duplicated). I also would like to not introduce a mutable variable if I don't have to, thus leading me to believe a for comprehension that yields this list of number values would be the best way to achieve this, but I'm not sure how.
If you want to use a for-comprehension you can so this:
val result = for {
key <- keys
nums <- map.get(key).toSeq
num <- nums
} yield num
result.distinct
Explanation:
for each key in the list try to get an entry and convert it to a Seq (necessary because flatMap expects a Seq in this case) and add every number in the list to the result. If the key is not present in the map the collection will be empty and therefore not yield any results. At the end call distinct to remove the duplicates.
val myMap = Map("Bob" -> List(1,2,3), "John" -> List(1,5,6), "Trevor" -> List(4,5,7))
val names = List("Bob", "John")
You can add default value to Map using method withDefaultValue:
val mapWithDefaul = myMap withDefaultValue Nil
Then you could use Map as function in flatMap:
names.flatMap(mapWithDefaul).distinct
// List(1, 2, 3, 5, 6)
Let
val db = Map("Bob" -> List(1,2,3), "John" -> List(1,5,6), "Trevor" -> List(4,5,7))
val names = List("Bob", "John")
Then a similar approach to #senia's using flatMap,
implicit class mapCorr[A,B](val db: Map[A,List[B]]) extends AnyVal {
def corr(keys: List[A]): List[B] = {
keys.flatMap{ k => db get k }.flatten.distinct
}
}
and
scala> db.corr(keys)
res0: List[Int] = List(1, 2, 3, 5, 6)
Here we allow for key lists of type A and maps from type A to type List[B] .
val myset = Set("Bob","John")
val database = Map(("Bob"->List(1,2,3)),("John"->List(1,5,6)),("Trevor"->List(4,5,7)))
val ids = database.filter(m => myset.contains(m._1)).map(_._2).flatten.toList.distinct
outputs:
ids: List[Int] = List(1, 2, 3, 5, 6)
Something like:
val result = database.filter(elem => list.contains(elem._1)).foldLeft(List())((res,elem) => res ++ elem._2)
where list is the input list of names.
This is the problem that I did solve, however being a total imperative Scala noob, I feel I found something totally not elegant. Any ideas of improvement appreciated.
val l1 = 4 :: 1 :: 2 :: 3 :: 4 :: Nil // original list
val insert = List(88,99) // list I want to insert on certain places
// method that finds all indexes of a particular element in a particular list
def indexesOf(element:Any, inList:List[Any]) = {
var indexes = List[Int]()
for(i <- 0 until inList.length) {
if(inList(i) == element) indexes = indexes :+ i
}
indexes
}
var indexes = indexesOf(4, l1) // get indexes where 4 appears in the original list
println(indexes)
var result = List[Any]()
// iterate through indexes and insert in front
for(i <- 0 until indexes.length) {
var prev = if(i == 0) 0 else indexes(i-1)
result = result ::: l1.slice(prev, indexes(i)) ::: insert
}
result = result ::: l1.drop(indexes.last) // append the last bit from original list
println(result)
I was thinking more elegant solution would be achievable with something like this, but that's just pure speculation.
var final:List[Any] = (0 /: indexes) {(final, i) => final ::: ins ::: l1.slice(i, indexes(i))
def insert[A](xs: List[A], extra: List[A])(p: A => Boolean) = {
xs.map(x => if (p(x)) extra ::: List(x) else List(x)).flatten
}
scala> insert(List(4,1,2,3,4),List(88,99)){_ == 4}
res3: List[Int] = List(88, 99, 4, 1, 2, 3, 88, 99, 4)
Edit: explanation added.
Our goal here is to insert a list (called extra) in front of selected elements in another list (here called xs--commonly used for lists, as if one thing is x then lots of them must be the plural xs). We want this to work on any type of list we might have, so we annotate it with the generic type [A].
Which elements are candidates for insertion? When writing the function, we don't know, so we provide a function that says true or false for each element (p: A => Boolean).
Now, for each element in the list x, we check--should we make the insertion (i.e. is p(x) true)? If yes, we just build it: extra ::: List(x) is just the elements of extra followed by the single item x. (It might be better to write this as extra :+ x--add the single item at the end.) If no, we have only the single item, but we make it List(x) instead of just x because we want everything to have the same type. So now, if we have something like
4 1 2 3 4
and our condition is that we insert 5 6 before 4, we generate
List(5 6 4) List(1) List(2) List(3) List(5 6 4)
This is exactly what we want, except we have a list of lists. To get rid of the inner lists and flatten everything into a single list, we just call flatten.
The flatten trick is cute, I wouldn't have thought of using map here myself. From my perspective this problem is a typical application for a fold, as you want go through the list and "collect" something (the result list). As we don't want our result list backwards, foldRight (a.k.a. :\) is here the right version:
def insert[A](xs: List[A], extra: List[A])(p: A => Boolean) =
xs.foldRight(List[A]())((x,xs) => if (p(x)) extra ::: (x :: xs) else x :: xs)
Here's another possibility, using Seq#patch to handle the actual inserts. You need to foldRight so that later indices are handled first (inserts modify the indices of all elements after the insert, so it would be tricky otherwise).
def insert[A](xs: Seq[A], ys: Seq[A])(pred: A => Boolean) = {
val positions = xs.zipWithIndex filter(x => pred(x._1)) map(_._2)
positions.foldRight(xs) { (pos, xs) => xs patch (pos, ys, 0) }
}