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

Завершение цикла for-assrehension, когда проверка на одном из элементов возвращает false

Я немного новичок в Scala, поэтому извиняюсь, если это что-то немного тривиальное.

У меня есть список элементов, которые я хочу выполнить. Я должен выполнить проверку каждого из элементов, и если только один из них не работает, я хочу, чтобы вся функция вернула значение false. Таким образом, вы можете видеть это как условие И. Я хочу, чтобы это оценивалось лениво, т.е. Когда я сталкиваюсь с первым ложным возвратом false.

Я использую синтаксис for - yield, который фильтрует элементы, сгенерированные через некоторый генератор (список элементов, последовательность и т.д.). В моем случае, однако, я просто хочу вырваться и вернуть false, не выполняя оставшуюся часть цикла. В обычной Java просто нужно сделать return false; внутри цикла.

Неэффективным способом (т.е. не останавливаться, когда я сталкиваюсь с первым ложным элементом), я мог бы это сделать:

   (for {
          item <- items
          if !satisfiesCondition(item)
        } yield item).isEmpty

Что по существу говорит о том, что если ни один элемент не проходит через фильтр, все они удовлетворяют условию. Но это кажется немного запутанным и неэффективным (подумайте, что у вас есть 1 миллион элементов, а первый уже не удовлетворяет этому условию).

Какой лучший и самый элегантный способ сделать это в Scala?

4b9b3361

Ответ 1

Остановка на раннем этапе с первым ложным условием выполняется с помощью forall в Scala. (Связанный вопрос)

Ваше решение переписано:

items.forall(satisfiesCondition)

Чтобы продемонстрировать короткое замыкание:

List(1,2,3,4,5,6).forall { x => println(x); x < 3 }
1
2
3
res1: Boolean = false

Противоположность forall равна exists, которая останавливается, как только выполняется условие:

List(1,2,3,4,5,6).exists{ x => println(x); x > 3 }
1
2
3
4
res2: Boolean = true

Ответ 2

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

Есть три вещи, которые может выполнить Scala для понимания, когда вы возвращаете значение (т.е. используя yield). В самом основном случае он может это сделать:

  • Для объекта типа M[A] и функции A => B (то есть, который возвращает объект типа B при заданном объекте типа A), возвращает объект типа M[B];

Например, если задана последовательность символов, Seq[Char], получите целое число UTF-16 для этого символа:

val codes = for (char <- "A String") yield char.toInt

Выражение char.toInt преобразует a Char в Int, поэтому String, который неявно преобразован в Seq[Char] в Scala, становится a Seq[Int] (фактически, a IndexedSeq[Int], через некоторую магию коллекции Scala).

Второе, что он может сделать, это следующее:

  • Заданные объекты типа M[A], M[B], M[C] и т.д. и функция A, B, C и т.д. в D возвращают объект типа M[D];

Вы можете думать об этом как об обобщении предыдущего преобразования, хотя не все, что могло бы поддержать предыдущее преобразование, может обязательно поддержать это преобразование. Например, мы могли бы создать координаты для всех координат battleship, как это:

val coords = for {
  column <- 'A' to 'L'
  row    <- 1 to 10
} yield s"$column$row"

В этом случае мы имеем объекты типов Seq[Char] и Seq[Int] и функцию (Char, Int) => String, поэтому вернемся к Seq[String].

Третья, и последняя, ​​вещь для понимания может сделать это:

  • Для объекта типа M[A], такого, что тип M[T] имеет нулевое значение для любого типа T, функцию A => B и условие A => Boolean, возвращает либо нуль, либо объект типа M[B], в зависимости от условия;

Это сложнее понять, хотя сначала это может выглядеть просто. Давайте посмотрим на то, что сначала выглядит просто, скажем, нахождение всех гласных в последовательности символов:

def vowels(s: String) = for {
  letter <- s
  if Set('a', 'e', 'i', 'o', 'u') contains letter.toLower
} yield letter.toLower

val aStringVowels = vowels("A String")

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

Чтобы объяснить это лучше, я переключусь с Seq на Option. Option[A] имеет два подтипа: Some[A] и None. Очевидно, что нуль есть None. Он используется, когда вам нужно представить возможное отсутствие значения или самого значения.

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

def getUser(req: HttpRequest): Option[User]

Если пользователь не вошел в систему, мы получаем None, в противном случае получаем Some(user), где user - это структура данных с информацией о пользователе, который сделал запрос. Затем мы можем моделировать эту операцию следующим образом:

def adminJs(req; HttpRequest): Option[String] = for {
  user <- getUser(req)
  if user.isAdmin
} yield adminScriptForUser(user)

Здесь легче увидеть точку нуля. Когда условие ложно, adminScriptForUser(user) не может быть выполнено, поэтому для понимания нужно что-то возвращать, а что-то есть "ноль": None.

В технических терминах Scala для понятий предоставляет синтаксические сахара для операций над monads, с дополнительной операцией для монадов с нулем (см. список соображения в той же статье).

То, что вы на самом деле хотите выполнить, называется catamorphism, обычно представленным как метод fold, о котором можно думать как функция M[A] => B. Вы можете записать его с помощью fold, foldLeft или foldRight в последовательности, но ни одна из них фактически не завершит итерацию.

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

Существует три решения вашей проблемы:

  • Используйте специальные методы forall или exists, которые нацелены на ваш точный вариант использования, хотя они не решают общую проблему;
  • Использовать нестандартную коллекцию; там Scala Stream, но у него есть проблемы, которые мешают его эффективному использованию. Библиотека Scalaz может помочь вам там;
  • Используйте ранний возврат, так как библиотека Scala решает эту проблему в общем случае (в конкретных случаях она использует лучшие оптимизации).

В качестве примера третьего варианта вы можете написать это:

def hasEven(xs: List[Int]): Boolean = {
  for (x <- xs) if (x % 2 == 0) return true
  false
}

Обратите также внимание на то, что это называется "for loop", а не "для понимания", потому что оно не возвращает значение (ну, оно возвращает Unit), так как оно не имеет yield.

Вы можете больше узнать о реальной общей итерации в статье The Essence of the Iterator Pattern, которая представляет собой эксперимент Scala с концепциями описанных в документе с тем же именем.

Ответ 3

forall определенно лучший выбор для конкретного сценария, но для иллюстрации здесь хорошая старая рекурсия:

@tailrec def hasEven(xs: List[Int]): Boolean = xs match {
  case head :: tail if head % 2 == 0 => true
  case Nil  => false
  case _ => hasEven(xs.tail)
}

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