Если я правильно понял, что вы пытаетесь выполнить sh, предположим, что у вас есть несколько точек
(def sample-set-a #{[0 0] [1 0] [2 0] [0 1]})
и некоторые другие точки
(def sample-set-b #{[1.1 -0.2] [0.01 0.99]})
, которые вы хотите установить sh пар между элементами первого и второго набора, так что каждая точка в наборе с наименьшим количеством баллов сопоставляется с уникальной точкой из другого набора. С приведенным выше примером ввода мы ожидаем результата:
#{[[1 0] [1.1 -0.2]] [[0 1] [0.01 0.99]]}
Для этого есть библиотека Clojure . Вы можете добавить зависимость [munkres "0.5.0"]
в свой project.clj
файл, а затем просто использовать эту библиотеку. Вот реализация вашей проблемы с использованием этой библиотеки:
(ns assignment-problem.core
(:require [munkres]))
(defn sqr [x]
(* x x))
(defn dist [[x0 x1] [y0 y1]]
(Math/sqrt (+ (sqr (- x0 y0))
(sqr (- x1 y1)))))
(defn solve-euclidean-assignment [a b]
(if (< (count b) (count a))
(update (solve-euclidean-assignment b a) :assignments (partial mapv (comp vec reverse)))
(let [a (vec a)
b (vec b)
cost-matrix (for [x a]
(for [y b]
(dist x y)))]
(munkres/minimize-weight cost-matrix a b))))
Функция solve-euclidean-assignment
вызывается с двумя наборами точек, а затем возвращает карту с ключами :assignments
и :weight
, где :assignments
содержит пары. Вот пример его вызова:
(def sample-set-a #{[0 0] [1 0] [2 0] [0 1]})
(def sample-set-b #{[1.1 -0.2] [0.01 0.99]})
(solve-euclidean-assignment sample-set-a sample-set-b)
;; => {:assignments [[[1 0] [1.1 -0.2]] [[0 1] [0.01 0.99]]], :weight 0.23774893337370998}
РАСШИРЕННЫЙ ОТВЕТ: другие целевые функции
Приведенное выше решение минимизирует целевую функцию, как указано в исходной задаче: «минимизировать сумму евклидовых расстояний»
Однако это не всегда может привести к желаемому результату, например, в частном случае
(def sample-set-a #{[0.575 0.675] [0.575 0.575]})
(def sample-set-b #{[0.575 0.675] [0.575 9.575]})
есть два возможных решения с таким же минимизированным расстоянием. Чтобы выбрать вариант с точным соответствием, мы должны настроить целевую функцию . Мы переименовываем dist
в euclidean-dist
, а затем возводим его в степень. Дробная степень меньше 1 будет иметь тенденцию отдавать предпочтение решениям с точным соответствием:
(defn euclidean-dist [[x0 x1] [y0 y1]]
(Math/sqrt (+ (sqr (- x0 y0))
(sqr (- x1 y1)))))
(defn dist [a b]
(let [e 0.5] ;; <-- A value less than 1 means we prefer exact matches.
;; Raising to ½ is the same as computing the square root.
(Math/pow (euclidean-dist a b) e)))
Вот еще один кандидат на функцию dist
, который может быть легче понять:
(defn dist [a b]
(let [d (euclidean-dist a b)]
(if (zero? d)
-100
d)))
Здесь мы просто возвращаем большое отрицательное расстояние всякий раз, когда есть точное совпадение, чтобы явно предпочесть это присвоение.