如何从列表中随机选择元素?

时间:2013-01-16 06:07:11

标签: prolog

以下实现将永远循环。

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

2 个答案:

答案 0 :(得分:2)

这里的问题是pick_nums(Count, From, To)内部pick_nums(Count, From, To)的呼叫相同。这里的条件逻辑似乎是在说“给我一个随机的From元素,但是如果我已经把它放在To中再试一次;否则,通常会重复。”这里的基本问题只是你的状况总是成功。

我确信对你的问题有一个更直接的解决方案,而不是从头开始重写,但对我来说,它似乎有些单一的Prolog。相反,让我们重新思考一下我们想要说的话,以防我们能够更直接地“逻辑地”说出来。你实际上想说的是“让我得到一个具有一定大小的From of的随机子集。”原始代码中的条件实际上只是“嘿,我已经看过这个元素所以让我们继续前进”的陪衬“但是在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].

我希望这是足够的帮助。

答案 1 :(得分:1)

我认为问题是电话member(X,To)。 var To尚未实例化,如果首先从数字中选择5,它将变为To = [5|_G397](使用调试器查看这些详细信息!)。

我能看到的纠正代码的最简单方法是添加累加器:

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

我还添加了一名警卫'避免循环回溯