I have this code. PartnerReader.fetchParters() returns a List[Partner]. Partners have a country attribute that is a String, and a dates attribute that is a list of Calendar objects. I group all Partners by their country. I expect partnersByCountry is a Map[String, List[Partner]].
I then want to associate countries with all the dates from all their Partners. getAllDatesForPartners() returns a List[Calendar] resulting (hopefully) in a Map[String, List[Calendar]]. I attempt to do this with the assignment to allDatesForCountry, but this fails on the call to map with the error Cannot resolve overloaded method 'map'.
Why does this code not work and what's the correct way to do the transformation?
val partnersByCountry = PartnerReader.fetchPartners()
.groupBy(_.country)
val allDatesForCountry = partnersByCountry
.map((country: String, partners: List[Partner]) => {
country -> getAllDatesForPartners(partners)
})
def getAllDatesForPartners(partners: List[Partner]): List[Calendar] = ???
When you .map() over a Map[?,?] you get a sequence of tuples. To break each tuple into its constituent parts you need pattern matching.
.map{case (country: String, partners: List[Partner]) =>
country -> getAllDatesForPartners(partners)
}
Which is the long (documented) way to write...
.map(tup => (tup._1, getAllDatesForPartners(tup._2)))
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.
Given this case class:
case class Categories(fruit: String, amount: Double, mappedTo: String)
I have a list containing the following:
List(
Categories("Others",22.38394964594807,"Others"),
Categories("Others",77.6160503540519,"Others")
)
I want to combine two elements in the list by summing up their amount if they are in the same category, so that the end result in this case would be:
List(Categories("Others",99.99999999999997,"Others"))
How can I do that?
Since groupMapReduce was introduced in Scala 2.13, I'll try to provide another approch to Martinjn's great answer.
Assuming we have:
case class Categories(Fruit: String, amount: Double, mappedTo: String)
val categories = List(
Categories("Apple",22.38394964594807,"Others"),
Categories("Apple",77.6160503540519,"Others")
)
If you want to aggregate by both mappedTo and Fruit
val result = categories.groupBy(c => (c.Fruit, c.mappedTo)).map {
case ((fruit, mappedTo), categories) => Categories(fruit, categories.map(_.amount).sum, mappedTo)
}
Code run can be found at Scastie.
If you want to aggregate only by mappedTo, and choose a random Fruit, you can do:
val result = categories.groupBy(c => c.mappedTo).map {
case (mappedTo, categories) => Categories(categories.head.Fruit, categories.map(_.amount).sum, mappedTo)
}
Code run can be found at Scastie
You want to group your list entries by category, and reduce them to a single value. There is groupMapReduce for that, which groups entries, and then maps the group (you don't need this) and reduces the group to a single value.
given
case class Category(category: String, amount: Double)
if you have a val myList: List[Category], then you want to group on Category#category, and reduce them by merging the members, summing up the amount.
that gives
myList.groupMapReduce(_.category) //group
(identity) //map. We don't need to map, so we use the identity mapping
{
case (Category(name, amount1), Category(_, amount2)) =>
Category(name, amount1 + amount2) }
} //reduce, combine each elements by taking the name, and summing the amojunts
In theory just a groupReduce would have been enough, but that doesn't exist, so we're stuck with the identity here.
For example I have a directive:
val sourceTypeDirective: Directive1[DocumentSource] = {
parameters('docsource.as[String]) flatMap {
case docSource if sourceAcceptedType(docSource) => provide(sourceValueOf(docSource))
case _ => reject
}
}
I want to get docsource even if it will be written as DOCSOURCE or DocSource, how can I make that?
No Direct Solution
I don't think this is possible with the parameters Directive directly. The 'docsource Symbol is being converted into a NameReceptacle via an implicit conversion method in ParameterDirectives. There is only 1 String member variable in NameReceptacle: name. This prevents any sort of regular expression matching or converting the Symbol into multiple Strings to match all possible capitalization patterns...
Indirect Solution
The parameterMap Directive can be used to get a Map[String, String] where you can then operate on the keys:
val sourceTypeDirective : Directive1[DocumentSource] =
parameterMap {
(_ : Map[String, String])
.view
.map(tup => (tup._1.toLowerCase, tup._2))
.toMap
.get("docsource")
.filter(sourceAcceptedType)
.map(docSource => provide(sourceValueOf(docSource)))
.getOrElse(reject)
}
Warning
The query string key "field" is distinct from the key "FIELD". Those are technically 2 different keys and should correspond to 2 different values. But any solution to the question would result in a key collision for those two fields.
My intention is simple: to get comma seperated values of emaild from list of User objects.
I have done this in Java with a for loop and if else conditions.
Now i want to do it in Scala, so i tried this.
case class User(name: String, email: String)
val userList = List(User("aaa", "aaa#aaa.com"),
User("bbb", "bbb#bbb.com"),
User("ccc", "ccc#ccc.com"))
now
val mailIds = userList.foldLeft(""){(a: String, b: User) => ( b.email+","+a) }
gives
ccc#ccc.com,bbb#bbb.com,aaa#aaa.com,
(note the comma at the end.)
and
val mailids = userList.map(x => x.email).reduceLeft(_+","+_)
gives
aaa#aaa.com,bbb#bbb.com,ccc#ccc.com
i tried using only reduceLeft like this
val emailids = userList.reduceLeft((emails: String, user: User) => emails+", "+user.email)
but it throws compilation error
type mismatch; found : (String, User) => java.lang.String required: (java.io.Serializable, User) => java.io.Serializable
so, is there a better way of using reduceLeft without map in the above case ?
No, reduceLeft is a foldLeft where the accumulator is the first element of the collection. In your case this first element is a User and you have to pass an argument of the same type for the next step of the iteration. That is why you get a type missmatch.
You can do the map within a lazy collection and reduce this like the following:
val mailIds = userList.view map (_.email) reduceLeft (_ + "," + _)
This will give you aaa#aaa.com,bbb#bbb.com,ccc#ccc.com without creating a second collection. Jessie Eichar wrote a verry good tutorial for that.
EDIT
A way without a lazy collection would be deleting the last comma by:
val rawMailIds = userList.foldLeft("")((acc, elem) => acc + elem.email + ",")
val mailIds = rawMailIds.substring(0, rawMailIds.length - 1)
Why don't you stick with the combination of map and reduce? In any case, your issue is that your reduceLeft only version needs to have a string as its initial value.
Suppose I have list countries of type List[String] and map capitals of type Map[String, String]. Now I would like to write a functionpairs(countries:List[String], capitals:Map[String, String]):Seq[(String, String)]to return a sequence of pairs (country, capital) and print an error if the capital for some country is not found. What is the best way to do that?
To start with, your Map[String,String] is already a Seq[(String,String)], you can formalise this a bit by calling toSeq if you wish:
val xs = Map("UK" -> "London", "France" -> "Paris")
xs.toSeq
// yields a Seq[(String, String)]
So the problem then boils down to finding countries that aren't in the map. You have two ways of getting a collection of those countries that are represented.
The keys method will return an Iterator[String], whilst keySet will return a Set[String]. Let's favour the latter:
val countriesWithCapitals = xs.keySet
val allCountries = List("France", "UK", "Italy")
val countriesWithoutCapitals = allCountries.toSet -- countriesWithCapitals
//yields Set("Italy")
Convert that into an error in whatever way you see fit.
countries.map(x=>(x, capitals(x)))