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

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

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

Я думаю ... нет '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