i wanna have a result-Sequence with a Triple of (String, Int, Int) like this:
var all_info: Seq[(String, Int, Int)] = null
now that i try adding elements to my Seq as followed:
if (all_info == null) {
all_info = Seq((name, id, count))
} else {
all_info :+ (name, id, count)
}
and print them
Console.println(all_info.mkString)
Unfortunately, the printed result is only the first triple that is added by the if-clause and basically intializes a new Seq, since it's been just "null" before.
All following triples which are supposed to be added to the Seq in the else-clause are not.
I also tried different methods like "++" which won't work either ("too many arguments")
Can't really figure out what I'm doing wrong here.
Thanks for any help in advance!
Greetings.
First of all instead of using nulls you would be better using an empty collection. Next use :+= so the result of :+ would not be thrown away — :+ produces a new collection as the result instead of modifying the existing one. The final code would look like
var all_info: Seq[(String, Int, Int)] = Seq.empty
all_info :+= (name, id, count)
As you can see, now you don't need ifs and code should work fine.
Method :+ creates new collection and leaves your original collection untouched.
You should use method +=. If there is no method += in all_info compiler will treat all_info += (name, id, count) as all_info = all_info + (name, id, count).
On the contrary, if you'll change the type of all_info to some mutable collection, you'll get method += in it, so your code will work as expected: method += on mutable collections changes target collection.
Note that there is no method :+= in mutable collections, so you'll get all_info = all_info :+ (name, id, count) even for mutable collection.
Related
I have a List of “rules” tuples as follows:
val rules = List[(A, List[B])], where A and B are two separate case-classes
For the purposes of my use-case, I need to convert this to an Option[(A, List[B])]. The A case class contains a property id which is of type Option[String], based on which the tuple is returned.
I have written a function def findRule(entryId: Option[String]), from which I intend to return the tuple (A, List[B]) whose A.id = entryId as an Option. So far, I have written the following snippet of code:
def findRule(entryId: Option[String]) = {
for {
ruleId <- rules.flatMap(_._1.id) // ruleId is a String
id <- entryId // entryId is an Option[String]
} yield {
rules.find(_ => ruleId.equalsIgnoreCase(id)) // returns List[Option[(A, List[B])]
}
}
This snippet returns a List[Option[(A, List[B])] but I can’t figure out how to retrieve just the Option from it. Using .head() isn’t an option, since the rules list may be empty. Please help as I am new to Scala.
Example (the real examples are too large to post here, so please consider this representative example):
val rules = [(A = {id=1, ….}, B = [{B1}, {B2}, {B3}, …]), (A={id=2, ….}, B = [{B10}, {B11}, {B12}, …]), …. ] (B is not really important here, I need to find the tuple based on the id element of case-class A)
Now, suppose entryId = Some(1)
After the findRule() function, this would currently look like:
[Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …]))]
I want to return:
Some((A = {id=1, …}, B = [{B1}, {B2}, {B3}, …])) , ie, the Option within the List returned (currently) from findRule()
From your question, it sounds like you're trying to pick a single item from your list based on some conditional, which means you'll probably want to start with rules.find. The problem then becomes how to express the predicate function that you pass to find.
From my read of your question, the conditional is
The id on the A part of the tuple needs to match the entryId that was passed in elsewhere
def findRule(entryId: Option[String]) =
rules.find { case (a, listOfB) => entryId.contains(a.id) }
The case expression is just nice syntax for dealing with the tuple. I could have also done
rules.find { tup => entryId.contains(tup._1.id) }
The contains method on Option roughly does "if I'm a Some, see if my value equals the argument; if I'm a None, just return false and ignore the argument". It works as a comparison between the Option[String] you have for entryId and the plain String you have for A's id.
Your first attempt didn't work because rules.flatMap got you a List, and using that after the <- in the for-comprehension means another flatMap, which keeps things as a List.
A variant that I personally prefer is
def findRule(entryId: Option[String]): Option[(A, List[B])] = {
entryId.flatMap { id =>
rules.find { case (a, _) => a.id == id }
}
}
In the case where entryId is None, it just immediately returns None. If you start with rules.find, then it will iterate through all of the rules and check each one even when entryId is None. It's unlikely to make any observable performance difference if the list of rules is small, but I also find it more intuitive to understand.
The scala program is iterating through a list of words and appending the word with value if already found or adding key->word otherwise. It is expected to produce Map[] but producing List[Map[]] instead.
val hashmap:Map[List[(Char, Int)], List[String]]=Map()
for (word <- dictionary) yield {
val word_occ = wordOccurrences(word)
hashmap + (if (hashmap.contains(word_occ)) (word_occ -> (hashmap(word_occ) ++ List(word))) else (word_occ -> List(word)))
}
Note that in this case you probably want to build the Map in a single pass rather than modifying a mutable Map:
val hashmap:Map[List[(Char, Int)], List[String]]=
dictionary
.map(x => (wordOccurrences(x), x))
.groupBy(_._1)
.map { case (k, v) => k -> v.map(_._2) }
In Scala 2.13 you can replace the last two lines with
.groupMap(_._1)(_._2)
You can also use a view on the dictionary to avoid creating the intermediate list if performance is a significant issue.
A for comprehension with a single <- generator de-sugars to a map() call on the original collection. And, as you'll recall, map() can change the elements of a collection, but it won't change the collection type itself.
So if dictionary is a List then what you end up with will be a List. The yield specifies what is to be the next element in the resulting List.
In your case the code is creating a new single-element Map for each element in the dictionary. Probably not what you want. I'd suggest you try using foldLeft().
I'm new to scala and Gatling. I'm trying to transform the result of findAll into a sorted list and then return a String representation of the sorted list. I can't seem to do this with the following code:
http(requestTitle)
.post(serverUrl)
.body(ElFileBody(sendMessageFile))
.header("correlation-id", correlationId)
.check(status.is(200),
jsonPath("$.data.sendMessage.targetedRecipients").findAll.transform(recipients => {
println("recipients class: " + recipients.getClass)
var mutable = scala.collection.mutable.ListBuffer(recipients: _*)
var sortedRecipients = mutable.sortWith(_ < _)
println("users sorted "+ usersSorted)
usersSorted.mkString(",")
}).is(expectedMessageRecipients))
Recipients is of type scala.collection.immutable.Vector.
I thought I would be able to convert the immutable collection into a mutable collection using scala.collection.mutable.ListBuffer. Any help would be appreciated, thanks.
I don't think your problem is immutability, it's JSON parsing vs Gatling's .find and .findAll methods.
I'm going to make a guess that your response looks something like...
{"data":{"sendMessage":{"targetedRecipients":[1,4,2,3]}}}
in which case Gatling's .findAll method will return a vector (it always does if it finds something), but it will only have one element which will be "[1,4,2,3]" - ie: a string representing json data, so sorting the collection of a single element naturally achieves nothing. To get .findAll to behave like you seem to be expecting, you would need a response something like...
{"data":
{"sendMessage":
{"targetedRecipients":
[{"recipientId":1},
{"recipientId":4},
{"recipientId":2},
{"recipientId":3}]
}}}
which you could use .jsonPath("$..recipientId").findAllto turn into a Vector[String] of the Ids.
So assuming you are indeed just getting a single string representation of an array of values, you could use a straight transform to generate an array and sort (as you tried in your example)
Here's a working version
val data = """{"data":{"sendMessage":{"targetedRecipients":[1,4,2,3]}}}"""
def sortedArray : ScenarioBuilder = scenario("sorting an array")
.exec(http("test call")
.post("http://httpbin.org/anything")
.body(StringBody(data)).asJson
.check(
status.is(200),
jsonPath("$.json.data.sendMessage.targetedRecipients")
.find
.transform(_
.drop(1)
.dropRight(1)
.split(",")
.toVector
.sortWith(_<_)
)
.saveAs("received")
))
.exec(session => {
println(s"received: ${session("received").as[Vector[String]]}")
session
})
There is no reason to use mutable collection if all you want is to sort the result:
Vector(5,4,3,2,1).sortWith(_ < _).mkString(", ") // "1, 2, 3, 4, 5"
To use ListBuffer you have to copy all elements into newly allocated object, so it isn't even more optimal in any way. Same with vars - you can use vals as you don't update the reference
println(s"recipients class: ${recipients.getClass}")
val result = recipients.sortWith(_ < _).mkString(", ")
println(s"users sorted $result")
result
Given this function, which I can't modify:
def numbers(c: Char): Iterator[Int] =
if(Character.isDigit(c)) Iterator(Integer.parseInt(c.toString))
else Iterator.empty
// numbers: (c: Char)Iterator[Int]
And this input data:
val data = List('a','b','c','1','d','&','*','x','9')
// data: List[Char] = List(a, b, c, 1, d, &, *, x, 9)
How can I make this function lazy, such that data is only processed to the first occurrence of a number character?
def firstNumber(data: List[Char]) :Int = data.flatMap(numbers).take(1)
data.iterator.flatMap(numbers).take(1).toList
Don't use streams; you don't need the old data stored. Don't use views; they aren't being carefully maintained and are overkill anyway.
If you want an Int, you need some default behavior. Depending on what that is, you might choose
data.iterator.flatMap(numbers).take(1).headOption.getOrElse(0)
or something like
{
val ns = data.iterator.flatMap(numbers)
if (ns.hasNext) ns.next
else throw new NoSuchElementException("Number missing")
}
Just calling .toStream on your data should do it:
firstNumber(data.toStream)
One possibility would be to use Scala's collection views:
http://www.scala-lang.org/docu/files/collections-api/collections_42.html
Calling .view on a collection allows you to call functions like map, flatMap etc on the collection without generating intermediate results.
So in your case you could write:
data.view.flatMap(numbers).take(1).force
which would give a List[Int] with at most one element and only process data to the first number.
you could use Streams:
http://www.scala-lang.org/api/current/index.html#scala.collection.immutable.Stream
One way of stream creation:
1 #:: 2 #:: empty
The following code compiles as long as the return type of the recursive call is Any, but obviously I'm doing something wrong because it should not have to be Any.
case class Group(
id: Long = -1,
parentId: Long = -1,
name: String = "")
def makeTree(groupId: Long, groups: List[Group]) = {
def getAllChildren(gid: Long): Any = {
def children = for {
g <- groups; if g.parentId == gid
} yield g
if (children.isEmpty) List()
else {
children map { x =>
getAllChildren(x.id)
}
}
}
getAllChildren(groupId)
}
val groups = List(Group(1, 0, "A"),
Group(2, 1, "B"),
Group(3, 1, "C"),
Group(4, 2, "D"))
makeTree(1, groups)
//Results in: Any = List(List(List()), List())
}
If I change the signature of getAllChildren to:
def getAllChildren(gid: Long): List[Group]
then I get an error:
type mismatch; found : List[List[Group]] required: List[Group]
What am I doing wrong here.
Not really speaking scala, but it looks like, for some id, you collect the groups with that id, and then replace each group with a list of it's children, and so on.
Specifically, here:
if (children.isEmpty) List()
else {
children map { x =>
getAllChildren(x.id)
}
}
Indeed, here is the root of your error: Your algorithm allows for infinite recursion, and each recursion adds another List[...] around your return type. But you can't have a type with dynamic depth.
For example, if you try to fix this by giving type List[List[Group]], it will complain that it found List[List[List[Group]]], and so on, until you give up.
This is the way typecheckers tell us that we're about to program a potentially infinite recursion. You may have assumed the invariant that your hierarchy can't involve loops, yet this is not reflected in the types. In fact, it is not hard to construct an example where two groups are each others parents. In that case, your program will produce an ever deeper nesting list until it terminates due to lack of memory.
Note that you can't fix your code simply by using flatMap instead of map: the reason being that getAllChildren never ever produces a list with Group elements. It either returns an empty list, or, a flattened list of empty lists, that is, an empty list. Hence, if it should return at all, it would return an empty list in the flatmap version.
You need to change the call to children map ... for children flatMap ..., otherwise you're not returning a List[Group] but potentially a List[List[.....]] like #Ingo suggests. flatMap will map each element to a List and then flatten all lists to create just one List[Group] containing all children.
EDIT: As #Ingo points out, even if flatMap would solve the typing problem, your function still doesn't work, since you always return an empty List. You should go for
children flatMap { x => x :: getAllChildren(x.id, acc) }
to prepend the child to the child's children.
Your code will work if you flatten the list returned on children map like this:
def makeTree(groupId: Long, groups: List[Group]) = {
def getAllChildren(gid: Long): List[Group] = {
def children = for {
g <- groups; if g.parentId == gid
} yield g
if (children.isEmpty) List()
else {
val listList = children map { x =>
x :: getAllChildren(x.id)
}
listList.flatten
}
}
getAllChildren(groupId)
}
The reason this is happening is that you are taking a List[Group] and mapping it over a function that also returns a List[Group], thus resulting in a List[List[Group]]. Simply flattening that List will produce the desired result.