I have a IndexedSeq[Map[String, String]] and I would like to extract value where key is "text" and I would like to put it in a val text:IndexedSeq[String]. I have written the following piece but it doesn't work:
val text:IndexedSeq[String] = _
for(j <- 0 to indSeq.length-1){
text(j) = indSeq(j).get("text")
}
You are probably seeing a compiler error because indSeq(j).get("text") returns an Option[String], not a String.
If you just want to get all the values for the key "text" in a sequence, use:
val text = indSeq flatMap (_ get "text")
If it's important that the indices of both sequences line up, then you will want to substitute a default value in case the key "text" is not present:
val text = indSeq map (_.getOrElse("text", "default"))
I think the best approach is with a for-comprehension with a guard to get rid of the maps that don't have the "text" element:
val result = for {
i <- 0 until indexSeq.length
map = indexSeq(i)
if map isDefinedAt ("text")
} yield { (i, map("text")) }
val seq = result.toIndexedSeq
That way you keep the original indexes with the map. It also avoids holding any var value, which is always a perk
Since you were trying to use a for-comprehension originally, you might also be interested in doing it this way:
val text = (for { m <- indSeq } yield m get "text") flatten
EDIT
or if you want a default value you could do:
val text = for { m <- indSeq } yield m getOrElse("text", "default")
Related
I have a below list
val list = listOf("o=one", "t=two", "t=two", "f=four", "o=one", "t=two", "s=seven", "o=one")
I wanna split it into list of the list contains [["o=one", "t=two", "t=two", "f=four"],["o=one", "t=two", "s=seven"],["o=one"]]
Actually I want to group list by "o=" delimiter and the list will always have at least one "0=" value. How could I achieve this in Kotlin without creating mutable var keyword because of my code should be in the functional style?
I have tried with group() and groupBy{} methods but couldn't get the expected result.
This might not cover all edge cases, and there might be better ways of doing it, but given that your requirements are not fully clear and extremely contrived, this should get you started either way. From here on out you can polish it yourself.
// Identifies indices of all "o=" elements in the list.
val splitAt = list
.withIndex()
.filter { it.value.startsWith( "o=" ) }
.map { it.index }
// Create sublists.
val split = splitAt
.windowed( 2, step = 1, partialWindows = true )
.map {
list.subList(
it[0],
if (it.count() == 1) it[0] + 1 else it[1]
)
}
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
What would be an idiomatic way to create a mutable list of a given length n with repeating elements of value v (e.g listOf(4,4,4,4,4)) as an expression.
I'm doing val list = listOf((0..n-1)).flatten().map{v} but it can only create an immutable list.
Use:
val list = MutableList(n) {index -> v}
or, since index is unused, you could omit it:
val list = MutableList(n) { v }
another way may be:
val list = generateSequence { v }.take(4).toMutableList()
This style is compatible with both MutableList and (Read Only) List
If you want different objects you can use repeat.
For example,
val list = mutableListOf<String>().apply {
repeat(2){ this.add(element = "YourObject($it)") }
}
Replace String with your object. Replace 2 with number of elements you want.
You're able to use the ranges for this purpose, e.g.
val listOfFour = (1..10).map { 4 }
or
val objectList = (1..10).map {
YourClass(
arg1 = "someValue",
arg2 = it
)
}
if you need you can use it (index) for your needs as well.
I've struggled to find a way to stay true to functional style in for expressions when I need to collect multiple parameters of an object into a List.
As an example, say I have a Notification object, which has both a fromId (the user id the notification is from) and an objectOwnerId (the id of the user who created the original object). These can differ in facebook style notifications ("X also commented on Y's post").
I can collect the userIds with a for expression like so
val userIds = for { notification <- notifications } yield notification.fromId
however say I want to collect both the fromIds and the objectOwnerIds into a single list, is there any way to do this in a single for expression without the user of vars?
I've done something like this in the past:
var ids = List()
for {
notification <- notifications
ids = ids ++ List(notification.fromId, notification.objectOwnerId)
}
ids = ids.distinct
but it feels like there must be a better way. The use of a var, and the need to call distinct after I complete the collection are both ugly. I could avoid the distinct with some conditionals, but I'm trying to learn the proper functional methods to do things.
Thanks in advance for any help!
For such cases, there is foldLeft:
(notifications foldLeft Set.empty[Id]) { (set, notification) =>
set ++ Seq(notification.fromId, notification.ownerId)
}
or in short form:
(Set.empty[Id] /: notifications) { (set, notification) =>
set ++ Seq(notification.fromId, notification.ownerId)
}
A set doesn't hold duplicates. After the fold you can convert the set to another collection if you want.
val userIds = for {
notification <- notifications
id <- List(notification.fromId, notification.objectOwnerId)
} yield id
Apply distinct afterwards if required. If the id can only be duplicated on a single notification, you can apply distinct on the second generator instead.
Sure, instead of just yielding the fromId, yield a tuple
val idPairs:List[(String, String)] = for(notification <- notifications) yield(notification.fromId, notification.objectOwnerId)
Well, here is my answer to the following:
How to map from [Y(x1, x2), Y(x3, x4)] to [x1,x2,x3,x4]?
Use flatMap (see Collection.Traversable, but note it's actually first defined higher up).
case class Y(a: Int, b: Int)
var in = List(Y(1,2), Y(3,4))
var out = in.flatMap(x => List(x.a, x.b))
> defined class Y
> in: List[Y] = List(Y(1,2), Y(3,4))
> out: List[Int] = List(1, 2, 3, 4)
Also, since for..yield is filter, map and flatMap in one (but also see "sugar for flatMap?" that points out that this isn't as efficient as it could be: there is an extra map):
var out = for { i <- in; x <- Seq(i.a, i.b) } yield x
I would likely pick one of the other answers, however, as this does not directly address the final problem being solved.
Happy coding.
You can also use Stream to transform the pairs into a stream of individual items:
def toStream(xs: Iterable[Y]): Stream[Int] = {
xs match {
case Y(a, b) :: t => a #:: b #:: toStream(t)
case _ => Stream.empty
}
}
But like pst said, this doesn't solve your final problem of getting the distinct values, but once you have the stream it's trivial:
val result = toStream(ys).toList.removeDuplicates
Or a slight modification to the earlier suggestions to use flatten - add a function that turns a Y into a List:
def yToList(y: Y) = List(y.a, y.b)
Then you can do:
val ys = List(Y(1, 2), Y(3, 4))
(ys map yToList flatten).removeDuplicates
I agree with Dave's solution but another approach is to fold over the list, producing your map of id to User object as you go. The function to apply In the fold will query the db for both users and add them to the map being accumulated.
What about simple map? AFAIK for yield gets converted to series of flatMap and map anyway. Your problem could be solved simply as follows:
notifications.map(n => (n.fromId, n.objectOwnerId)).distinct