Как разорвать цикл for в Clojure?

#clojure

Вопрос:

У меня есть следующая функция:

 (defn next-transformation
  [arr]
  (let [
        arr-len (count arr)
        i-range (range 0 arr-len)
        j-range (range 0 arr-len)
        indexes (for [i i-range
                      j j-range]
              (let [
                   xi (nth arr i)
                   xj (nth arr j)
                   ]
                (if (> xi xj)
                  [i j] 
                  nil
                  )
               )
            ) 
        non-nil-indexes (filter
                         (fn [elem]
                           (not (= elem nil))
                           )
                         indexes 
                        ) 
        ]
    (if (not (empty? non-nil-indexes))
       (first non-nil-indexes)
       nil
      )
    )
)
 

Он возвращает первый элемент массива кортежей [i j] , которые описывают элементы массива arr , для которых arr[i] > arr[j] верно.

for Цикл в приведенном ниже фрагменте проходит через каждую пару i и j:

 indexes (for [i i-range
              j j-range]
      (let [
           xi (nth arr i)
           xj (nth arr j)
           ]
        (if (> xi xj)
          [i j] ;; I want the loop to stop here
          nil
          )
       )
    ) 
 

Как я могу изменить этот цикл для, чтобы он остановился, как только найдет первый соответствующий кортеж (то есть цикл должен остановиться в месте, отмеченном ;; I want the loop to stop here комментарием)?

Вот эквивалентный код на Java:

 private Integer[] next-transformation(final Integer[] arr) {
  for (int i=0; i < arr.length; i  ) {
     for (int j=0; j < arr.length; j  ) {
       if (arr[i] > arr[j]) {
         return new Integer[] {i, j};
       }
     }
  }

}
 

Обновление 1:

Как рекомендовал @CharlesDuffy, я заменил for на loop / recur :

 (defn next-transformation
  [arr]
  (loop [i 0
         j 0]
    (let [
          arr-len (count arr)
          ]
      (if (and (< i arr-len)
               (< j arr-len))
        (let [
                xi (nth arr i)
                xj (nth arr j)
                j-plus-1 (  j 1)
                i-plus-1 (  i 1)
                new-i (if (< j-plus-1 arr-len)
                       i
                       (  i 1))
                new-j (if (< j-plus-1 arr-len)
                       (  j 1)
                       0)
              ]
          (if (> xi xj)
              ;; We found it
              [i j] 
              ;; We haven't found it, recur 
              (recur new-i new-j)
            )
          )
          nil ; We are at the end of the  loop
        ) ; if 
      )
    ) ; loop 
  ) ; defn
 

Комментарии:

1. for в Clojure не следует рассматривать как петлю. Это определение последовательности ; то, что его можно использовать для управления потоком, является побочным эффектом, а не основной целью.

2. …тем не менее, загляни :while внутрь for .

3. …в любом случае, для вашего реального случая использования я бы, как правило, пошел с loop / recur и for вообще не пошел. Выбор-правильного-инструмента-для-работы и все такое.

4. (кроме того, если вы используете :when в своем for , вам не нужно возлагать на вызывающего абонента ответственность за пропуск nil s)

5. for это не похоже на циклическую конструкцию, которую вы используете в императивных языках, но это «понимание списка». Clojure-это не еще один «транспилятор LISP» для базового языка, а функциональный язык программирования с неизменяемыми структурами данных. Так что это довольно серьезный сдвиг в мышлении, так что не копайтесь слишком глубоко в своих «императивных» областях мозга для решения подобных коанов.

Ответ №1:

В for понимании списка используйте :when фильтр для интересующих кортежей и first возвращайте только первый:

 (defn next-transformation [arr]
  (first (for [i (range (count arr))
               j (range (count arr))
               :when (> (nth arr i) (nth arr j))]
           [i j])))