Break flatMap after first Some occurence - scala

abstract class Node(id: Long, name: String) {
def find(id: Long) = if (this.id == id) Some(this) else None
}
case class Contact(id: Long, name: String, phone: String) extends Node(id, name)
case class Group(id: Long, name: String, entries: Node*) extends Node(id, name) {
override def find(id: Long): Option[Node] = {
super.find(id) orElse entries.flatMap(e => e.find(id)).headOption
}
}
val tree =
Group(0, "Root"
, Group(10, "Shop", Contact(11, "Alice", "312"))
, Group(20, "Workshop"
, Group(30, "Tyres", Contact(31, "Bob", "315"), Contact(32, "Greg", "319"))
, Contact(33, "Mary", "302"))
, Contact(1, "John", "317"))
println(tree.find(32))
Tree data is built from Contacts and Groups (w/ sub-Groups and Contacts). I want to find a node with specific id. Currently I traverse Group's members using:
entries.flatMap(e => e.find(id)).headOption
but it isn't optimal because I check all child entries instead of break upon first finding.
I'd appreciate your help in magic Scala Wold. Thanks.

You want collectFirst, which will select the first matching element and wrap it in Some, or None if it's not found. You can also turn entries into a view, to make the evaluation lazy.
entries.view.map(_.find(id)).collectFirst { case Some(node) => node }
It will work with your original code, as well:
entries.view.flatMap(_.find(id)).headOption

Another way to approach this problem could be providing traverse support for the data structure. Overall it's a tree so it can be traversed easily. Please check the code below:
sealed abstract class Node(val id: Long, val name: String) extends Traversable[Node] {
def foreach[U](f:Node => U) = this match {
case x: Contact => f(x)
case x: Group => f(x); x.entries.foreach(_ foreach f)
}
}
case class Contact(override val id: Long, override val name: String, phone: String) extends Node(id, name) {
override def toString = (id, name, phone).toString
}
case class Group(override val id: Long, override val name: String, entries: Node*) extends Node(id, name) {
override def toString = (id, name).toString + entries.map(_.toString).mkString
}
val tree =
Group(0, "Root"
, Group(10, "Shop", Contact(11, "Alice", "312"))
, Group(20, "Workshop"
, Group(30, "Tyres", Contact(31, "Bob", "315"), Contact(32, "Greg", "319"))
, Contact(33, "Mary", "302"))
, Contact(1, "John", "317"))
println(tree) // (0,Root)(10,Shop)(11,Alice,312)(20,Workshop)(30,Tyres)(31,Bob,315)(32,Greg,319)(33,Mary,302)(1,John,317)
println(tree find {_.id == 32}) //Some((32,Greg,319))
println(tree map {_.name }) //List(Root, Shop, Alice, Workshop, Tyres, Bob, Greg, Mary, John)
The good thing is that now you can use of all the benefits of Traversable[Node] trait. The problem on the other hand is that I had to override toString method in case classes. So I guess there is still room for improvement.

Related

How to add derived variables to a ResultSet

Almost all guides/tutorials that I've seen only show how to parse values from columns that are directly available in the database. For example, the following is a very common pattern, and I understand how it can be useful in many ways:
case class Campaign(id: Int, campaign_mode_id: Int, name: String)
class Application #Inject()(db: Database) extends Controller {
val campaign = {
get[Int]("campaign.id") ~
get[Int]("campaign.campaign_mode_id") ~
get[String]("campaign.name") map {
case id ~ campaign_mode_id ~ name => Campaign(id, campaign_mode_id, name)
}
}
def index = Action {
val data : List[Campaign] = db.withConnection { implicit connection =>
SQL("SELECT id, campaign_mode_id, name FROM campaign").as(campaign.*)
}
Ok(Json.toJson(data))
}
}
And it'd produce a result that might look like the following:
[
{
id: 2324,
campaign_mode_id: 13,
name: "ABC"
},
{
id: 1324,
campaign_mode_id: 23,
name: "ABCD"
}
]
Now what if there were an additional date field in the campaign table like started_on that referred to when the campaign was started? Or another field called num_followers that was an integer referring to the number of followers?
And suppose that I wanted to do some calculations after running the DB query and before returning my JSON. For example, I want to include a latest_compaign_date that references the started_on date of the newest campaign. Or say that I wanted to include an average_num_followers that referred to the average number of followers for all campaigns.
My final result would look like:
{
latest_compaign_date: 12 Dec 2018,
average_num_followers: 123,
campaigns: [
{
id: 2324,
campaign_mode_id: 13,
name: "ABC"
},
{
id: 1324,
campaign_mode_id: 23,
name: "ABCD"
}
]
}
I know that for the examples I've given it's better to do those calculations in my DB query and not in my application code. But what if I had to do something really complicated and wanted to do it in my application code for some reason? How should I modify my ResutSetParser to facilitate this?
Here are a couple of approaches that I've tried:
Do not use ResultSetParser and instead do everything manually
case class CampaignData(newestCampaignDate: Long, newestCampaignId: Long, averageNumFollowers: Float, campaigns: Seq[Campaign])
def aggregater(rows: Seq[Row]): CampaignData = {
val newestCampaignDate: Long = getNewestDate(rows)
val newestCampaignId: Long = getNewestCampaign(rows)
val averageNumFollowers: Float = getAverageNumFollowers(rows)
val campaigns: Seq[Campaign] = rows.map(row => {
val rowMap: Map[String, Any] = row.asMap
Campaign(
rowMap("campaign.id").asInstanceOf[Int],
rowMap("campaign.campaign_mode_id") match { case None => 0 case Some(value) => value.asInstanceOf[Int]},
rowMap("campaign.name") match { case None => "" case Some(value) => value.asInstanceOf[String]}
)
})
CampaignData(newestCampaignDate, newestCampaignId, averageNumFollowers, campaigns)
}
def index = Action {
val data : Seq[Row] = db.withConnection { implicit connection =>
SQL("SELECT id, campaign_mode_id, name, started_on, num_followers FROM campaign")
}
Ok(Json.toJson(aggregater(data)))
}
This approach smells bad because having to deal with every field using asInstanceOf and match is very tedious and honestly feels unsafe. And also intuitively, I feel that Anorm should have something better for this since I'm probably not the first person who has run into this problem.
Use ResultSetParser in combination with another function
case class Campaign(id: Int, campaign_mode_id: Int, name: String)
case class CampaignData(newestCampaignDate: Long, newestCampaignId: Long, averageNumFollowers: Float, campaigns: Seq[Campaign])
val campaign = {
get[Int]("campaign.id") ~
get[Int]("campaign.campaign_mode_id") ~
get[Int]("campaign.num_followers") ~
get[Long]("campaign.started_on") ~
get[String]("campaign.name") map {
case id ~ campaign_mode_id ~ num_followers ~ started_on ~ name => Map(
"id" -> id,
"campaign_mode_id" -> campaign_mode_id,
"num_followers" -> num_followers,
"started_on" -> started_on,
"name" -> name
)
}
}
def index = Action {
val data : Map[String, Any] = db.withConnection { implicit connection =>
SQL("SELECT id, campaign_mode_id, name, started_on, num_followers FROM campaign").as(campaign.*)
}
Ok(Json.toJson(aggregator(data)))
}
def aggregator(data: Map[String, Any]): CampaignData = {
val newestCampaignDate: Long = getNewestDate(data)
val newestCampaignId: Long = getNewestCampaign(data)
val averageNumFollowers: Float = getAverageNumFollowers(data)
val campaigns: Seq[Campaign] = getCampaigns(data)
CampaignData(newestCampaignDate, newestCampaignId, averageNumFollowers, campaigns)
}
This approach is better in the sense that I don't have to deal with isInstanceOf, but then there is a bigger problem of having to deal with the intermediate Map. And it makes all the getter functions (e.g. getCampaigns) so much more complicated. I feel that Anorm has to offer something better out of the box that I'm not aware of.
As you posted in your first code snippet, the following code
def index = Action {
val data : List[Campaign] = db.withConnection { implicit connection =>
SQL("SELECT id, campaign_mode_id, name FROM campaign").as(campaign.*)
}
Ok(Json.toJson(data))
}
returns a typesafe List of Campaign thanks to Anorm extractors.
Typically, you will pre-process the result with a typesafe function like so
case class CampaignAggregateData(campaigns:List[Campaign], averageNumFollowers:Int, newestCampaignId:Option[Long])
def aggregate(f:List[Campaign]):CampaignAggregatedData
def index = Action {
val manyCampaign : List[Campaign] = db.withConnection { implicit connection =>
SQL("SELECT id, campaign_mode_id, name FROM campaign").as(campaign.*)
}
val aggregatedData:CampaignAggregateData = aggregate(manyCampaign)
Ok(Json.toJson(data))
}
In cases where you would need aggregation to be executed by the database engine, you would typically have multiple db.withConnection statements inside a single action

DSL in scala using case classes

My use case has case classes something like
case class Address(name:String,pincode:String){
override def toString =name +"=" +pincode
}
case class Department(name:String){
override def toString =name
}
case class emp(address:Address,department:Department)
I want to create a DSL like below.Can anyone share the links about how to create a DSL and any suggestions to achieve the below.
emp.withAddress("abc","12222").withDepartment("HR")
Update:
Actual use case class may have more fields close to 20. I want to avoid redudancy of code
I created a DSL using reflection so that we don't need to add every field to it.
Disclamer: This DSL is extremely weakly typed and I did it just for fun. I don't really think this is a good approach in Scala.
scala> create an Employee where "homeAddress" is Address("a", "b") and "department" is Department("c") and that_s it
res0: Employee = Employee(a=b,null,c)
scala> create an Employee where "workAddress" is Address("w", "x") and "homeAddress" is Address("y", "z") and that_s it
res1: Employee = Employee(y=z,w=x,null)
scala> create a Customer where "address" is Address("a", "b") and "age" is 900 and that_s it
res0: Customer = Customer(a=b,900)
The last example is the equivalent of writing:
create.a(Customer).where("address").is(Address("a", "b")).and("age").is(900).and(that_s).it
A way of writing DSLs in Scala and avoid parentheses and the dot is by following this pattern:
object.method(parameter).method(parameter)...
Here is the source:
// DSL
object create {
def an(t: Employee.type) = new ModelDSL(Employee(null, null, null))
def a(t: Customer.type) = new ModelDSL(Customer(null, 0))
}
object that_s
class ModelDSL[T](model: T) {
def where(field: String): ValueDSL[ModelDSL2[T], Any] = new ValueDSL(value => {
val f = model.getClass.getDeclaredField(field)
f.setAccessible(true)
f.set(model, value)
new ModelDSL2[T](model)
})
def and(t: that_s.type) = new { def it = model }
}
class ModelDSL2[T](model: T) {
def and(field: String) = new ModelDSL(model).where(field)
def and(t: that_s.type) = new { def it = model }
}
class ValueDSL[T, V](callback: V => T) {
def is(value: V): T = callback(value)
}
// Models
case class Employee(homeAddress: Address, workAddress: Address, department: Department)
case class Customer(address: Address, age: Int)
case class Address(name: String, pincode: String) {
override def toString = name + "=" + pincode
}
case class Department(name: String) {
override def toString = name
}
I really don't think you need the builder pattern in Scala. Just give your case class reasonable defaults and use the copy method.
i.e.:
employee.copy(address = Address("abc","12222"),
department = Department("HR"))
You could also use an immutable builder:
case class EmployeeBuilder(address:Address = Address("", ""),department:Department = Department("")) {
def build = emp(address, department)
def withAddress(address: Address) = copy(address = address)
def withDepartment(department: Department) = copy(department = department)
}
object EmployeeBuilder {
def withAddress(address: Address) = EmployeeBuilder().copy(address = address)
def withDepartment(department: Department) = EmployeeBuilder().copy(department = department)
}
You could do
object emp {
def builder = new Builder(None, None)
case class Builder(address: Option[Address], department: Option[Department]) {
def withDepartment(name:String) = {
val dept = Department(name)
this.copy(department = Some(dept))
}
def withAddress(name:String, pincode:String) = {
val addr = Address(name, pincode)
this.copy(address = Some(addr))
}
def build = (address, department) match {
case (Some(a), Some(d)) => new emp(a, d)
case (None, _) => throw new IllegalStateException("Address not provided")
case _ => throw new IllegalStateException("Department not provided")
}
}
}
and use it as emp.builder.withAddress("abc","12222").withDepartment("HR").build().
You don't need optional fields, copy, or the builder pattern (exactly), if you are willing to have the build always take the arguments in a particular order:
case class emp(address:Address,department:Department, id: Long)
object emp {
def withAddress(name: String, pincode: String): WithDepartment =
new WithDepartment(Address(name, pincode))
final class WithDepartment(private val address: Address)
extends AnyVal {
def withDepartment(name: String): WithId =
new WithId(address, Department(name))
}
final class WithId(address: Address, department: Department) {
def withId(id: Long): emp = emp(address, department, id)
}
}
emp.withAddress("abc","12222").withDepartment("HR").withId(1)
The idea here is that each emp parameter gets its own class which provides a method to get you to the next class, until the final one gives you an emp object. It's like currying but at the type level. As you can see I've added an extra parameter just as an example of how to extend the pattern past the first two parameters.
The nice thing about this approach is that, even if you're part-way through the build, the type you have so far will guide you to the next step. So if you have a WithDepartment so far, you know that the next argument you need to supply is a department name.
If you want to avoid modifying the origin classes you can use implicit class, e.g.
implicit class EmpExtensions(emp: emp) {
def withAddress(name: String, pincode: String) {
//code omitted
}
// code omitted
}
then import EmpExtensions wherever you need these methods

Scala reduce on Case class with keys

Below is the scenario of 2 case classes that I have -
case class Pack(name: String, age: Int, dob: String)
object Pack{
def getPack(name:String, age: Int, dob:String): Pack = {
Pack(name,age,dob)
}
}
case class NewPack(name: String, pack: (List[Pack]))
object NewPack{
def getNewPackList(data: List[Pack]): List[NewPack] = {
val newData = for(x <- data )yield(x.name,List(x))
val newPackData = for(x <- newData)yield(NewPack(x._1,x._2))
newPackData
}
}
val someData = List(Pack.getPack("x",12,"day1"), Pack.getPack("y",23,"day2"),Pack.getPack("x",34,"day3") )
val somePackData = NewPack.getNewPackList(someData)
the values of someData and somePackData is like this -
someData: List[Pack] = List(Pack(x,12,day1), Pack(y,23,day2), Pack(x,34,day3))
somePackData: List[NewPack] = List(NewPack(x,List(Pack(x,12,day1))), NewPack(y,List(Pack(y,23,day2))), NewPack(x,List(Pack(x,34,day3))))
Now I want to get the final data in the below mentioned format. Could anyone suggest me the better way of doing that.
finalData: List[NewPack] = List(NewPack(x,List(Pack(x,12,day1),Pack(x,34,day3)),NewPack(y,List(Pack(y,23,day2))))
Here is small simplification.
case class Pack(name: String, age: Int, dob: String)
case class NewPack(name: String, pack: List[Pack])
object NewPack{
def getNewPackList(data: List[Pack]): List[NewPack] =
data.map{case pack#Pack(name, _, _) => NewPack(name, List(pack))}
}
Note that in general you don't need additional factory method for case class. If you would like to present something tricky later, you can always define custom apply in your companion object.
Additionally if you were intending to group packs by name you can easily define something like
def newPacksGrouped(data: List[Pack]): List[NewPack] =
data.groupBy(_.name).map{case (name, packs) => NewPack(name, packs)}.toList

Adding functionality before calling constructor in extra constructor

Is it possible to add functionality before calling constructor in extra constructor in scala ?
Lets say, I have class User, and want to get one string - and to split it into attributes - to send them to the constructor:
class User(val name: String, val age: Int){
def this(line: String) = {
val attrs = line.split(",") //This line is leading an error - what can I do instead
this(attrs(0), attrs(1).toInt)
}
}
So I know I'm not able to add a line before sending to this, because all constructors need to call another constructor as the first statement of the constructor.
Then what can I do instead?
Edit:
I have a long list of attributes, so I don't want to repeat line.split(",")
I think this is a place where companion object and apply() method come nicely into play:
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
class User(val name: String, val age: Int)
Then you just create your object the following way:
val u1 = User("Zorro,33")
Also since you're exposing name and age anyway, you might consider using case class instead of standard class and have consistent way of constructing User objects (without new keyword):
object User {
def apply(line: String): User = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
case class User(name: String, age: Int)
val u1 = User("Zorro,33")
val u2 = User("Zorro", "33")
Ugly, but working solution#1:
class User(val name: String, val age: Int){
def this(line: String) = {
this(line.split(",")(0), line.split(",")(1).toInt)
}
}
Ugly, but working solution#2:
class User(val name: String, val age: Int)
object User {
def fromString(line: String) = {
val attrs = line.split(",")
new User(attrs(0), attrs(1).toInt)
}
}
Which can be used as:
val johny = User.fromString("johny,35")
You could use apply in place of fromString, but this will lead to a confusion (in one case you have to use new, in the other you have to drop it) so I prefer to use different name
Another ugly solution:
class User(line: String) {
def this(name: String, age: Int) = this(s"$name,$age")
val (name, age) = {
val Array(nameStr,ageStr) = line.split(",")
(nameStr,ageStr.toInt)
}
}
But using a method of the companion object is probably better.

Using extractors to parse text files

I'm trying to improve a CSV parsing routine and feel that extractors could be useful here but can't figure them out. Suppose there's a file with user ids and emails:
1,alice#alice.com
2,bob#bob.com
3,carol#carol.com
If the User class is defined as case class User(id: Int, email: String) everything is pretty easy with something like
lines map { line =>
line split "," match {
case Array(id, email) => User(id.toInt, email)
}
}
What I don't understand is how to deal with the case where User class can have complex properties e.g
case class Email(username: String, host: string)
case class User(id: Int, email: Email)
You probably want to use a regular expression to extract the contents of the email address. Maybe something like this:
val lines = Vector(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com")
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val EmailRe = """(.*)#(.*\.com)""".r // substitute a real email address regex here
lines.map { line =>
line.split(",") match {
case Array(id, EmailRe(uname, host)) => User(id.toInt, Email(uname, host))
}
}
Here you go, an example using a custom Extractor.
// 1,Alice,21212,Baltimore,MD" -> User(1, Alice, Address(21212, Baltimore, MD))
Define a custom Extractor that creates the objects out of given String:
object UserExtractor {
def unapply(s: String) : Option[User] = try {
Some( User(s) )
}
catch {
// bettor handling of bad cases
case e: Throwable => None
}
}
Case classes to hold the data with a custom apply on Comapnion object on User:
case class Address(code: String, cit: String, county: String)
case class User(id: Int, name: String, address: Address)
object User {
def apply(s: String) : User = s.split(",") match {
case Array(id, name, code, city, county) => User(id.toInt, name, Address(code, city, county) )
}
}
Unapplying on a valid string (in the example valid means the correct number of fields).
"1,Alice,21212,Baltimore,MD" match { case UserExtractor(u) => u }
res0: User = User(1,Alice,Address(21212,Baltimore,MD))
More tests could be added with more custom apply methods.
I'd use a single RegexExtractor :
val lines = List(
"1,alice#alice.com",
"2,bob#bob.com",
"3,carol#carol.com"
)
case class Email(username: String, host: String)
case class User(id: Int, email: Email)
val idAndEMail = """^([^,]+),([^#]+)#(.+)$""".r
and define a function that transforms a line to the an User :
def lineToUserWithMail(line: String) : Option[User] =
idAndEMail.findFirstMatchIn(line) map {
case userWithEmail(id,user,host) => User(id.toInt, Email(user,host) )
}
Applying the function to all lines
lines flatMap lineToUserWithMail
//res29: List[User] = List(User(1,Email(alice,alice.com)), User(2,Email(bob,bob.com)), User(3,Email(carol,carol.com)))
Alternatively you could implement custom Extractors on the case classe by adding an unnapply Method. But for that case it wouldn't be worth the pain.
Here is an example for unapply
class Email(user:String, host:String)
object Email {
def unapply(s: String) : Option[(String,String)] = s.split("#") match {
case Array(user, host) => Some( (user,host) )
case _ => None
}
}
"bob#bob.com" match {
case Email(u,h) => println( s"$u , $h" )
}
// prints bob , bob.com
A word of warning on using Regex to parse CSV-data. It's not as easy as you might think, i would recommend to use a CSV-Reader as http://supercsv.sourceforge.net/ which handles some nasty edge cases out of the box.