以下实现将永远循环。
pick_nums(0,_,_).
pick_nums(Count,From,To) :-
random_member(X,From),
( member(X,To) -> pick_nums(Count,From,To)
; C1 is Count-1,
pick_nums(C1,From,[X|To]) ) .
?- numlist(1,9,X), pick_nums(3,X,Y).
以下实现将永远循环。
pick_nums(0,_,_).
pick_nums(Count,From,To) :-
random_member(X,From),
( member(X,To) -> pick_nums(Count,From,To)
; C1 is Count-1,
pick_nums(C1,From,[X|To]) ) .
?- numlist(1,9,X), pick_nums(3,X,Y).
这里的问题是,在里面pick_nums(Count, From, To)
你有一个相同的pick_nums(Count, From, To)
. 这里的条件逻辑似乎是在说“给我一个 From 的随机元素,但如果我已经在 To 中有它,请再试一次;否则,正常重复。” 这里的基本问题只是你的条件总是成功的。
我确信有一个比从头重写更直接的解决方案来解决您的问题,但对我来说这似乎有点单调的 Prolog。相反,让我们重新考虑一下我们要说什么,以防我们可以更直接地“合乎逻辑地”说出来。你实际上想说的是“给我一个具有一定大小的 From 的随机子集”。原始代码中的条件实际上只是“嘿,我已经看过这个元素,所以让我们继续前进”的衬托,但在 Prolog 中,你经常通过说出你的意思而不是告诉 Prolog 如何去做来进一步了解。
pick_nums(0, _, []).
pick_nums(Count, From, [X|SelectedFromRemaining]) :-
random_member(X, From),
select(X, From, Remaining),
C1 is Count - 1,
pick_nums(C1, Remaining, SelectedFromRemaining).
通过使用select/3
,我能够生成其中没有选定元素的列表,然后我可以将其传递给下一个调用。这样我就不必担心我是否已经看过这个元素了。请注意,这样做可能会导致性能损失,但我们用更少的语句到达那里,并且(希望)更清楚我们正在尝试做什么。
如果我们愿意进一步依赖 SWI-Prolog,那么有一些内置库可以进一步简化这个过程。例如,该random
库有一个谓词,它将给我们一个列表的随机排列。我们可以使用它非常清楚地说明我们在做什么:
pick_nums(Count, From, To) :-
random_permutation(From, Scrambled),
append(To, _, Scrambled),
length(To, Count).
如果您总是希望将它与numlist
那里的另一个助手一起使用,那么它已经这样做了:
?- randset(3, 9, X).
X = [3, 4, 8].
?- randset(3, 9, X).
X = [2, 7, 9].
我希望这是足够的帮助。
我认为问题在于通话member(X,To)
。varTo
尚未实例化,To = [5|_G397]
如果首先从数字中选择 5,它将变为(使用调试器查看这些详细信息!)。
我可以看到更正代码的最简单方法是添加一个累加器:
pick_nums(Count, From, To) :-
pick_nums(Count, From, [], To).
pick_nums(0, _, Acc, Acc).
pick_nums(Count, From, Acc, To) :-
Count > 0, % avoid looping on backtracking
random_member(X, From),
( memberchk(X, Acc)
-> pick_nums(Count, From, Acc, To)
; C1 is Count-1,
pick_nums(C1, From, [X|Acc], To)
) .
我还添加了一个“守卫”以避免回溯循环