Подтвердить что ты не робот

Как я могу сделать "if..else" внутри осознания?

Я задаю очень простой вопрос, который смутил меня в последнее время. Я хочу написать выражение Scala For, чтобы сделать что-то вроде следующего:

for (i <- expr1) {
  if (i.method) {
    for (j <- i) {
      if (j.method) {
        doSomething()
      } else {
        doSomethingElseA()
      }
    }
  } else {
    doSomethingElseB()
  }
}

Проблема в том, что в множественных генераторах Для выражения я не знаю, где я могу поместить каждый для тела выражения.

for {i <- expr1
  if(i.method) // where can I write the else logic ?
  j <- i 
  if (j.method)
} doSomething()

Как я могу переписать код в Scala Style?

4b9b3361

Ответ 1

Первый код, который вы написали, абсолютно корректен, поэтому нет необходимости переписывать его. В другом месте вы сказали, что хотите знать, как это сделать. Scala -стиль. На самом деле нет стиля Scala, но я буду использовать более функциональный стиль и придерживаться этого.

for (i <- expr1) {
  if (i.method) {
    for (j <- i) {
      if (j.method) {
        doSomething()
      } else {
        doSomethingElseA()
      }
    }
  } else {
    doSomethingElseB()
  }
}

Первая проблема заключается в том, что это не возвращает никакой ценности. Все, что он делает, это побочные эффекты, которых следует избегать. Итак, первое изменение будет таким:

val result = for (i <- expr1) yield {
  if (i.method) {
    for (j <- i) yield {
      if (j.method) {
        returnSomething()
        // etc

Теперь существует большая разница между

for (i <- expr1; j <- i) yield ...

и

for (i <- expr1) yield for (j <- i) yield ...

Они возвращают разные вещи, и есть моменты, которые вы хотите, а не первые. Я предполагаю, что вы хотите, чтобы первый. Теперь, прежде чем мы продолжим, исправьте код. Это уродливо, трудно следовать и неинформировать. Позвольте реорганизовать его, извлекая методы.

def resultOrB(j) = if (j.method) returnSomething else returnSomethingElseB
def nonCResults(i) = for (j <- i) yield resultOrB(j)
def resultOrC(i) = if (i.method) nonCResults(i) else returnSomethingC
val result = for (i <- expr1) yield resultOrC(i)

Он уже намного чище, но он не возвращает того, чего мы ожидаем. Посмотрите на разницу:

trait Element
object Unrecognized extends Element
case class Letter(c: Char) extends Element
case class Punct(c: Char) extends Element
val expr1 = "This is a silly example." split "\\b"

def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j)
def validElements(i: String) = for (j <- i) yield wordOrPunct(j)
def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else Unrecognized
val result = for (i <- expr1) yield classifyElements(i)

Тип result есть Array[AnyRef], а использование нескольких генераторов даст Array[Element]. Легкая часть исправления такова:

val result = for {
  i <- expr1
  element <- classifyElements(i)
} yield element

Но это само по себе не будет работать, потому что classifyElements сам возвращает AnyRef, и мы хотим, чтобы он возвращал коллекцию. Теперь validElements возвращает коллекцию, так что это не проблема. Нам нужно только исправить часть else. Так как validElements возвращает IndexedSeq, верните его и в части else. Конечный результат:

trait Element
object Unrecognized extends Element
case class Letter(c: Char) extends Element
case class Punct(c: Char) extends Element
val expr1 = "This is a silly example." split "\\b"

def wordOrPunct(j: Char) = if (j.isLetter) Letter(j.toLower) else Punct(j)
def validElements(i: String) = for (j <- i) yield wordOrPunct(j)
def classifyElements(i: String) = if (i.nonEmpty) validElements(i) else IndexedSeq(Unrecognized)
val result = for {
  i <- expr1
  element <- classifyElements(i)
} yield element

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

О выходе

Я думаю, что важно отметить одну вещь о представленной проблеме. Пусть это упростит:

for (i <- expr1) {
  for (j <- i) {
    doSomething
  }
}

Теперь это реализовано с помощью foreach (см. здесь или других подобных вопросов и ответов). Это означает, что код выше делает то же самое, что и этот код:

for {
  i <- expr1
  j <- i
} doSomething

Точно одно и то же. Это неверно, если вы используете yield. Следующие выражения не дают того же результата:

for (i <- expr1) yield for (j <- i) yield j

for (i <- expr1; j <- i) yield j

Первый фрагмент будет реализован через два вызова map, а второй фрагмент будет использовать один flatMap и один map.

Таким образом, только в контексте yield даже возникает смысл беспокоиться о вложенности циклов for или использовании нескольких генераторов. И, фактически, генераторы означают, что что-то генерируется, что справедливо только для истинных понятий (те yield что-то).

Ответ 2

Часть

for (j <- i) {
   if (j.method) {
     doSomething(j)
   } else {
     doSomethingElse(j)
   }
 }

можно переписать как

for(j <- i; e = Either.cond(j.method, j, j)) {
  e.fold(doSomething _, doSomethingElse _)  
}  

(конечно, вы можете использовать доход вместо этого, если ваши методы... возвращают что-то)

Здесь это не так страшно полезно, но если у вас есть более глубокая вложенная структура, это может...

Ответ 3

Вы не можете. Конструкция for (expr, if) просто фильтрует элемент, который должен обрабатываться в цикле.

Ответ 4

import scalaz._; import Scalaz._

val lhs = (_ : List[X]) collect { case j if j.methodJ => doSomething(j) } 
val rhs = (_ : List[X]) map doSomethingElse
lhs <-: (expr1 partition methodI) :-> rhs

Ответ 5

Если порядок не важен для вызовов doSomething() и doSomethingElse(), вы можете изменить порядок кода таким образом.

val (tmp, no1) = expr1.partition(_.method)
val (yes, no2) = tmp.partition(_.method)

yes.foreach(doSomething())
no1.foreach(doSomethingElse())
no2.foreach(doSomethingElse())

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

Ответ 6

Условия, указанные в операции Scala для операции, фильтруют элементы из генераторов. Элементы, не удовлетворяющие условиям, отбрасываются и не отображаются в блок yield/code.

Это означает, что если вы хотите выполнять альтернативные операции на основе условного выражения, тест должен быть отложен до блока yield/code.

Также имейте в виду, что для операции относительно дорого вычислить (в настоящее время), поэтому, возможно, более простой итерационный подход может быть более уместным, возможно, что-то вроде:

expr1 foreach {i =>
  if (i.method) {
    i foreach {j =>
      if (j.method)
        doSomething()
      else
        doSomethingElseA()
    }
  }
  else
    doSomethingElseB()
}

Update:

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

for (i <- expr1; j <- i) {
  if (i.method) {if (j.method) doSomething() else doSomethingElseA()} else doSomethingElseB()
}