How to apply a filter with FP/scala - scala

I have a class that has some Option values, and I need to apply all the values that come as Some in the class to a list of objects.
Example:
class Thing(name: String, age: Int)
class Filter(name: Option[String], age: Option[Int], size: Option[Int])
val list: List[Thing] = functionThatReturnsAListOfThings(param)
val filter: Filter = functionThatReturnsAFilter(otherParam)
list.filter{ thing =>
if filter.name.isDefined {
thing.name.equals(filter.name.get)
}
if filter.age.isDefined {
thing.age == filter.age.get
}
}.take{
if filter.size.isDefined filter.size.get
else list.size
}
How can I apply the filter to the list properly with FP?

First off we need to make a small change so that the constructor arguments are public members.
class Thing(val name: String, val age: Int)
class Filter(val name : Option[String]
,val age : Option[Int]
,val size : Option[Int])
Next, it's not clear, from your example code, what should happen when filter.name and filter.age are both None. I'll assume that None means true, i.e. not filtered out.
list.filter { thing =>
filter.name.fold(true)(_ == thing.name) &&
filter.age.fold(true)(_ == thing.age)
}.take(filter.size.getOrElse(Int.MaxValue))
Note that take(Int.MaxValue) is a bit more efficient than take(list.size).

Related

Nested Scala case classes to/from CSV

There are many nice libraries for writing/reading Scala case classes to/from CSV files. I'm looking for something that goes beyond that, which can handle nested cases classes. For example, here a Match has two Players:
case class Player(name: String, ranking: Int)
case class Match(place: String, winner: Player, loser: Player)
val matches = List(
Match("London", Player("Jane",7), Player("Fred",23)),
Match("Rome", Player("Marco",19), Player("Giulia",3)),
Match("Paris", Player("Isabelle",2), Player("Julien",5))
)
I'd like to effortlessly (no boilerplate!) write/read matches to/from this CSV:
place,winner.name,winner.ranking,loser.name,loser.ranking
London,Jane,7,Fred,23
Rome,Marco,19,Giulia,3
Paris,Isabelle,2,Julien,5
Note the automated header line using the dot "." to form the column name for a nested field, e.g. winner.ranking. I'd be delighted if someone could demonstrate a simple way to do this (say, using reflection or Shapeless).
[Motivation. During data analysis it's convenient to have a flat CSV to play around with, for sorting, filtering, etc., even when case classes are nested. And it would be nice if you could load nested case classes back from such files.]
Since a case-class is a Product, getting the values of the various fields is relatively easy. Getting the names of the fields/columns does require using Java reflection.
The following function takes a list of case-class instances and returns a list of rows, each is a list of strings. It is using a recursion to get the values and headers of child case-class instances.
def toCsv(p: List[Product]): List[List[String]] = {
def header(c: Class[_], prefix: String = ""): List[String] = {
c.getDeclaredFields.toList.flatMap { field =>
val name = prefix + field.getName
if (classOf[Product].isAssignableFrom(field.getType)) header(field.getType, name + ".")
else List(name)
}
}
def flatten(p: Product): List[String] =
p.productIterator.flatMap {
case p: Product => flatten(p)
case v: Any => List(v.toString)
}.toList
header(classOf[Match]) :: p.map(flatten)
}
However, constructing case-classes from CSV is far more involved, requiring to use reflection for getting the types of the various fields, for creating the values from the CSV strings and for constructing the case-class instances.
For simplicity (not saying the code is simple, just so it won't be further complicated), I assume that the order of columns in the CSV is the same as if the file was produced by the toCsv(...) function above.
The following function starts by creating a list of "instructions how to process a single CSV row" (the instructions are also used to verify that the column headers in the CSV matches the the case-class properties). The instructions are then used to recursively produce one CSV row at a time.
def fromCsv[T <: Product](csv: List[List[String]])(implicit tag: ClassTag[T]): List[T] = {
trait Instruction {
val name: String
val header = true
}
case class BeginCaseClassField(name: String, clazz: Class[_]) extends Instruction {
override val header = false
}
case class EndCaseClassField(name: String) extends Instruction {
override val header = false
}
case class IntField(name: String) extends Instruction
case class StringField(name: String) extends Instruction
case class DoubleField(name: String) extends Instruction
def scan(c: Class[_], prefix: String = ""): List[Instruction] = {
c.getDeclaredFields.toList.flatMap { field =>
val name = prefix + field.getName
val fType = field.getType
if (fType == classOf[Int]) List(IntField(name))
else if (fType == classOf[Double]) List(DoubleField(name))
else if (fType == classOf[String]) List(StringField(name))
else if (classOf[Product].isAssignableFrom(fType)) BeginCaseClassField(name, fType) :: scan(fType, name + ".")
else throw new IllegalArgumentException(s"Unsupported field type: $fType")
} :+ EndCaseClassField(prefix)
}
def produce(instructions: List[Instruction], row: List[String], argAccumulator: List[Any]): (List[Instruction], List[String], List[Any]) = instructions match {
case IntField(_) :: tail => produce(tail, row.drop(1), argAccumulator :+ row.head.toString.toInt)
case StringField(_) :: tail => produce(tail, row.drop(1), argAccumulator :+ row.head.toString)
case DoubleField(_) :: tail => produce(tail, row.drop(1), argAccumulator :+ row.head.toString.toDouble)
case BeginCaseClassField(_, clazz) :: tail =>
val (instructionRemaining, rowRemaining, constructorArgs) = produce(tail, row, List.empty)
val newCaseClass = clazz.getConstructors.head.newInstance(constructorArgs.map(_.asInstanceOf[AnyRef]): _*)
produce(instructionRemaining, rowRemaining, argAccumulator :+ newCaseClass)
case EndCaseClassField(_) :: tail => (tail, row, argAccumulator)
case Nil if row.isEmpty => (Nil, Nil, argAccumulator)
case Nil => throw new IllegalArgumentException("Not all values from CSV row were used")
}
val instructions = BeginCaseClassField(".", tag.runtimeClass) :: scan(tag.runtimeClass)
assert(csv.head == instructions.filter(_.header).map(_.name), "CSV header doesn't match target case-class fields")
csv.drop(1).map(row => produce(instructions, row, List.empty)._3.head.asInstanceOf[T])
}
I've tested this using:
case class Player(name: String, ranking: Int, price: Double)
case class Match(place: String, winner: Player, loser: Player)
val matches = List(
Match("London", Player("Jane", 7, 12.5), Player("Fred", 23, 11.1)),
Match("Rome", Player("Marco", 19, 13.54), Player("Giulia", 3, 41.8)),
Match("Paris", Player("Isabelle", 2, 31.7), Player("Julien", 5, 16.8))
)
val csv = toCsv(matches)
val matchesFromCsv = fromCsv[Match](csv)
assert(matches == matchesFromCsv)
Obviously this should be optimized and hardened if you ever want to use this for production...

Filter list elements based on another list elements

I have 2 Lists: lista and listb. For each element in lista, I want to check if a_type of each element is in b_type of listb. If true, get the b_name for corresponding b_type and construct an object objc. And, then I should return the list of of constructed objc.
Is there a way to do this in Scala and preferably without any mutable collections?
case class obja = (a_id: String, a_type: String)
case class objb = (b_id: String, b_type: String, b_name: String)
case class objc = (c_id: String, c_type: String, c_name: String)
val lista: List[obja] = List(...)
val listb: List[objb] = List(...)
def getNames(alist: List[obja], blist: List[objb]): List[objc] = ???
Lookup in lists requires traversal in O(n) time, this is inefficient. Therefore, the first thing you do is to create a map from b_type to b_name:
val bTypeToBname = listb.map(b => (b.b_type, b_name)).toMap
Then you iterate through lista, look up in the map whether there is a corresponding b_name for a given a.a_type, and construct the objc:
val cs = for {
a <- lista
b_name <- bTypeToBname.get(a.a_type)
} yield objc(a.a_id, a.a_type, b_name)
Notice how Scala for-comprehensions automatically filter those cases for which bTypeToBname(a.a_type) isn't defined: then the corresponding a is simply skipped. This because we use bTypeToBname.get(a.a_type) (which returns an Option), as opposed to calling bTypeToBname(a.a_type) directly (this would lead to a NoSuchElementException). As far as I understand, this filtering is exactly the behavior you wanted.
case class A(aId: String, aType: String)
case class B(bId: String, bType: String, bName: String)
case class C(cId: String, cType: String, cName: String)
def getNames(aList: List[A], bList: List[B]): List[C] = {
val bMap: Map[String, B] = bList.map(b => b.bType -> b)(collection.breakOut)
aList.flatMap(a => bMap.get(a.aType).map(b => C(a.aId, a.aType, b.bName)))
}
Same as Andrey's answer but without comprehension so you can see what's happening inside.
// make listb into a map from type to name for efficiency
val bs = listb.map(b => b.b_type -> b_name).toMap
val listc: Seq[objc] = lista
.flatMap(a => // flatmap to exclude types not in listb
bs.get(a.a_type) // get an option from blist
.map(bName => objc(a.a_id, a.a_type, bName)) // if there is a b name for that type, make an objc
)

In Scala, is there a way to map over a collection while passing a value along fold-style?

In Scala, is there a way to map over a collection while passing a value along fold-style? Something like:
case class TxRecord(name: String, amount: Int)
case class TxSummary(name: String, amount: Int, balance: Int)
val txRecords: Seq[TxRecord] = txRecordService.getSortedTxRecordsOfUser("userId")
val txSummarys: Seq[TxSummary] = txRecords.foldMap(0)((sum, txRecord) =>
(sum + txRecord.amount, TxSummary(txRecord.name, txRecord.amount, sum + txRecord.amount)))

How can I create a different entity type using the original entity id in sorm?

I have the following classes:
case class Product( title : String, description: String, contract: Contract)
case class Contract(contractType: ContractType, price: Int )
case class ContractType(description: String)
and these DTOs:
case class ProductDto(id: Long, title: String, description: String, contractType: ContractTypeDto, price: Int)
case class ContractTypeDto(id: Long, description: String)
I need to create a method that returns the list of products but with the data filled in DTOs, something like this:
def list = Db.query[Product].fetch().toList.map(x => ProductDto(x.id, x.title,
x.description, ContractTypeDto(x.contract.contractType.id,
x.contract.contractType.description), x.contract.price))
The thing is that I can't access to the x.contract.contractType.id but SORM allows me to access to x.id (at first level), there is any way to do it??
Thanks
Casting Approach
You can always access the id using casting if you have to:
x.contract.contractType.asInstanceOf[ sorm.Persisted ].id
Total Approach
It is cleaner though to utilize pattern matching to produce a total function to do it:
def asPersisted[ A ]( a : A ) : Option[ A with sorm.Persisted ]
= a match {
case a : A with sorm.Persisted => Some( a )
case _ => None
}
Then we can use it like so:
asPersisted( x.contract.contractType ).map( _.id ) // produces Option[ Long ]
The benefit of the total approach is that you protect yourself from runtime casting exceptions, which will arise if you try to cast a non-persisted value.
Total Approach With Pimping
You can also "pimp" asPersisted as a method onto Any using value-classes if you don't find this disturbing:
implicit class AnyAsPersisted[ A ]( val a : A ) extends AnyVal {
def asPersisted : Option[ A with sorm.Persisted ]
= a match {
case a : A with sorm.Persisted => Some( a )
case _ => None
}
}
Then you'll be able to use it like so:
x.contract.contractType.asPersisted.map( _.id ) // produces Option[ Long ]

Case class equals with list of byte array

I am trying to use the should matchers on a case class
case class ListOfByteArrayCaseConfig(
#BeanProperty
permissions: java.util.List[Array[Byte]]
)
With the following test case
val orig = ListOfByteArrayCaseConfig(List(Array[Byte](10, 20, 30)))
val orig2 = ListOfByteArrayCaseConfig(List(Array[Byte](10, 20, 30)))
orig2 should be === orig
Obviously this would fail because the two byte arrays are not equal reference wise. What I want to do is somehow make this work without changing the test case code and still keeping the case class.
Is it even possible? (like adding a custom equals method to the case class?)
I found the solution. Apparently I can override the equals method in a case class
Scala: Ignore case class field for equals/hascode?
Though it gets rid of the reason for using case classes in the first place which is to simplify data objects.
case class ListOfByteArrayCaseConfig(
#BeanProperty
permissions: java.util.List[Array[Byte]]
) {
override def equals(arg: Any): Boolean = {
val obj = arg.asInstanceOf[ListOfByteArrayCaseConfig]
var i: Int = 0
for (i <- 0 until permissions.size()) {
if (!util.Arrays.equals(permissions.get(i), obj.permissions.get(i))) {
return false
}
}
return true
}
}