【问题标题】:Build conditionally list avoiding mutations有条件地构建避免突变的列表
【发布时间】:2018-05-04 09:00:00
【问题描述】:

假设我想有条件地构建 Pizza 的成分列表:

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

但是有更好的方法吗?

【问题讨论】:

    标签: scala collections


    【解决方案1】:

    这是另一种更接近@Antot 但恕我直言更简单的可能方式。

    您的原始代码中不清楚的是 isVegetarianshouldBeSpicy 实际来自何处。这里我假设有一个PizzaConf 类如下来提供这些配置设置

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

    假设这一点,我认为最简单的方法是使用 List[(String, Function1[PizzaConf, Boolean])] 类型的 allIngredients,即存储成分和功能以检查其相应可用性的类型。鉴于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)
    }
    

    或者您可以使用collect 合并filtermap,如下所示:

    def buildIngredients(pizzaConf: PizzaConf): List[String] = 
      allIngredients.collect({ case (ing, cond) if cond(pizzaConf) => ing })
    

    【讨论】:

      【解决方案2】:

      你原来的方法还不错。我可能会坚持使用列表:

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

      这使得添加更多与条件相关的成分变得容易(参见上面的“香肠”)

      【讨论】:

        【解决方案3】:

        您可以从完整的成分列表开始,然后过滤掉不符合条件的成分:

        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)
        

        【讨论】:

        • 仍然看起来像很多样板:)
        【解决方案4】:

        这可以通过创建一系列谓词来实现,谓词定义了用于过滤成分的条件。

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

        【讨论】:

          【解决方案5】:

          受其他答案的启发,我想出了这样的东西:

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

          仍在等待更好的选择:)

          【讨论】:

            【解决方案6】:

            如果任何成分只需要针对一种条件进行测试,您可以执行以下操作:

            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 和辣味成分需要时才应包含该成分。一种方法是同时测试两种条件:

            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
            

            这可以扩展到测试任意数量的条件,当然,所需的案例臂的数量随着测试的条件数量呈指数增长。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2014-11-18
              • 1970-01-01
              • 2020-07-01
              • 2012-02-21
              • 2010-12-24
              • 1970-01-01
              • 2018-06-24
              • 2021-12-11
              相关资源
              最近更新 更多