Build conditionally list avoiding mutations - scala

Let's say I want to build list of Pizza's ingredients conditionally:
val ingredients = scala.collection.mutable.ArrayBuffer("tomatoes", "cheese")
if (!isVegetarian()) {
ingredients += "Pepperoni"
}
if (shouldBeSpicy()) {
ingredients += "Jalapeno"
}
//etc
Is there functional way to build this array using immutable collections?
I thought about:
val ingredients = List("tomatoes", "cheese") ++ List(
if (!isVegetarian()) Some("Pepperoni") else None,
if (shouldBeSpicy()) Some("Jalapeno") else None
).flatten
but is there better way?

Here is another possible way that is closer to #Antot but IMHO is much simpler.
What is unclear in your original code is where isVegetarian and shouldBeSpicy actually come from. Here I assume that there is a PizzaConf class as following to provide those configuration settings
case class PizzaConf(isVegetarian: Boolean, shouldBeSpicy: Boolean)
Assuming this, I think the simplest way is to have a allIngredients of List[(String, Function1[PizzaConf, Boolean])] type i.e. one that stores ingredients and functions to check their corresponding availability. Given that buildIngredients becomes trivial:
val allIngredients: List[(String, Function1[PizzaConf, Boolean])] = List(
("Pepperoni", conf => conf.isVegetarian),
("Jalapeno", conf => conf.shouldBeSpicy)
)
def buildIngredients(pizzaConf: PizzaConf): List[String] = {
allIngredients
.filter(_._2(pizzaConf))
.map(_._1)
}
or you can merge filter and map using collect as in following:
def buildIngredients(pizzaConf: PizzaConf): List[String] =
allIngredients.collect({ case (ing, cond) if cond(pizzaConf) => ing })

Your original approach is not bad. I would probably just stick with list:
val ingredients =
List("tomatoes", "cheese") ++
List("Pepperoni", "Sausage").filter(_ => !isVegetarian) ++
List("Jalapeno").filter(_ => shouldBeSpicy)
Which makes it easy to add more ingredients connected to a condition (see "Sausage" above)

You could start with the full list of ingredients and then filter out the ingredients not passing the conditions:
Set("tomatoes", "cheese", "Pepperoni", "Jalapeno")
.filter {
case "Pepperoni" => !isVegetarian;
case "Jalapeno" => shouldBeSpicy;
case _ => true // ingredients by default
}
which for:
val isVegetarian = true
val shouldBeSpicy = true
would return:
Set(tomatoes, cheese, Jalapeno)

This can be achieved by creating a sequence of predicates, which defines the conditions applied to filter the ingredients.
// available ingredients
val ingredients = Seq("tomatoes", "cheese", "ham", "mushrooms", "pepper", "salt")
// predicates
def isVegetarian(ingredient: String): Boolean = ingredient != "ham"
def isSpicy(ingredient: String): Boolean = ingredient == "pepper"
def isSalty(ingredient: String): Boolean = ingredient == "salt"
// to negate another predicate
def not(predicate: (String) => Boolean)(ingr: String): Boolean = !predicate(ingr)
// sequences of conditions for different pizzas:
val vegeterianSpicyPizza: Seq[(String) => Boolean] = Seq(isSpicy, isVegetarian)
val carnivoreSaltyNoSpices: Seq[(String) => Boolean] = Seq(not(isSpicy), isSalty)
// main function: builds a list of ingredients for specified conditions!
def buildIngredients(recipe: Seq[(String) => Boolean]): Seq[String] = {
ingredients.filter(ingredient => recipe.exists(_(ingredient)))
}
println("veg spicy: " + buildIngredients(vegeterianSpicyPizza))
// veg spicy: List(tomatoes, cheese, mushrooms, pepper, salt)
println("carn salty: " + buildIngredients(carnivoreSaltyNoSpices))
// carn salty: List(tomatoes, cheese, ham, mushrooms, salt)

Inspired by other answers, I came up with something like this:
case class If[T](conditions: (Boolean, T)*) {
def andAlways(values: T*): List[T] =
conditions.filter(_._1).map(_._2).toList ++ values
}
It could be used like:
val isVegetarian = false
val shouldBeSpicy = true
val ingredients = If(
!isVegetarian -> "Pepperoni",
shouldBeSpicy -> "Jalapeno",
).andAlways(
"Cheese",
"Tomatoes"
)
Still waiting for a better option :)

If any ingredient will only need testing against one condition, you could do something like this:
val commonIngredients = List("Cheese", "Tomatoes")
val nonVegetarianIngredientsWanted = {
if (!isVegetarian)
List("Pepperoni")
else
List.empty
}
val spicyIngredientsWanted = {
if (shouldBeSpicy)
List("Jalapeno")
else
List.empty
}
val pizzaIngredients = commonIngredients ++ nonVegetarianIngredientsWanted ++ spicyIngredientsWanted
This doesn't work if you have ingredients which are tested in two categories: for example if you have spicy sausage then that should only be included if !isVegetarian and spicyIngredientsWanted. One method of doing this would be to test both conditions together:
val (optionalIngredients) = {
(nonVegetarianIngredientsWanted, spicyIngredientsWanted) match {
case (false, false) => List.empty
case (false, true) => List("Jalapeno")
case (true, false) => List("Pepperoni")
case (true, true) => List("Pepperoni, Jalapeno, Spicy Sausage")
}
val pizzaIngredients = commonIngredients ++ optionalIngredients
This can be extended to test any number of conditions, though of course the number of case arms needed extends exponentially with the number of conditions tested.

Related

Scala: How to add match vals to a list val

I have a few vals that match for matching values
Here is an example:
val job_ = Try(jobId.toInt) match {
case Success(value) => jobs.findById(value).map(_.id)
.getOrElse( Left(WrongValue("jobId", s"$value is not a valid job id")))
case Failure(_) => jobs.findByName(jobId.toString).map(_.id)
.getOrElse( Left(WrongValue("jobId", s"'$jobId' is not a known job title.")))
}
// Here the value arrives as a string e.i "yes || no || true || or false" then converted to a boolean
val bool_ = bool.toLowerCase() match {
case "yes" => true
case "no" => false
case "true" => true
case "false" => false
case other => Left(Invalid("bool", s"wrong value received"))
}
Note: invalid case is case class Invalid(x: String, xx: String)
above i'm looking for a given job value and checking whether it exist in the db or not,
No I have a few of these and want to add to a list, here is my list val and flatten it:
val errors = List(..all my vals errors...).flatten // <--- my_list_val (how do I include val bool_ and val job_)
if (errors.isEmpty) { do stuff }
My result should contain errors from val bool_ and val job_
THANK!
You need to fix the types first. The type of bool_ is Any. Which does not give you something you can work with.
If you want to use Either, you need to use it everwhere.
Then, the easiest approach would be to use a for comprehension (I am assuming you're dealing with Either[F, T] here, where WrongValue and Invalid are both sub-classes of F and you're not really interested in the errors).
for {
foundJob <- job_
_ <- bool_
} yield {
// do stuff
}
Note, that in Scala >= 2.13 you can use toIntOption when converting the String to Int:
vaj job_: Either[F, T] = jobId.toIntOption match {
case Some(value) => ...
case _ => ...
}
Also, in case expressions, you can use alternatives when you have the same statement for several cases:
val bool_: Either[F, Boolean] = bool.toLowerCase() match {
case "yes" | "true" => Right(true)
case "no" | "false" => Right(false)
case other => Left(Invalid("bool", "wrong value received"))
}
So, according to your question, and your comments, these are the types you're dealing with.
type ID = Long //whatever id is
def WrongValue(x: String, xx: String) :String = "?-?-?"
case class Invalid(x: String, xx: String)
Now let's create a couple of error values.
val job_ :Either[String,ID] = Left(WrongValue("x","xx"))
val bool_ :Either[Invalid,Boolean] = Left(Invalid("x","xx"))
To combine and report them you might do something like this.
val errors :List[String] =
List(job_, bool_).flatMap(_.swap.toOption.map(_.toString))
println(errors.mkString(" & "))
//?-?-? & Invalid(x,xx)
After checking types as #cbley explained. You can just do a filter operation with pattern matching on your list:
val error = List(// your variables ).filter(_ match{
case Left(_) => true
case _ => false
})

How to map sequence only if a condition applies with scala using an immutable approach?

Given a sequence of Price objects, I want to map it to applyPromo function if a condition, i.e. promo == "FOO" applies, otherwise return the sequence as is.
This is my applyPromo:
val pricePromo = price => price.copy(amount = price.amount - someDiscount)
In a mutable way I probably would write it like this:
var prices: Seq[Price] = Seq(price1, price2, ...)
.map(doStuff)
.map(doSomeOtherStuff)
if (promo == "FOO") {
prices = prices.map(applyPromo)
}
prices
I was wondering if I could do something similar like this while keeping the immutable approach of scala. Instead of creating a temp var, I prefer to keep the chain.
Pseudo-code:
val prices = Seq(price1, price2, ...)
prices
.map(dosStuff)
.map(doOtherStuff)
.mapIf(promo == "FOO", applyPromo)
I don't want to check the condition within the map function in this case, as it applies for all elements:
prices.map(price => {
if (promo == "FOO") {
applyDiscount(price)
} else
price
}
)
You just need to use else to make it functional (and you can create an implicit class to add the mapIf method if you prefer):
val prices: Seq[Price] = Seq(price1, price2,...).map(doStuff).map(doSomeOtherStuff)
/* val resultPrices = */ if (promo == "FOO") {
prices.map(price => {
price.copy(amount = price.amount - someDiscount)
})
} else prices
Something like this:
implicit class ConditionalMap[T](seq: Seq[T]) extends AnyVal {
def mapIf[Q](cond: =>Boolean, f: T => Q): Seq[Q] = if (cond) seq.map(f) else seq
}
You can also map(x => x) in the else case:
val discountFunction = if (promo == "FOO") (price: Price) =>
price.copy(amount = price.amount - someDiscount) else (x: Price) => x
val prices: Seq[Price] = Seq(price1, price2,...).
map(doStuff).
map(doSomeOtherStuff).
map(discountFunction)
I'd do it like this:
val maybePromo: (Price => Price) =
if(promo == "FOO") applyPromo else identity _
prices.map(maybePromo)
Or you can inline it within map itself:
prices.map(if(promo == "FOO") applyPromo else identity)
In scalaz, a function A => A is called an endomorphism and is a Monoid whose associative binary operation is function composition and whose identity is the identity function. This is useful because there is a bunch of syntax available where monoids are concerned. For example, scalaz adds the ?? operation to boolean along these lines:
def ??[A: Monoid](a: A) = if (self) a else Monoid[A].zero
Thus:
prices
.map(doStuff)
.map(doSomeOtherStuff)
.map(((promo === "FOO") ?? deductDiscount).run)
Where:
val deductDiscount: Endo[Price] = Endo(px => px.copy(amount = px.amount - someDiscount))
The above all requires
import scalaz._
import Scalaz._
Notes
=== is typesafe equals syntax
?? is boolean syntax
oxbow_lakes has an interesting answer
Easy way solve to me is wrapping Seq in a Option context.
scala> case class Price(amount: Double)
defined class Price
when condition matches,
scala> val promo = "FOO"
promo: String = FOO
scala> Some(Seq(Price(1), Price(2), Price(3))).collect{
case prices if promo == "FOO" => prices.map { p => p.copy(p.amount - 1 )}
case prices => prices}
res6: Option[Seq[Price]] = Some(List(Price(0.0), Price(1.0), Price(2.0)))
when condition does not match
scala> val promo = "NOT-FOO"
promo: String = NOT-FOO
scala> Some(Seq(Price(1), Price(2), Price(3))).collect{
case prices if promo == "FOO" => prices.map { p => p.copy(p.amount - 1 )}
case prices => prices}
res7: Option[Seq[Price]] = Some(List(Price(1.0), Price(2.0), Price(3.0)))

Scala alternative to series of if statements that append to a list?

I have a Seq[String] in Scala, and if the Seq contains certain Strings, I append a relevant message to another list.
Is there a more 'scalaesque' way to do this, rather than a series of if statements appending to a list like I have below?
val result = new ListBuffer[Err]()
val malformedParamNames = // A Seq[String]
if (malformedParamNames.contains("$top")) result += IntegerMustBePositive("$top")
if (malformedParamNames.contains("$skip")) result += IntegerMustBePositive("$skip")
if (malformedParamNames.contains("modifiedDate")) result += FormatInvalid("modifiedDate", "yyyy-MM-dd")
...
result.toList
If you want to use some scala iterables sugar I would use
sealed trait Err
case class IntegerMustBePositive(msg: String) extends Err
case class FormatInvalid(msg: String, format: String) extends Err
val malformedParamNames = Seq[String]("$top", "aa", "$skip", "ccc", "ddd", "modifiedDate")
val result = malformedParamNames.map { v =>
v match {
case "$top" => Some(IntegerMustBePositive("$top"))
case "$skip" => Some(IntegerMustBePositive("$skip"))
case "modifiedDate" => Some(FormatInvalid("modifiedDate", "yyyy-MM-dd"))
case _ => None
}
}.flatten
result.toList
Be warn if you ask for scala-esque way of doing things there are many possibilities.
The map function combined with flatten can be simplified by using flatmap
sealed trait Err
case class IntegerMustBePositive(msg: String) extends Err
case class FormatInvalid(msg: String, format: String) extends Err
val malformedParamNames = Seq[String]("$top", "aa", "$skip", "ccc", "ddd", "modifiedDate")
val result = malformedParamNames.flatMap {
case "$top" => Some(IntegerMustBePositive("$top"))
case "$skip" => Some(IntegerMustBePositive("$skip"))
case "modifiedDate" => Some(FormatInvalid("modifiedDate", "yyyy-MM-dd"))
case _ => None
}
result
Most 'scalesque' version I can think of while keeping it readable would be:
val map = scala.collection.immutable.ListMap(
"$top" -> IntegerMustBePositive("$top"),
"$skip" -> IntegerMustBePositive("$skip"),
"modifiedDate" -> FormatInvalid("modifiedDate", "yyyy-MM-dd"))
val result = for {
(k,v) <- map
if malformedParamNames contains k
} yield v
//or
val result2 = map.filterKeys(malformedParamNames.contains).values.toList
Benoit's is probably the most scala-esque way of doing it, but depending on who's going to be reading the code later, you might want a different approach.
// Some type definitions omitted
val malformations = Seq[(String, Err)](
("$top", IntegerMustBePositive("$top")),
("$skip", IntegerMustBePositive("$skip")),
("modifiedDate", FormatInvalid("modifiedDate", "yyyy-MM-dd")
)
If you need a list and the order is siginificant:
val result = (malformations.foldLeft(List.empty[Err]) { (acc, pair) =>
if (malformedParamNames.contains(pair._1)) {
pair._2 ++: acc // prepend to list for faster performance
} else acc
}).reverse // and reverse since we were prepending
If the order isn't significant (although if the order's not significant, you might consider wanting a Set instead of a List):
val result = (malformations.foldLeft(Set.empty[Err]) { (acc, pair) =>
if (malformedParamNames.contains(pair._1)) {
acc ++ pair._2
} else acc
}).toList // omit the .toList if you're OK with just a Set
If the predicates in the repeated ifs are more complex/less uniform, then the type for malformations might need to change, as they would if the responses changed, but the basic pattern is very flexible.
In this solution we define a list of mappings that take your IF condition and THEN statement in pairs and we iterate over the inputted list and apply the changes where they match.
// IF THEN
case class Operation(matcher :String, action :String)
def processInput(input :List[String]) :List[String] = {
val operations = List(
Operation("$top", "integer must be positive"),
Operation("$skip", "skip value"),
Operation("$modify", "modify the date")
)
input.flatMap { in =>
operations.find(_.matcher == in).map { _.action }
}
}
println(processInput(List("$skip","$modify", "$skip")));
A breakdown
operations.find(_.matcher == in) // find an operation in our
// list matching the input we are
// checking. Returns Some or None
.map { _.action } // if some, replace input with action
// if none, do nothing
input.flatMap { in => // inputs are processed, converted
// to some(action) or none and the
// flatten removes the some/none
// returning just the strings.

Working scala code using a var in a pure function. Is this possible without a var?

Is it possible (or even worthwhile) to try to write the below code block without a var? It works with a var. This is not for an interview, it's my first attempt at scala (came from java).
The problem: Fit people as close to the front of a theatre as possible, while keeping each request (eg. Jones, 4 tickets) in a single theatre section. The theatre sections, starting at the front, are sized 6, 6, 3, 5, 5... and so on. I'm trying to accomplish this by putting together all of the potential groups of ticket requests, and then choosing the best fitting group per section.
Here are the classes. A SeatingCombination is one possible combination of SeatingRequest (just the IDs) and the sum of their ticketCount(s):
class SeatingCombination(val idList: List[Int], val seatCount: Int){}
class SeatingRequest(val id: Int, val partyName: String, val ticketCount: Int){}
class TheatreSection(val sectionSize: Int, rowNumber: Int, sectionNumber: Int) {
def id: String = rowNumber.toString + "_"+ sectionNumber.toString;
}
By the time we get to the below function...
1.) all of the possible combinations of SeatingRequest are in a list of SeatingCombination and ordered by descending size.
2.) all of the TheatreSection are listed in order.
def getSeatingMap(groups: List[SeatingCombination], sections: List[TheatreSection]): HashMap[Int, TheatreSection] = {
var seatedMap = new HashMap[Int, TheatreSection]
for (sect <- sections) {
val bestFitOpt = groups.find(g => { g.seatCount <= sect.sectionSize && !isAnyListIdInMap(seatedMap, g.idList) })
bestFitOpt.filter(_.idList.size > 0).foreach(_.idList.foreach(seatedMap.update(_, sect)))
}
seatedMap
}
def isAnyListIdInMap(map: HashMap[Int, TheatreSection], list: List[Int]): Boolean = {
(for (id <- list) yield !map.get(id).isEmpty).reduce(_ || _)
}
I wrote the rest of the program without a var, but in this iterative section it seems impossible. Maybe with my implementation strategy it's impossible. From what else I've read, a var in a pure function is still functional. But it's been bothering me I can't think of how to remove the var, because my textbook told me to try to avoid them, and I don't know what I don't know.
You can use foldLeft to iterate on sections with a running state (and again, inside, on your state to add iteratively all the ids in a section):
sections.foldLeft(Map.empty[Int, TheatreSection]){
case (seatedMap, sect) =>
val bestFitOpt = groups.find(g => g.seatCount <= sect.sectionSize && !isAnyListIdInMap(seatedMap, g.idList))
bestFitOpt.
filter(_.idList.size > 0).toList. //convert option to list
flatMap(_.idList). // flatten list from option and idList
foldLeft(seatedMap)(_ + (_ -> sect))) // add all ids to the map with sect as value
}
By the way, you can simplify the second method using exists and map.contains:
def isAnyListIdInMap(map: HashMap[Int, TheatreSection], list: List[Int]): Boolean = {
list.exists(id => map.contains(id))
}
list.exists(predicate: Int => Boolean) is a Boolean which is true if the predicate is true for any element in list.
map.contains(key) checks if map is defined at key.
If you want to be even more concise, you don't need to give a name to the argument of the predicate:
list.exists(map.contains)
Simply changing var to val should do it :)
I think, you may be asking about getting rid of the mutable map, not of the var (it doesn't need to be var in your code).
Things like this are usually written recursively in scala or using foldLeft, like other answers suggest. Here is a recursive version:
#tailrec
def getSeatingMap(
groups: List[SeatingCombination],
sections: List[TheatreSection],
result: Map[Int, TheatreSection] = Map.empty): Map[Int, TheatreSection] = sections match {
case Nil => result
case head :: tail =>
val seated = groups
.iterator
.filter(_.idList.nonEmpty)
.filterNot(_.idList.find(result.contains).isDefined)
.find(_.seatCount <= head.sectionSize)
.fold(Nil)(_.idList.map(id => id -> sect))
getSeatingMap(groups, tail, result ++ seated)
}
btw, I don't think you need to test every id in list for presence in the map - should suffice to just look at the first one. You could also make it a bit more efficient, probably, if instead of checking the map every time to see if the group is already seated, you'd just drop it from the input list as soon as the section is assigned.
#tailrec
def selectGroup(
sect: TheatreSection,
groups: List[SeatingCombination],
result: List[SeatingCombination] = Nil
): (List[(Int, TheatreSection)], List[SeatingCombination]) = groups match {
case Nil => (Nil, result)
case head :: tail
if(head.idList.nonEmpty && head.seatCount <= sect.sectionSize) => (head.idList.map(_ -> sect), result.reverse ++ tail)
case head :: tail => selectGroup(sect, tail, head :: result)
}
and then in getSeatingMap:
...
case head :: tail =>
val(seated, remaining) => selectGroup(sect, groups)
getSeatingMap(remaining, tail, result ++ seated)
Here is how I was able to achieve without using the mutable.HashMap, the suggestion by the comment to use foldLeft was used to do it:
class SeatingCombination(val idList: List[Int], val seatCount: Int){}
class SeatingRequest(val id: Int, val partyName: String, val ticketCount: Int){}
class TheatreSection(val sectionSize: Int, rowNumber: Int, sectionNumber: Int) {
def id: String = rowNumber.toString + "_"+ sectionNumber.toString;
}
def getSeatingMap(groups: List[SeatingCombination], sections: List[TheatreSection]): Map[Int, TheatreSection] = {
sections.foldLeft(Map.empty[Int, TheatreSection]) { (m, sect) =>
val bestFitOpt = groups.find(g => {
g.seatCount <= sect.sectionSize && !isAnyListIdInMap(m, g.idList)
}).filter(_.idList.nonEmpty)
val newEntries = bestFitOpt.map(_.idList.map(_ -> sect)).getOrElse(List.empty)
m ++ newEntries
}
}
def isAnyListIdInMap(map: Map[Int, TheatreSection], list: List[Int]): Boolean = {
(for (id <- list) yield map.get(id).isDefined).reduce(_ || _)
}

Counting pattern in scala list

My list looks like the following: List(Person,Invite,Invite,Person,Invite,Person...). I am trying to match based on a inviteCountRequired, meaning that the Invite objects following the Person object in the list is variable. What is the best way of doing this? My match code so far looks like this:
aList match {
case List(Person(_,_,_),Invitee(_,_,_),_*) => ...
case _ => ...
}
First stack question, please go easy on me.
Let
val aList = List(Person(1), Invite(2), Invite(3), Person(2), Invite(4), Person(3), Invite(6), Invite(7))
Then index each location in the list and select Person instances,
val persons = (aList zip Stream.from(0)).filter {_._1.isInstanceOf[Person]}
namely, List((Person(1),0), (Person(2),3), (Person(3),5)) . Define then sublists where the lower bound corresponds to a Person instance,
val intervals = persons.map{_._2}.sliding(2,1).toArray
res31: Array[List[Int]] = Array(List(0, 3), List(3, 5))
Construct sublists,
val latest = aList.drop(intervals.last.last) // last Person and Invitees not paired
val associations = intervals.map { case List(pa,pb,_*) => b.slice(pa,pb) } ++ latest
Hence the result looks like
Array(List(Person(1), Invite(2), Invite(3)), List(Person(2), Invite(4)), List(Person(3), Invite(6), Invite(7)))
Now,
associations.map { a =>
val person = a.take(1)
val invitees = a.drop(1)
// ...
}
This approach may be seen as a variable size sliding.
Thanks for your tips. I ended up creating another case class:
case class BallotInvites(person:Person,invites:List[Any])
Then, I populated it from the original list:
def constructBallotList(ballots:List[Any]):List[BallotInvites] ={
ballots.zipWithIndex.collect {
case (iv:Ballot,i) =>{
BallotInvites(iv,
ballots.distinct.takeRight(ballots.distinct.length-(i+1)).takeWhile({
case y:Invitee => true
case y:Person =>true
case y:Ballot => false})
)}
}}
val l = Ballot.constructBallotList(ballots)
Then to count based on inviteCountRequired, I did the following:
val count = l.count(b=>if ((b.invites.count(x => x.isInstanceOf[Person]) / contest.inviteCountRequired)>0) true else false )
I am not sure I understand the domain but you should only need to iterate once to construct a list of person + invites tuple.
sealed trait PorI
case class P(i: Int) extends PorI
case class I(i: Int) extends PorI
val l: List[PorI] = List(P(1), I(1), I(1), P(2), I(2), P(3), P(4), I(4))
val res = l.foldLeft(List.empty[(P, List[I])])({ case (res, t) =>
t match {
case p # P(_) => (p, List.empty[I]) :: res
case i # I(_) => {
val head :: tail = res
(head._1, i :: head._2) :: tail
}
}
})
res // List((P(4),List(I(4))), (P(3),List()), (P(2),List(I(2))), (P(1),List(I(1), I(1))))