Clojure - n-й корневой алгоритм - цикл while-do - PullRequest
0 голосов
/ 03 мая 2018

Я работаю над алгоритмом Ньютона для подсчета n-го корня и столкнулся с проблемой неработающего цикла. Вот код:

(defn root [nth guess]
  (if (<= guess 0) "Root doesn't exists" (count_root nth guess))
)

(defn count_root nth guess [nth guess]
  (def result guess)
  (def last_result result)
  (def temp (power nth result))
  (while (> (absolute (- result last_result)) 0.01)
         (do
           (def last_result result)
           (def result ('fn [nth result guess temp] (* (/ 1.0 nth) (+ (* (- nth 1) result) (/ guess temp)))))
           (def temp (power nth result))
         )
  )
  (str "Result: " result)
)

(defn power [nth result]
  (* result (- nth 1))
)

(defn absolute [x]
  (if (>= x 0) x (- x))
)

Когда я закомментирую (пока ...) строку, она будет считать один проход цикла, и результат будет правильным. Но когда есть строка (while ...), весь код ниже игнорируется.

Я перестроил код выше примерно так:

(defn power [nth result]
  (* result (- nth 1))
)

(defn absolute [x]
  (if (>= x 0) x (- x))
)

(defn is-good? [prev-result result]
  (< (absolute (- prev-result result)) 0.01)
)

(defn improve [nth result temp]
  (* (/ 1.0 nth) (+ (* (- nth 1) result) (/ result temp)))
)

(defn count-root [nth number]
  (loop [result number
         prev-result result
         temp (power nth result)]
       (let [next-result (* (/ 1.0 nth) (+ (* (- nth 1) result) (/ number temp)))])
       (if (is-good? (result next-result)) result (recur next-result)))
)

(defn root [nth number]
  (if (<= number 0) "Root doesn't exists" (count-root nth number))
)

Но ошибка компилятора, что следующий результат не может быть решен ... Что происходит сейчас?

1 Ответ

0 голосов
/ 04 мая 2018

Существует ряд проблем с этим кодом. Чтобы предоставить вам рабочий пример, давайте перепишем функции root и count_root:

(defn power [nth result]
  (* result (- nth 1)))

(defn count-root [nth guess]
  (loop [result       guess
         last_result  guess
         temp         (power nth result)]
    (cond
      (> (Math/abs (- result last_result)) 0.01)
        (let [next-result (* (/ 1.0 nth) (+ (* (- nth 1) result) (/ guess temp)))]
          (recur next-result
                 result
                 (power nth next-result)))
      :else
        (str "Result: " result))))

(defn root [nth guess]
  (cond
    (<= guess 0)  "Root doesn't exists"
    :else         (count-root nth guess)))

Давайте посмотрим, что здесь изменилось, и попробуем объяснить.

Сначала в функции count-root:

  1. Я предлагаю не использовать подчеркивания в именах. Наиболее распространенное соглашение Clojure - использовать тире для разделения элементов имен функций и определений.
  2. Чтобы выполнить цикл с определениями, используйте конструкцию loop, чтобы определить, где начинается цикл, вместе с любыми необходимыми определениями, и используйте recur, чтобы выполнить рекурсивный вызов цикла с новыми значениями, указанными для определения. Обратите внимание, что если бы все необходимые определения были заданы в качестве параметров функции count-root, то цикл не потребовался бы, и рекурсия могла бы вернуться к началу функции.
  3. Я рекомендую вам привыкнуть использовать cond вместо if. Функция if выглядит целым лотом как оператор if на других языках, и это время от времени приводит нас (ну, хорошо, ME :-) к трудностям , Проблема в том, что когда вы используете if, вы должны помнить, чтобы указать ОБА действия "true" и "false", потому что форма if не может быть "сокращенной". То есть, если бы это было так на «других» языках, вы всегда должны были бы написать:

    ЕСЛИ что-то ТОГДА правда ELSE фальшивка ENDIF

или, более Clojurist моды

(if something
  true-stuff
  false-stuff)

В Clojure не следует писать

(if something
  true-stuff)

потому что я обнаружил, что это не работает правильно. Если вы вызываете функцию if и задаете only аргумент true, как вы часто делаете в «других» языках программирования, он будет компилироваться и выполняться, но вы получите странные результаты, вещи этого не может произойти, и, как правило, вы запутаетесь, пока не поймете, что у вас есть if без «ложной» половины или чего-то в этом роде. Избавьте себя от неприятностей - используйте cond вместо if. 4. Я предлагаю использовать Math/abs вместо определения собственной функции absolute. 5. Используйте форму let, чтобы определить временные значения, когда вам не нужно будет зацикливаться внутри формы. Здесь я определил next-result как временное значение, которое я буду использовать в следующей форме. 6. Используйте recur для рекурсивного «прыжка» в хвост к следующей самой ранней точке рекурсии. Здесь следующая самая ранняя точка рекурсии - форма loop. Если бы у нас не было этой формы loop или какой-либо другой точки рекурсии, определение функции для count-root послужило бы точкой рекурсии. Когда

Далее в функции root:

  1. определение функции было неверным в исходной версии. Я убрал это здесь так, что функция определена с двумя аргументами, nth и guess.

EDIT

В оригинальной версии count_root вы использовали функцию while. Использование while требует, чтобы его аргумент изменился, что означает, что ему придется так или иначе иметь дело с атомом; то есть он имеет дело с изменяемой переменной. Большую часть времени в Clojure то, что обычно считается «переменными», на самом деле не может быть изменено. Это намеренно. Clojure с самого начала предназначался для написания многопоточных приложений, где координация изменений в «переменных» становится серьезной проблемой. Если «переменные» на самом деле не могут быть изменены, необходимость в такой координации (посредством таких конструкций, как семафоры, критические секции и т. Д.) Исчезает. Таким образом, определения значений (они на самом деле не являются «переменными», если они не могут изменяться или изменяться) по умолчанию постоянны. Теперь любая «настоящая» программа нуждается в значениях, которые меняются со временем, и Clojure обеспечивает такие потребности, но это не значение по умолчанию. Вы должны четко подумать о том, что нужно изменить, и вам нужно по-разному обращаться к этим изменяемым значениям.

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

Когда вы изучаете что-то новое, полезно хорошее руководство. Я предлагаю Clojure For The Brave And True , преимущество которого заключается в том, что его онлайн-издание бесплатное, как пиво.

Удачи.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...