Как получить один элемент из цикла for в Scala?

17

Как и этот вопрос:

Функциональный код для цикла с ранним выходом

Скажите, что код

def findFirst[T](objects: List[T]):T = {
  for (obj <- objects) {
    if (expensiveFunc(obj) != null) return /*???*/ Some(obj)
  }
  None
}

Как получить один элемент из цикла for, подобного этому, в Scala?

Я не хочу использовать find, как было предложено в исходном вопросе, мне интересно узнать, можно ли и как это реализовать с помощью цикла for.

* ОБНОВЛЕНИЕ *

Во-первых, спасибо за все комментарии, но, думаю, я не совсем понял вопрос. Я снимаю что-то вроде этого:

val seven = for {
    x <- 1 to 10
    if x == 7
} return x

И это не компилируется. Две ошибки: - вернуть внешнее определение метода - метод main имеет оператор return; нужен тип результата

Я знаю, что find () будет лучше в этом случае, я просто изучаю и изучаю язык. И в более сложном случае с несколькими итераторами, я думаю, что поиск с for может быть действительно полезным.

Спасибо, комментаторы, я начну вознаграждение, чтобы восполнить плохую постановку вопроса:)

    
задан faermanj 12.11.2012 в 13:13
источник
  • Пример кода точно возвращает один элемент из цикла for. Что вы хотите по-другому? –  Alexey Romanov 12.11.2012 в 13:33
  • Измените тип возврата на Option [T], и код, который вы написали, будет работать. Не уверен, что я понимаю ваш вопрос. –  missingfaktor 12.11.2012 в 13:33
  • Я бы посоветовал вам использовать версию с помощью find, а также избегать использования null и return, чтобы сделать ваш код более идиоматичным Scala. –  Jesper 12.11.2012 в 13:35
  • Полностью согласен с Jesper. Ваш вопрос звучит так: да, я знаю, как решить эту проблему кратким и очевидным образом, но может ли кто-нибудь научить меня делать это унииоматичным и запутанным? Нет лучшего решения этой проблемы, кроме той, которая основана на «нахождении». –  Nikita Volkov 12.11.2012 в 18:13

9 ответов

17

Вы можете превратить свой список в поток, чтобы любые фильтры, содержащиеся в цикле for, оценивались только по требованию. Однако выход из потока всегда будет возвращать поток, и я предполагаю, что вам нужна опция, поэтому в качестве последнего шага вы можете проверить, имеет ли результирующий поток хотя бы один элемент, и вернуть его заголовок в качестве опции. Функция headOption делает именно это.

def findFirst[T](objects: List[T], expensiveFunc: T => Boolean): Option[T] =
    (for (obj <- objects.toStream if expensiveFunc(obj)) yield obj).headOption
    
ответ дан dapek 16.11.2012 в 16:19
21

Если вы хотите использовать цикл for , который использует более приятный синтаксис, чем цепочечные вызовы .find , .filter и т. д., есть хитрый прием. Вместо того, чтобы перебирать строгие коллекции, такие как list, перебирайте ленивые, такие как итераторы или потоки. Если вы начинаете со строгой коллекции, сделайте ее ленивой, например, .toIterator .

Давайте посмотрим на пример.

Сначала давайте определим «шумный» int, который покажет нам, когда он вызывается

def noisyInt(i : Int) = () => { println("Getting %d!".format(i)); i }

Теперь давайте заполним список некоторыми из них:

val l = List(1, 2, 3, 4).map(noisyInt)

Мы хотим найти первый элемент, который является четным.

val r1 = for(e <- l; val v = e() ; if v % 2 == 0) yield v

Приведенная выше строка приводит к:

Getting 1!
Getting 2!
Getting 3!
Getting 4!
r1: List[Int] = List(2, 4)

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

val r2 = (for(e <- l.toIterator; val v = e() ; if v % 2 == 0) yield v)

Это приводит к:

Getting 1!
Getting 2!
r2: Iterator[Int] = non-empty iterator

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

Чтобы получить первый результат, теперь вы можете просто вызвать r2.next .

Если вы хотите получить результат типа Option , используйте:

if(r2.hasNext) Some(r2.next) else None

Изменить . Второй пример в этой кодировке:

val seven = (for {
    x <- (1 to 10).toIterator
    if x == 7
} yield x).next

... конечно, вы должны быть уверены, что всегда есть хотя бы решение, если вы собираетесь использовать .next . Либо используйте headOption , определенный для всех Traversable s, чтобы получить Option[Int] .

    
ответ дан Philippe 12.11.2012 в 13:49
  • То, что близко, можно ли написать это так, чтобы тип возвращаемого значения был фактически одним Int, а не Vector [Int] или List [Int]? –  faermanj 15.11.2012 в 13:13
  • Тип результата - это Iterator в моем примере. Вызовите .next, чтобы получить Int (будет генерировать исключение, если не было никаких решений). –  Philippe 15.11.2012 в 13:20
  • Я вижу ... есть метод, который возвращает None вместо исключения? (и Some [Int], если найдено). Если нет, это было бы выполнимо в моей «OptionIterator» и моей неявной конвертации, верно? –  faermanj 15.11.2012 в 17:48
  • Раньше был метод «headOpt», но он устарел давным-давно. Вы действительно можете переопределить его самостоятельно с неявным преобразованием. –  Philippe 15.11.2012 в 22:10
  • Nevermind, все еще есть headOption. Не знаю, как я это пропустил. –  Philippe 19.11.2012 в 20:09
3

Почему бы не сделать именно то, что вы набросали выше, то есть return из цикла раньше? Если вы заинтересованы в том, что на самом деле делает Scala, запустите ваш код с -print . Scala удаляет цикл в foreach , а затем использует исключение, чтобы преждевременно покинуть foreach .

    
ответ дан Malte Schwerhoff 12.11.2012 в 13:32
2

Итак, вы пытаетесь разорвать цикл после того, как ваше условие выполнено. Ответ здесь может быть то, что вы ищете. Как выйти из цикла в Scala? .

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

    
ответ дан Udayakumar Rayala 12.11.2012 в 13:32
1

Если вам интересно, вот как поиск реализован в LineerSeqOptimized .scala ; какой список наследует

override /*IterableLike*/
  def find(p: A => Boolean): Option[A] = {
    var these = this
    while (!these.isEmpty) {
      if (p(these.head)) return Some(these.head)
      these = these.tail
    }
    None
  }
    
ответ дан Faruk Sahin 12.11.2012 в 13:44
1

Это ужасный взлом. Но это даст вам желаемый результат.

Идиоматически вы бы использовали Stream или View и просто вычисляли нужные вам части.

def findFirst[T](objects: List[T]): T = {

def expensiveFunc(o : T)  = // unclear what should be returned here

case class MissusedException(val data: T) extends Exception

try {
  (for (obj <- objects) {
    if (expensiveFunc(obj) != null) throw new MissusedException(obj)
  })
  objects.head // T must be returned from loop, dummy
} catch {
  case MissusedException(obj) => obj
}

}

    
ответ дан Andreas Neumann 12.11.2012 в 13:54
  • Возврат от закрытий осуществляется таким образом. –  pedrofurla 12.11.2012 в 13:58
1

Почему бы не что-то вроде

object Main {     
  def main(args: Array[String]): Unit = {
    val seven = (for (
    x <- 1 to 10
    if x == 7
    ) yield x).headOption
  }
}

Переменная seven будет опцией, содержащей Some(value) , если значение удовлетворяет условию

    
ответ дан Vincenzo Maggio 19.11.2012 в 14:16
  • Это (использование «если» для понимания) на самом деле является хорошей идеей и должно быть первым, что нужно учитывать. Однако это не всегда возможно - бывают случаи, когда будут созданы множественные совпадения, а трюк второго уровня (представленный в ответе @ dapek) - это делать ленивую оценку на них. –  akauppi 08.07.2016 в 12:29
0

Я надеюсь помочь вам.

Я думаю ... нет возврата "вкл.

object TakeWhileLoop extends App {
    println("first non-null: " + func(Seq(null, null, "x", "y", "z")))

    def func[T](seq: Seq[T]): T = if (seq.isEmpty) null.asInstanceOf[T] else
        seq(seq.takeWhile(_ == null).size)
}

object OptionLoop extends App {
    println("first non-null: " + func(Seq(null, null, "x", "y", "z")))

    def func[T](seq: Seq[T], index: Int = 0): T = if (seq.isEmpty) null.asInstanceOf[T] else
        Option(seq(index)) getOrElse func(seq, index + 1)
}

object WhileLoop extends App {
    println("first non-null: " + func(Seq(null, null, "x", "y", "z")))

    def func[T](seq: Seq[T]): T = if (seq.isEmpty) null.asInstanceOf[T] else {
        var i = 0
        def obj = seq(i)
        while (obj == null)
            i += 1
        obj
    }
}
    
ответ дан J Camphor 12.11.2012 в 17:19
0
objects iterator filter { obj => (expensiveFunc(obj) != null } next

Хитрость заключается в том, чтобы получить некоторое ленивое оцененное представление для коллекции, либо итератор, либо поток, либо objects.view. Фильтр будет выполняться только по мере необходимости.

    
ответ дан Jürgen Strobel 15.11.2012 в 17:30