Как получить один элемент из цикла 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 имеет оператор возврата; требуется тип результата

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

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

    
задан Julio_AWS_DevRel 12.11.2012 в 13:13
источник

9 ответов

15

Вы можете превратить свой список в поток, чтобы любые фильтры, которые содержат for-loop, оценивались по запросу. Однако уступка из потока всегда будет возвращать поток, и вы хотите, я полагаю, вариант, поэтому в качестве последнего шага вы можете проверить, имеет ли результирующий поток хотя бы один элемент и возвращает свою голову в качестве опции. Функция 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
источник
19

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

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

Сначала давайте определим «шумный» 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)

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

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

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

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

Если вам нужен результат r2.next , используйте:

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

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

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

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

    
ответ дан Philippe 12.11.2012 в 13:49
источник
2

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

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

    
ответ дан Udayakumar Rayala 12.11.2012 в 13:32
источник
2

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

    
ответ дан Malte Schwerhoff 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
источник
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
источник
0

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

Я думаю ... нет 'return' impl.

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

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

    
ответ дан Jürgen Strobel 15.11.2012 в 17:30
источник