Построить условно список, избегая мутаций - PullRequest
0 голосов
/ 04 мая 2018

Допустим, я хочу составить список ингредиентов пиццы условно:

val ingredients = scala.collection.mutable.ArrayBuffer("tomatoes", "cheese")

if (!isVegetarian()) {
   ingredients += "Pepperoni"  
}

if (shouldBeSpicy()) {
   ingredients += "Jalapeno"
}

//etc

Существует ли функциональный способ построения этого массива с использованием неизменяемых коллекций?

Я думал о:

val ingredients = List("tomatoes", "cheese") ++ List(
    if (!isVegetarian()) Some("Pepperoni") else None,
    if (shouldBeSpicy()) Some("Jalapeno") else None
).flatten

а есть ли лучший способ?

Ответы [ 6 ]

0 голосов
/ 05 мая 2018

Ваш оригинальный подход не плох. Я бы, наверное, просто придерживался списка:

val ingredients = 
  List("tomatoes", "cheese") ++
  List("Pepperoni", "Sausage").filter(_ => !isVegetarian) ++
  List("Jalapeno").filter(_ => shouldBeSpicy)

Что позволяет легко добавлять больше ингредиентов, связанных с условием (см. «Колбаса» выше)

0 голосов
/ 04 мая 2018

Если какой-либо ингредиент будет нуждаться в тестировании только на одно условие, вы можете сделать что-то вроде этого:

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

Это не работает, если у вас есть ингредиенты, которые тестируются в двух категориях: например, если у вас есть острая колбаса, то это следует включать только в том случае, если! IsVegetarian и spicyIngredientsWanted. Один из способов сделать это состоит в том, чтобы проверить оба условия вместе:

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

Это может быть расширено для проверки любого количества условий, хотя, конечно, количество необходимых корпусных кронштейнов увеличивается экспоненциально с количеством тестируемых условий.

0 голосов
/ 04 мая 2018

Вдохновленный другими ответами, я придумал что-то вроде этого:

case class If[T](conditions: (Boolean, T)*) {
  def andAlways(values: T*): List[T] =
    conditions.filter(_._1).map(_._2).toList ++ values
}

Может использоваться как:

val isVegetarian = false
val shouldBeSpicy = true

val ingredients = If(
   !isVegetarian -> "Pepperoni",
   shouldBeSpicy -> "Jalapeno",
).andAlways(
   "Cheese",
   "Tomatoes"
)

Все еще жду лучшего варианта:)

0 голосов
/ 04 мая 2018

Вот еще один возможный способ, который ближе к @Antot, но ИМХО намного проще.

Что неясно в вашем исходном коде, так это то, откуда на самом деле берутся isVegetarian и shouldBeSpicy. Здесь я предполагаю, что существует класс PizzaConf, как указано ниже, чтобы обеспечить эти параметры конфигурации

case class PizzaConf(isVegetarian: Boolean, shouldBeSpicy: Boolean)

Предполагая это, я думаю, что самый простой способ - это иметь allIngredients типа List[(String, Function1[PizzaConf, Boolean])] типа, то есть тот, который хранит ингредиенты и функции для проверки их соответствующей доступности. Учитывая, что buildIngredients становится тривиальным:

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)
}

или вы можете объединить filter и map, используя collect, как показано ниже:

def buildIngredients(pizzaConf: PizzaConf): List[String] = 
  allIngredients.collect({ case (ing, cond) if cond(pizzaConf) => ing })
0 голосов
/ 04 мая 2018

Этого можно достичь, создав последовательность предикатов, которая определяет условия, применяемые для фильтрации ингредиентов.

// 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)
0 голосов
/ 04 мая 2018

Вы можете начать с полного списка ингредиентов, а затем отфильтровать ингредиенты, не соответствующие условиям:

Set("tomatoes", "cheese", "Pepperoni", "Jalapeno")
  .filter {
    case "Pepperoni" => !isVegetarian;
    case "Jalapeno" => shouldBeSpicy; 
    case _ => true // ingredients by default
  }

который для:

val isVegetarian = true
val shouldBeSpicy = true

вернется:

Set(tomatoes, cheese, Jalapeno)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...