Clojure core.logic/fd减法不符合预期且无文档,求优化方案
问题解决与代码优化方案
1. 修复相邻差值约束的方向依赖问题
你的stepLimit函数问题出在对fd/-的理解上:core.logic.fd/fd/-的语义是第一个参数减去第二个参数等于第三个参数(即a - b = c),并非无方向的差值。你当前的写法仅约束了lower - upper ∈ [-maxStep, maxStep],但实际需要的是相邻两数的绝对值差不超过maxStep,即|x - y| ≤ maxStep。
正确实现方式
可以直接通过两个不等式约束实现,无需额外的diff变量:
(defn step-limit [max-step] (fn [x y] (and* [(fd/<= (fd/- x y) max-step) (fd/<= (fd/- y x) max-step)])))
或者用fd/abs简化(需Clojure 1.10+版本的core.logic支持):
(defn step-limit [max-step] (fn [x y] (fd/<= (fd/abs (fd/- x y)) max-step)))
关于区间与域的互换问题:fd/domain和fd/interval都是定义变量取值范围的工具,多数场景下可以互换。比如(fd/interval (- max-step) max-step)完全可以替代你之前的diffDom,写法更简洁。
2. 实现最多maxDistinct个不同整数的约束
无法直接用普通Clojure的distinct处理逻辑变量,需要用core.logic的约束原语实现,这里提供两种通用方案:
方案A:基于候选值的约束(适合小maxDistinct)
当maxDistinct较小时,直接声明对应数量的逻辑变量作为候选值,约束结果中的每个元素都等于其中一个候选值:
(defn max-distinct-constraint [result max-distinct] (let [candidates (lvars max-distinct)] (and* ; 每个结果元素必须等于某一个候选值 (map (fn [x] (someg #(== x %) candidates)) result) )))
方案B:递归统计不同元素数量(通用场景)
通过递归遍历结果列表,统计新增不同值的数量不超过maxDistinct:
(defn count-distinct [remaining seen count-so-far max-allowed] (conde [(emptyo remaining) (fd/<= count-so-far max-allowed)] [(fresh [x rest-seen new-count] (firsto remaining x) (resto remaining remaining-rest) ; 判断当前元素是否已在已见集合中 (conde [(membero x seen) (== new-count count-so-far) (count-distinct remaining-rest seen new-count max-allowed)] [(conso x seen rest-seen) (fd/+ count-so-far 1 new-count) (fd/<= new-count max-allowed) (count-distinct remaining-rest rest-seen new-count max-allowed)]))])) (defn max-distinct-constraint [result max-distinct] (fresh [init-count] (== init-count 0) (count-distinct result () init-count max-distinct)))
3. core.logic代码风格改进建议
- 避免在run外部预定义逻辑变量:直接在
run内部用fresh或lvars声明,更符合逻辑编程风格。 - 使用Clojure标准命名规范:比如
stepLimit改为step-limit,maxStep改为max-step。 - 简化约束写法:
run内部的约束默认是合取关系,无需用and*包裹所有map结果,可直接用everyg等原语。 - 减少冗余变量:无需提前定义
result再绑定到q,可在fresh中直接声明。
优化后的完整代码
(ns thickness-optimizer.core (:refer-clojure :exclude [==]) (:use clojure.core.logic) (:require [clojure.core.logic.fd :as fd])) (defn step-limit [max-step] (fn [x y] (and* [(fd/<= (fd/- x y) max-step) (fd/<= (fd/- y x) max-step)]))) (defn count-distinct [remaining seen count-so-far max-allowed] (conde [(emptyo remaining) (fd/<= count-so-far max-allowed)] [(fresh [x rest-seen new-count] (firsto remaining x) (resto remaining remaining-rest) (conde [(membero x seen) (== new-count count-so-far) (count-distinct remaining-rest seen new-count max-allowed)] [(conso x seen rest-seen) (fd/+ count-so-far 1 new-count) (fd/<= new-count max-allowed) (count-distinct remaining-rest rest-seen new-count max-allowed)]))])) (defn max-distinct-constraint [result max-distinct] (fresh [init-count] (== init-count 0) (count-distinct result () init-count max-distinct))) (defn solve [minimums valids max-step max-distinct] (let [result-length (count minimums)] (run* [q] (fresh [result] (== q result) (== (count result) result-length) ; 每个元素属于允许的集合 (everyg #(fd/dom % (apply fd/domain valids)) result) ; 每个元素不小于对应最小值 (everyg (fn [x min-val] (fd/>= x min-val)) result minimums) ; 相邻元素差值不超过max-step (everyg (step-limit max-step) result (rest result)) ; 不同元素数量不超过max-distinct (max-distinct-constraint result max-distinct))))) (defn main [] (run! println (solve [10 10 50] [10 20 45 50] 20 2)))
运行这段代码后,你会得到预期的结果,比如[20 45 50]、[45 45 50]、[50 50 50]等。
内容的提问来源于stack exchange,提问作者mmachenry
相关产品推荐
相关产品推荐

