2013-11-14 3 views
13

Я немного новичок в Scala, поэтому извиняюсь, если это что-то немного тривиальное.Завершение цикла for-assrehension, когда проверка на одном из элементов возвращает false

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

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

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

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

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

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

+0

список forall {code}? –

+0

добавлен код, спасибо – jbx

+0

Вам нужно что-либо делать с элементами, пока вы их переплетаете? Если нет, то [ответ вы ищете здесь] (http://stackoverflow.com/questions/12547235/scala-forall-example). –

ответ

22

Остановка раннего при первом ложном состоянии для состояния выполняется с использованием forall в Scala. (A related question)

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

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 
+0

'forall' и' exist' не похожи на 'for-yield'. Это просто 'for' – Yuriy

+0

@Yury Извините, не понимаю ваш комментарий, не могли бы вы уточнить? Я никогда не говорил, что 'forall' и' exist' такие же, как 'for-yield' или' for'. Если бы вы могли это уточнить, это помогло бы. –

+0

@ krivachy.akos Спасибо за добавление существующей части. Это очень полезно для реализации OR, чтобы проверить, удовлетворяет ли какой-либо из этих элементов условию (а не всем). – jbx

5

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

Есть три вещи, которые может сделать 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 преобразует Char в сообщение Int, поэтому String - который неявно преобразованный в Seq[Char] в Scala -, становится Seq[Int] (фактически, 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. A 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, в которой написана большая часть этих документов. Скала, как и большинство других языков, по умолчанию является строгой.

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

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

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

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

Следует также отметить, что это называется «цикл», а не «для понимания», потому что Безразлично» t возвращает значение (ну, оно возвращает Unit), так как оно не имеет ключевого слова yield.

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

+0

Большое спасибо за все детали. Думаю, мне придется вернуться к нему, потому что вы предоставили массу информации. – jbx

1

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

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