Scala map function to remove fields - scala

I have a list of Person objects with many fields and I can easily do:
list.map(person => person.getName)
In order to generate another collection with all the peoples names.
How can you use the map function to create a new collection with all the fields of the Person class, BUT their name though?
In other words, how can you create a new collection out of a given collection which will contain all the elements of your initial collection with some of their fields removed?

You can use unapply method of your case class to extract the members as tuple then remove the things that you don't want from the tuple.
case class Person(name: String, Age: Int, country: String)
// defined class Person
val personList = List(
Person("person_1", 20, "country_1"),
Person("person_2", 30, "country_2")
)
// personList: List[Person] = List(Person(person_1,20,country_1), Person(person_2,30,country_2))
val tupleList = personList.flatMap(person => Person.unapply(person))
// tupleList: List[(String, Int, String)] = List((person_1,20,country_1), (person_2,30,country_2))
val wantedTupleList = tupleList.map({ case (name, age, country) => (age, country) })
// wantedTupleList: List[(Int, String)] = List((20,country_1), (30,country_2))
// the above is more easy to understand but will cause two parses of list
// better is to do it in one parse only, like following
val yourList = personList.flatMap(person => {
Person.unapply(person) match {
case (name, age, country) => (age, country)
}
})
// yourList: List[(Int, String)] = List((20,country_1), (30,country_2))

Related

How to sort contents of case class

If I have a case class like below:
case class Student(name: String, activities: Seq[String], grade: String)
And I have a List like this:
val students = List(
Student("John", List("soccer", "Video Games"), "9th"),
Student("Jane", List("sword fighting", "debate"), "10th"),
Student("Boy Wonder", List("1", "5", "2"), "5th")
)
How can I sort the contents based on name and activities attributes to form a string? In the scenario above the string would be:
boywonder_1_2_5_5th_jane_debate_swordfighting_10th_john_soccer_videogames_9th
The sorting in this case is done like this:
First the elements are sorted with name -- Thats why in the final string boywonder comes first
Then that elements' activities are sorted as well -- Thats why Boy Wonder's activities are sorted as 1_2_5
You need to:
Make everything lowercase.
Sort the inner list activities.
Sort the outer list students, by name.
Turn everything into a String.
Here is the code.
students
.map { student =>
student.copy(
name = student.name.toLowerCase,
activities = student.activities.sorted.map(activity => activity.toLowerCase)
)
}.sortBy(student => student.name)
.map(student => s"${student.name}${student.activities.mkString}${student.grade}")
.mkString
.replaceAll("\\s", "")
// res: String = "boywonder1255thjanedebateswordfighting10thjohnvideogamessoccer9th"

combine two lists of different case class types

Combine two case class lists in to merged case class list
case class emp(emp_id:Integer,emp_name:String)
case class manager(manger_id:Integer,manager_name :String,emp_id:Integer)
case class combined(emp_id:Integer,emp_name :String,
manager_id:Integer,manager_name :String)
val list1:List[emp]= List((1,"emp1"),(2,"emp2")
val list2:List[manager]= List((101,"mgr1",1)(103,"mgr3",1))
expected output
val list3 = List(
(1,"emp1",101,"mgr1"),
(1,"emp1",103,"mgr3"),
(2,"emp2",null,null))
Depends on. If your data is already sorted by `emp_id and you have the same amount of managers as employees you can go with:
list1.zip(list2).map { case (e, m) =>
combined(e.emp_id, e.emp_name, m.manager_id, m.manager_name)
}
However, I suppose is not the case in a real-life scenario, where you need to match. Since the managers have an emp_id you can first run a groupBy on managers and then iterate over the employees to enrich them with manager input.
val grouped: Map[Int, manager] = list2.groupBy(_.emp_id)
list1.map { e =>
val manager_id = grouped.get(e.emp_id).flatMap (l =>
Try{l(0)}.toOption.map(_.manager_id)).getOrElse("null")
val manager_name = grouped.get(e.emp_id).flatMap (l =>
Try{l(0)}.toOption.map(_.manager_name)).getOrElse("null")
combined(e.emp_id, e.emp_name, manager_id, manager_name)
}
Did not checked the syntax, but you should get the point here.
P.S
Please use CamelCase and capital letters for classes in Scala.
Here's how I'd be tempted to tackle it.
// types
case class Emp(emp_id:Int, emp_name:String)
case class Manager(manager_id:Int, manager_name:String, emp_id:Int)
case class Combined(emp_id :Int
,emp_name :String
,manager_id :Option[Int]
,manager_name :String)
// input data
val emps :List[Emp] = List(Emp(1,"emp1"),Emp(2,"emp2"))
val mgrs :List[Manager] = List(Manager(101,"mgr1",1),Manager(103,"mgr3",1))
// lookup Emp name by ID
val empName = emps.groupMapReduce(_.emp_id)(_.emp_name)(_+_)
mgrs.map(mgr => Combined(mgr.emp_id
,empName(mgr.emp_id)
,Some(mgr.manager_id)
,mgr.manager_name)
) ++ empName.keySet
.diff(mgrs.map(_.emp_id).toSet)
.map(id => Combined(id, empName(id), None, ""))
//res0: List[Combined] = List(Combined(1, "emp1", Some(101), "mgr1")
// ,Combined(1, "emp1", Some(103), "mgr3")
// ,Combined(2, "emp2", None, ""))
I used Option[Int] and empty string "" to replace null, which Scala style tries to avoid.

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
)

How to sort a list of scala objects by sort order of other list?

I am having following 2 lists in scala.
case class Parents(name: String, savings: Double)
case class Children(parentName: String, debt: Double)
val parentList:List[Parents] = List(Parents("Halls",1007D), Parents("Atticus",8000D), Parents("Aurilius",900D))
val childrenList:List[Children] = List(Children("Halls",9379.40D), Children("Atticus",9.48D), Children("Aurilius",1100.75D))
val sortedParentList:List[Parents] = parentList.sortBy(_.savings).reverse
// sortedParentList = List(Parents(Atticus,8000.0), Parents(Halls,1007.0), Parents(Aurilius,900.0))
now my parenList is Sorted By savings in decreasing order, I want my childrenList to be sorted in the way that it follows parentList Order.
i.e. expected order will be following
// sortedParentList = List(Children(Atticus,9.48D), Children(Halls,9379.40D), Children(Aurilius,1100.75D))
Well, if you know both lists are initially in the same order (you can always ensure that by sorting both by name), you can just sort them both in one go:
val (sortedParentList, sortedChildrenList) = (parents zip children)
.sortBy(-_._1.savings)
.unzip
Or you can define the ordering ahead of time, and use it to sort both lists:
val order = parentList.map(p => p.name -> -p.savings).toMap
val sortedParentList = parentList.sortBy(order(_.name))
val sortedChildrenList = childrenList.sortBy(order(_.parentName))
Or you can sort parents first (maybe, they are already sorted), and then define the order:
val order = sortedParentList.zipWithIndex.map { case(p, idx) => p.name -> idx }.toMap
val sortedChildrenList = childrenList.sortBy(c => order(c.parentName))
case class Parents(name: String, savings: Double)
case class Children(parentName: String, debt: Double)
val familiesList: List[(Parents, Children)] = List(
Parents("Halls",1007D) -> Children("Halls",9379.40D),
Parents("Atticus",8000D) -> Children("Atticus",9.48D),
Parents("Aurilius",900D) -> Children("Aurilius",1100.75D))
val (sortedParents, sortedChildren) = familiesList.sortBy {
case (parents, _) => -parents.savings
}.unzip

Transform a list to a map

I have the following types:
case class Category(id: Int, name: String)
case class Product(id: Int, location: Location, categoryIdList: Option[List[Int]])
Given a list of products
val products:List[Product] = loadProducts()
How can I product a map of categoryId to Location?
Map[categoryId, Location]
So my method would look something like this:
def getMap(products: List[Product]): Map[Int, Location] = {
??
}
I need to somehow iterate over the optional list of categoryIdList and then create a map from that with the Location property.
In order to convert a Seq to a Map we need to first convert it to be a Seq[(Int,Location)], that is a Seq of a Tuple2. Only then will the toMap method actually be available.
Edit: Okay here's an implementation based on each categoryId on the list, note that you shouldn't use an option of a list, since an empty state for a List is just an empty list.
def getMap(products: List[Product]): Map[Int, Location] = {
products.flatMap(toListTuple2).toMap
}
def toListTuple2(product: Product): List[(Int, Location)] = {
product.categoryIdList
.getOrElse(List())
.map(category => (category, product.location))
}
So here we first turn our product into a list of categoryIds and Locations and then flatmap it to a List of (Int, Location), which can then be turned into a Map by calling toMap.
This should do what you are asking for however solution doesn't address problems provided in comments:
def getMap(products: List[Product]): Map[Int, Location] = {
val locations = scala.collection.mutable.Map[Int, Location]()
for {
product <- products
if product.categoryIdList.nonEmpty
category <- product.categoryIdList.get
} {
locations(category) = product.location
}
locations.toMap
}
def getMap(products: List[Product]) = {
products.map(p => (p.categoryIdList.getOrElse(List.empty), p.location))
.flatMap(x => x._1.map(_ -> x._2))
.toMap
}