Update (or Replace) item(s) in immutable collection in Scala - scala

What is best practice to update (or replace) a item in Seq ?
case class Minion(id: Int, name: String, motivation: Int)
val minions: Seq[Minion] = Seq(
Minion(1, "Bob", 50),
Minion(2, "Kevin", 50),
Minion(3, "Stuart", 50))
I'd like to acquire new Collection
Seq(
Minion(1, "Bob", 50),
Minion(2, "Kevin", 50),
Minion(3, "Stuart", 100))
What's best way ?

Use updated:
// first argument is index (zero-based) - so using 2 to replace 3rd item:
scala> minions.updated(2, Minion(3, "Stuart", 100))
res0: Seq[Minion] = List(Minion(1,Bob,50), Minion(2,Kevin,50), Minion(3,Stuart,100))
Or, without repeating the unchanged attributes of the new Minion:
scala> minions.updated(2, minions(2).copy(motivation = 100))
res1: Seq[Minion] = List(Minion(1,Bob,50), Minion(2,Kevin,50), Minion(3,Stuart,100))

Map also works, and might be a little bit easier to read than updated:
minions.map {
case Minion(2, name, n) => Minion(2, name, 100)
case m => m
}
One benefit of this over updated besides readability is that you can modify several elements in one go.

Related

Perform arithmetic on a list in Scala

I am new to Scala so please forgive me if I am overseeing something extremely basic here. I have the following:
case class Record(
ID: String,
Count: Double)
List(Record("ID1",10.0),Record("ID1",60.0),Record("ID2",50.0),Record("ID3",100.0),Record("ID3",20.0),Record("ID3",10.0))
where x is the ID and y is the Count in Record(x,y). I am able to print this list to the console with println(records).
I am trying to output the following:
ID1,70.0
ID2,50.0
ID3,130.0
which is a summation of the count per ID group. I would like to try the groupBy approach, but I am struggling to be able to parse the ID from each Record in my list in order to begin grouping the counts.
For example, I have considered:
val grouped = records.groupBy(<some_logic_here>)
but the problem is that the objects in the list have Record(x,y) wrapped around it.
Thank you for your help.
case class Record(ID: String,
Count: Double)
val records = List(Record("ID1", 10.0), Record("ID1", 60.0), Record("ID2", 50.0), Record("ID3", 100.0), Record("ID3", 20.0),
Record("ID3", 10.0))
here is the one liner:
val ans = records.groupBy(_.ID).mapValues(_.map(_.Count).sum)
ans.foreach(x => println(s"${x._1},${x._2}"))
case class Record(ID: String, Count: Double)
List(Record("1", 12), Record("1", 13), Record("2", 13))
.groupBy(_.ID)
.map(e => Record(e._1, e._2.map(e => e.Count).sum))
You need to groupBy(ID) and then you get an list, and then you need compute the sum.

Mutating a Node of a general tree in Scala

Consider the following standard class:
class Node(val data: NodeData, val children: Seq[Node])
The NodeData class is also simple:
case class NodeData(text: String, foo: List[Bar])
Also, the tree has arbitrary depth, it's not fixed.
Clearly, implementing a breath-first or depth-first search on that structure is trivial with idiomatic Scala. However, consider that I want not only to visit each of these nodes, but I also want to mutate them on each visit. More concretely, I want to mutate an object in that foo list. How would I go about implementing this? One way I thought about this is to somehow update the nodes and build a new tree while traversing it, but my intuition tells me there is a simpler solution than that.
If you really want to stay immutable, I would define a function recMap on Node, like this:
def recMap(f: NodeData => NodeData) : Node = Node(f(data), children.map(_.recMap(f)))
You could then use it like in this example (I made Node a case class too):
type Bar = Int
case class NodeData(text: String, foo: List[Bar])
case class Node(data: NodeData, children: Seq[Node]) {
def recMap(f: NodeData => NodeData) : Node = Node(f(data), children.map(_.recMap(f)))
}
val tree = new Node(NodeData("parent", List(1, 2, 3, 4)), Seq(
Node(NodeData("a child", List(5, 6, 7, 8)), Seq.empty),
Node(NodeData("another child", List(9, 10, 11, 12)), Seq.empty)
))
val modifiedTree = tree.recMap(
data => NodeData(
if(data.text == "parent") "I am the parent!" else "I am a child!",
data.foo.filter(_ % 2 == 0)
)
)
println(modifiedTree)
Try it out!
Maybe that's what you are searching for.

Merge two lists which contains case class objects scala

I have two lists which contains case class objects
case class Balance(id: String, in: Int, out: Int)
val l1 = List(Balance("a", 0, 0), Balance("b", 10, 30), Balance("c", 20, 0))
val l2 = List(Balance("a", 10, 0), Balance("b", 40, 0))
I want to sumup the elements in the tuples and combine the lists like below
List((Balance(a, 10, 0), Balance(b, 50, 30), Balance(c, 20, 0))
I have came with following solution
// create list of tuples with 'id' as key
val a = l1.map(b => (b.id, (b.in, b.out)))
val b = l2.map(b => (b.id, (b.in, b.out)))
// combine the lists
val bl = (a ++ b).groupBy(_._1).mapValues(_.unzip._2.unzip match {
case (ll1, ll2) => (ll1.sum, ll2.sum)
}).toList.map(b => Balance(b._1, b._2._1, b._2._2))
// output
// List((Balance(a, 10, 0), Balance(b, 50, 30), Balance(c, 20, 0))
Are they any shorter way to do this?
You don't really need to create the tuple lists.
(l1 ++ l2).groupBy(_.id)
.mapValues(_.foldLeft((0,0)){
case ((a,b),Balance(id,in,out)) => (a+in,b+out)})
.map{
case (k,(in,out)) => Balance(k,in,out)}
.toList
// res0: List[Balance] = List(Balance(b,50,30), Balance(a,10,0), Balance(c,20,0))
You'll note that the result appears out of order because of the intermediate representation as a Map, which, by definition, has no order.
Another approach would be to add a Semigroup instance for Balance and use that for the combine logic. The advantage of this is that that code is in one place only, rather that sprinkled wherever you need to combine lists or maps of Balances.
So, you first add the instance:
import cats.implicits._
implicit val semigroupBalance : Semigroup[Balance] = new Semigroup[Balance]
{
override def combine(x: Balance, y: Balance): Balance =
if(x.id == y.id) // I am arbitrarily deciding this: you can adapt the logic to your
// use case, but if you only need it in the scenario you asked for,
// the case where y.id and x.id are different will never happen.
Balance(x.id, x.in + y.in, x.out + y.out)
else x
}
Then, the code to combine multiple lists becomes simpler (using your example data):
(l1 ++ l2).groupBy(_.id).mapValues(_.reduce(_ |+| _)) //Map(b -> Balance(b,50,30), a -> Balance(a,10,0), c -> Balance(c,20,0))
N.B. As #jwvh already noted, the result will not be in order, in this simple case, because of the default unordered Map the groupBy returns. That could be fixed, if needed.
N.B. You might want to use Monoid instead of Semigroup, if you have a meaningful empty value for Balance.
For those who need to merge two list of case class objects, while maintaining the original ordering, here's my solution which is based on jwvh's answer to this question and this answer.
import scala.collection.immutable.SortedMap
val mergedList: List[Balance] = l1 ++ l2
val sortedListOfBalances: List[Balance] =
SortedMap(mergedList.groupBy(_.id).toSeq:_*)
.mapValues(_.foldLeft((0,0)){
case ((a,b),Balance(id,in,out)) => (a+in,b+out)
})
.map{
case (k,(in,out)) => Balance(k,in,out)
}
.toList
This will return List(Balance(a,10,0), Balance(b,50,30), Balance(c,20,0)) while when not using SortedMap we get List(Balance(b,50,30), Balance(a,10,0), Balance(c,20,0)).
map always returns in an unspecified order unless we specifically use a subtype of SortedMap.

Find common items in two lists (Scala)

A newbie Scala question .
I'm trying to implement a function that receive two Lists ,find a common item , than make manipulation and create a new list
I have a case class
case class weightedFruits(fruits: Set[String], weight: Double)
and two lists weightedFruitsList and filteredWeightedFruitsList:
// set is sorted
val weightedFruitsList = List(
weightedFruits(Set("banana"), 200),
weightedFruits(Set("banana", "orange"), 180),
weightedFruits(Set("banana", "orange", "apple"), 170),
weightedFruits(Set("feijoa", "fig"), 201))
//filtered List , Set sorted, contains "melon" as last member
val filteredWeightedFruitsList = List(
weightedFruits(Set("banana", "melon"), 250),
weightedFruits(Set("banana", "orange", "melon"), 270),
weightedFruits(Set("banana", "orange", "apple", "melon"), 365))
I'd like to go over each item in filteredWeightedFruitsList , find same items in weightedFruitsList, do a small manipulation and create a new List[weightedFruits]
My (not working yet) code :
def conf :Option[List[weightedFruits]] = {
for (filtered <- filteredWeightedFruitsList){
weightedFruitsList.find{
case x if ( x.fruits equals filtered.fruits.dropRight(1) ) => return weightedFruits(x.fruits, x.weight / filtered.weight)]
case _ => false
}
}
}
With this code I've two problems:
1) type mismatch; found : Unit required: Option
2) type mismatch; found : weightedFruits required: Option[List[weightedFruits]]
Any thoughts are welcome . Sorry if my question make you mad...
Last question maybe there is more efficient way to make this task ?
Thanks
type mismatch; found : weightedFruits required: Option[List[weightedFruits]] is caused by your conf method doesn't return Option[List[weightedFruits]] type result. maybe you can try use for yield to do this.
def conf :List[weightedFruits] = for {
f <- filteredWeightedFruitsList
t <- weightedFruitsList.find(i => f.fruits.dropRight(1) == i.fruits)
} yield t.copy(weight = t.weight / f.weight)
copy method will copy case class, and override some fields by using name

How to sort a list in Scala by two fields?

how to sort a list in Scala by two fields, in this example I will sort by lastName and firstName?
case class Row(var firstName: String, var lastName: String, var city: String)
var rows = List(new Row("Oscar", "Wilde", "London"),
new Row("Otto", "Swift", "Berlin"),
new Row("Carl", "Swift", "Paris"),
new Row("Hans", "Swift", "Dublin"),
new Row("Hugo", "Swift", "Sligo"))
rows.sortBy(_.lastName)
I try things like this
rows.sortBy(_.lastName + _.firstName)
but it doesn't work. So I be curious for a good and easy solution.
rows.sortBy(r => (r.lastName, r.firstName))
rows.sortBy (row => row.lastName + row.firstName)
If you want to sort by the merged names, as in your question, or
rows.sortBy (row => (row.lastName, row.firstName))
if you first want to sort by lastName, then firstName; relevant for longer names (Wild, Wilder, Wilderman).
If you write
rows.sortBy(_.lastName + _.firstName)
with 2 underlines, the method expects two parameters:
<console>:14: error: wrong number of parameters; expected = 1
rows.sortBy (_.lastName + _.firstName)
^
In general, if you use a stable sorting algorithm, you can just sort by one key, then the next.
rows.sortBy(_.firstName).sortBy(_.lastName)
The final result will be sorted by lastname, then where that is equal, by firstname.
Perhaps this works only for a List of Tuples, but
scala> var zz = List((1, 0.1), (2, 0.5), (3, 0.6), (4, 0.3), (5, 0.1))
zz: List[(Int, Double)] = List((1,0.1), (2,0.5), (3,0.6), (4,0.3), (5,0.1))
scala> zz.sortBy( x => (-x._2, x._1))
res54: List[(Int, Double)] = List((3,0.6), (2,0.5), (4,0.3), (1,0.1), (5,0.1))
appears to work and be a simple way to express it.