Search code examples
clojure

How to break a for loop in Clojure?


I have the following function:

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

It returns the first element of an array of tuples [i j] which describe elments of the array arr for which arr[i] > arr[j] is true.

The for loop in the fragment below runs through every pair of i and 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
          )
       )
    ) 

How can I modify this for loop so that it stops once it finds the first relevant tuple (i. e. the loop should stop at the place marked with ;; I want the loop to stop here comment)?

Here is the equivalent code in 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};
       }
     }
  }

}

Update 1:

As recommended by @CharlesDuffy, I replaced for with 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

Solution

  • In the for list comprehension, use :when to filter for the tuples of interest, and use first to return just the first one:

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