I have a class that looks like this:
case class Person(id : String, name : String, refId : String) {}
And I have a list of Person.
I want to have a map with
key = refId
value = List[Person] that have the same refId (duplicate keys)
What I did:
val persons = getPersons() // get the List from somewhere
val refMap = new mutable.HashMap[String,Seq[Person]]()
for (person<- persons){
refMap.put(person.refId,refMap.getOrElse(person.refId,new ArrayBuffer[Person]) :+ person)
}
That was my first idea and it work, but I want something more Scala-like or something that looks better. Do you have an idea?
I also tried what is written here: Convert List of tuple to map (and deal with duplicate key ?)
But they use Tuple and I couldn't get this work either.
I also tried it to map my list to tuples first but
1. I don't want to iterate 2 times over the List when it's not necessary (1 time to create tuples, 1 time to create the map.
2. I tried but I failed with tuples too.
Any help for a better code would be nice.
Try groupBy:
getPersons().groupBy(_.refId): Map[String, List[Person]]
Related
I have a list that I would like to group into group and then for each group get the max value. For example, a list of actions of user, get the last action per user.
case class UserActions(userId: String, actionId: String, actionTime: java.sql.Timestamp) extends Ordered[UserActions] {
def compare(that: UserActions) = this.actionTime.before(that.actionTime).compareTo(true)
}
val actions = List(UserActions("1","1",new java.sql.Timestamp(0L)),UserActions("1","1",new java.sql.Timestamp(1L)))
When I try the following groupBy:
actions.groupBy(_.userId)
I receive a Map
scala.collection.immutable.Map[String,List[UserActions]] = Map(1 -> List(UserActions(1,1,1970-01-01 00:00:00.0), UserActions(1,1,1970-01-01 00:00:00.001))
Which is fine, but when I try to add the maxBy I get an error:
actions.groupBy(_.userId).maxBy(_._2)
<console>:13: error: diverging implicit expansion for type
Ordering[List[UserActions]]
starting with method $conforms in object Predef
actions.groupBy(_.userId).maxBy(_._2)
What should I change?
Thanks
Nir
So you have a Map of String (userId) -> List[UserActions] and you want each list reduced to just its max element?
actions.groupBy(_.userId).mapValues(_.max)
//res0: Map[String,UserActions] = Map(1 -> UserActions(1,1,1969-12-31 16:00:00.0))
You don't need maxBy() because you've already added the information needed to order/compare different UserActions elements.
Likewise if you just want the max from the original list.
actions.max
You'd use maxBy() if you wanted the maximum as measured by some other parameter.
actions.maxBy(_.actionId.length)
Your compare method should be:
def compare(that: UserActions) = this.actionTime.compareTo(that.actionTime)
Then do actions.groupBy(_.userId).mapValues(_.max) as #jwvh shows.
You want to group values by using userId. You can do it by using groupBy().
actions.groupBy(_.userId)
Then you can take only values from key value pairs.
actions.groupBy(_.userId).values
You have used maxBy(_._2). So, I think You want to get max by comparing second value. You can do it by using map().
actions.groupBy(_.userId).values.map(_.maxBy(_._2))
I have a class called Group
class Group(id: Int, name: String, category: String) {
}
I am trying to convert Array[Group] to Map[String, Seq[Group]] with category: String as key. I want to create an empty Seq[Group] and add Group if the key does not exist otherwise update the Seq[Group]. I am not sure how to update the Seq if the key already exists.
groupBy will do it all.
arrayOfGroups.groupBy(_.category)
Just the result will be a Map[String, Array[Group]] (because the original container was an array). Array is not exactly a Seq, so if you want one, you may do
arraysOfGroup.groupBy(_.category).mapValues(_.toSeq)
You may replace the toSeq by any more precise transformation.
It would also be possible to do arrayOfGroup.toSeq.groupBy(_.category)
I'm trying to store a List of integers here is what I am doing:
MODEL
case class Score(
scoresPerTime: List[Int]
)
object Scores extends Table[Score]("SCORES"){
def scorePerTime = column[List[Int]]("SCORE_PER_TIME")
//...more code
}
Controller
val form = Form(
Map(
"scoresPerTime" -> list(number)
)(Score.apply)(Score.unapply)
)
I get one compilation error:
.... could not find implicit value for parameter tm: scala.slick.lifted.TypeMapper[List[Int]][error] def scorePerTime = column[List[Int]]("SCORE_PER_TIME")
How can I fix this to enter a list? or maybe try another option like a tuple, enum...
You can do that by defining a type mapper from let's say List[Int] to String and vice-versa.
One possibility:
implicit def date2dateTime = MappedTypeMapper.base[List[Int], String](
list => list mkString ",",
str => (str split "," map Integer.parseInt).toList
)
I say it's a possibility 'cause I haven't tested. Not sure the fact it's returning a list will disrupt Slick. One place where it can be ambiguous are aggregate queries, where you'd want to count the number of , and not do a count(field) (which will obviously be one).
But this is completely non-relation. The relational way would be to have a new table with two fields, one foreign key referring one line at table SCORES and another field with one SCORE_PER_TIME. The foreign key should be a non-unique index so searches are fast. And slick handles this pretty well.
I'm looking for a super simple way to take a big JSON fragment, that is a long list with a bunch of big objects in it, and parse it, then pick out the same few values from each object and then map into a case class.
I have tried pretty hard to get lift-json (2.5) working for me, but I'm having trouble cleanly dealing with checking if a key is present, and if so, then map the whole object, but if not, then skip it.
I absolutely do not understand this syntax for Lift-JSON one bit:
case class Car(make: String, model: String)
...
val parsed = parse(jsonFragment)
val JArray(cars) = parsed / "cars"
val carList = new MutableList[Car]
for (car <- cars) {
val JString(model) = car / "model"
val JString(make) = car / "make"
// i want to check if they both exist here, and if so
// then add to carList
carList += car
}
What on earth is that construct that makes it look like a case class is being created left of the assignment operator? I'm talking about the "JString" part.
Also how is it supposed to cope with the situation where a key is missing?
Can someone please explain to me what the right way to do this is?
And if I have nested values I'm looking for, I just want to skip the whole object and go on to try to map the next one.
Is there something more straightforward for this than Lift-JSON?
Would using extractOpt help?
I have looked at this a lot:
https://github.com/lift/framework/tree/master/core/json
and it's still not particularly clear to me.
Help is very much appreciated!!!!!
Since you are only looking to extract certain fields, you are on the right track. This modified version of your for-comprehension will loop through your car structure, extract the make and model and only yield your case class if both items exist:
for{
car <- cars
model <- (car \ "model").extractOpt[String]
make <- (car \ "make").extractOpt[String]
} yield Car(make, model)
You would add additional required fields the same way. If you want to also utilize optional parameters, let's say color - then you can call that in your yield section and the for comprehension won't unbox them:
for{
car <- cars
model <- (car \ "model").extractOpt[String]
make <- (car \ "make").extractOpt[String]
} yield Car(make, model, (car \ "color").extractOpt[String])
In both cases you will get back a List of Car case classes.
The weird looking assignment is pattern-matching used on val declaration.
When you see
val JArray(cars) = parsed / "cars"
it extracts from the parsed json the subtree of "cars" objects and matches the resulting value with the extractor pattern JArrays(cars).
That is to say that the value is expected to be in the form of a constructor JArrays(something) and the something is bound to the cars variable name.
It works pretty much the same as you're probably familiar with case classes, like Options, e.g.
//define a value with a class that can pattern match
val option = Some(1)
//do the matching on val assignment
val Some(number) = option
//use the extracted binding as a variable
println(number)
The following assignments are exactly the same stuff
//pattern match on a JSon String whose inner value is assigned to "model"
val JString(model) = car / "model"
//pattern match on a JSon String whose inner value is assigned to "make"
val JString(make) = car / "make"
References
The JSON types (e.g. JValue, JString, JDouble) are defined as aliases within the net.liftweb.json object here.
The aliases in turn point to corresponding inner case classes within the net.liftweb.json.JsonAST object, found here
The case classes have an unapply method for free, which lets you do the pattern-matching as explained in the above answer.
I think this should work for you:
case class UserInfo(
name: String,
firstName: Option[String],
lastName: Option[String],
smiles: Boolean
)
val jValue: JValue
val extractedUserInfoClass: Option[UserInfo] = jValue.extractOpt[UserInfo]
val jsonArray: JArray
val listOfUserInfos: List[Option[UserInfo]] = jsonArray.arr.map(_.extractOpt[UserInfo])
I expect jValue to have smiles and name -- otherwise extracting will fail.
I don't expect jValue to necessarily have firstName and lastName -- so I write Option[T] in the case class.
While I know there's a few ways to do this, I'm most interested in finding the most idiomatic and functional Scala method.
Given the following trite example:
case class User(id: String)
val users = List(User("1"), User("2"), User("3"), User("4"))
What's the best way to create an immutable lookup Map of user.id -> User so that I can perform quick lookups by user.id.
In Java I'd probably use Google-Collection's Maps.uniqueIndex although its unique property I care less about.
You can keep the users in a List and use list.find:
users.find{_.id == "3"} //returns Option[User], either Some(User("3")) or None if no such user
or if you want to use a Map, map the list of users to a list of 2-tuples, then use the toMap method:
val umap = users.map{u => (u.id, u)}.toMap
which will return an immutable Map[String, User], then you can use
umap contains "1" //return true
or
umap.get("1") //returns Some(User("1"))
If you're sure all IDs are unique, the canonical way is
users.map(u => (u.id, u)).toMap
as #Dan Simon said. However, if you are not sure all IDs are unique, then the canonical way is:
users.groupBy(_.id)
This will generate a mapping from user IDs to a list of users that share that ID.
Thus, there is an alternate not-entirely-canonical way to generate the map from ID to single users:
users.groupBy(_.id).mapValues(_.head)
For expert users who want to avoid the intermediate step of creating a map of lists, or a list which then gets turned into a map, there is the handy scala.collecion.breakOut method that builds the type that you want if there's a straightforward way to do it. It needs to know the type, though, so this will do the trick:
users.map(u => (u.id,u))(collection.breakOut): Map[String,User]
(You can also assign to a var or val of specified type.)
Convert the List into a Map and use it as a function:
case class User(id: String)
val users = List(User("1"), User("2"), User("3"))
val usersMap = users map { case user # User(id) => id -> user } .toMap
usersMap("1") // Some(User("1"))
usersMap("0") // None
If you would like to use a numeric index:
scala> users.map (u=> u.id.toInt -> u).toMap
res18: scala.collection.immutable.Map[Int,User] =
Map((1,User(1)), (2,User(2)), (3,User(3)))
Maps are functions too, their apply method provides access to the value associated with a particular key (or a NoSuchElementException is thrown for an unknown key) so this makes for a very clean lookup syntax. Following on from Dan Simon's answer and using a more semantically meaningful name:
scala> val Users = users map {u => (u.id, u)} toMap
Users: scala.collection.immutable.Map[String,User] = Map((1,User(1)), (2,User(2)), (3,User(3)))
which then provides the following lookup syntax:
scala> val user2 = Users("2")
user2: User = User(2)