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

avatar
PDP
8 августа 2021 в 17:13
153
1
0

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

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

Как изменить этот цикл for так, чтобы он останавливался, как только находит первый соответствующий кортеж (т. е. цикл должен останавливаться в месте, отмеченном комментарием ;; 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
Источник
Charles Duffy
8 августа 2021 в 17:17
1

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

Charles Duffy
8 августа 2021 в 17:19
0

...при этом см. :while внутри for.

Charles Duffy
8 августа 2021 в 17:20
0

Но если вы хотите остановиться после возврата первого элемента, почему бы не просто take 1 из сгенерированной последовательности? (На самом деле есть правильный ответ на этот вопрос «почему бы и нет?» о том, как последовательности Clojure разбиваются на фрагменты и не обрабатываются на уровне лени поэлементно, но это то место, где оптимизация производительности делает поведение в реальном мире немного отличным от теоретического идеала; однако стоит знать и понимать, что такое теоретические идеалы, если вы хотите иметь возможность писать идиоматический код).

Charles Duffy
8 августа 2021 в 17:22
1

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

Charles Duffy
8 августа 2021 в 17:23
1

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

cfrick
8 августа 2021 в 18:10
1

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

PDP
8 августа 2021 в 18:55
0

@CharlesDuffy Спасибо за ваши комментарии. У меня вопрос: является ли non-nil-indexes ленивой последовательностью?

PDP
8 августа 2021 в 19:31
0

@CharlesDuffy Re ... во всяком случае, для вашего фактического варианта использования я бы обычно использовал loop/recur: правильно ли мое решение в обновлении 1 (соответствует букве и духу Clojure )?

Ответы (1)

avatar
Steffan Westcott
8 августа 2021 в 20:55
2

В анализе списка 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])))