借助二进制表示生成powerset

时间:2011-12-26 17:52:11

标签: binary erlang subset powerset gray-code

我知道“一个powerset只是0到2 ^ N-1之间的任何数字,其中N是集合成员的数量,而二进制表示中的一个表示存在相应的成员”。

Hynek -Pichi- Vychodil

我想使用从二进制表示到实际集合元素的映射生成一个powerset。

我如何使用Erlang进行此操作?

我尝试修改this,但没有成功。

UPD:我的目标是编写一个迭代算法,在不保持堆栈的情况下生成集合的powerset。我倾向于认为二进制表示可以帮助我。

Here是Ruby中的成功解决方案,但我需要在Erlang中编写它。

UPD2: Here是伪代码的解决方案,我想在Erlang中做类似的事情。

1 个答案:

答案 0 :(得分:3)

首先,我要注意,对于Erlang,递归解决方案并不一定意味着它将消耗额外的堆栈。当一个方法是尾递归时(即它最后做的是递归调用),编译器会重新编写它来修改参数,然后跳转到方法的开头。这对于函数式语言来说是相当标准的。

要生成所有数字A到B的列表,请使用库方法lists:seq(A, B)

要将值列表(例如列表从0到2 ^ N-1)转换为另一个值列表(例如从其二进制表示生成的集合),请使用lists:map或{{ 3}}

不是将数字拆分成二进制表示,您可能需要考虑转换它并检查是否通过生成电源列表在每个M值(0到2 ^ N-1)中设置相应的位 - 的-2-位掩码。然后,您可以执行二进制AND以查看该位是否已设置。

将所有这些放在一起,你得到一个解决方案,如:

generate_powerset(List) ->
    % Do some pre-processing of the list to help with checks later.
    % This involves modifying the list to combine the element with
    % the bitmask it will need later on, such as:
    % [a, b, c, d, e] ==> [{1,a}, {2,b}, {4,c}, {8,d}, {16,e}]
    PowersOf2 = [1 bsl (X-1) || X <- lists:seq(1, length(List))],
    ListWithMasks = lists:zip(PowersOf2, List),

    % Generate the list from 0 to 1^N - 1
    AllMs = lists:seq(0, (1 bsl length(List)) - 1),

    % For each value, generate the corresponding subset
    lists:map(fun (M) -> generate_subset(M, ListWithMasks) end, AllMs).
    % or, using a list comprehension:
    % [generate_subset(M, ListWithMasks) || M <- AllMs].

generate_subset(M, ListWithMasks) ->
    % List comprehension: choose each element where the Mask value has
    % the corresponding bit set in M.
    [Element || {Mask, Element} <- ListWithMasks, M band Mask =/= 0].

但是,您也可以使用尾递归来实现相同的功能,而不会占用堆栈空间。它也不需要在0到2 ^ N-1之间生成或保持列表。

generate_powerset(List) ->
    % same preliminary steps as above...
    PowersOf2 = [1 bsl (X-1) || X <- lists:seq(1, length(List))],
    ListWithMasks = lists:zip(PowersOf2, List),
    % call tail-recursive helper method -- it can have the same name
    % as long as it has different arity.
    generate_powerset(ListWithMasks, (1 bsl length(List)) - 1, []).

generate_powerset(_ListWithMasks, -1, Acc) -> Acc;
generate_powerset(ListWithMasks, M, Acc) ->
    generate_powerset(ListWithMasks, M-1,
                      [generate_subset(M, ListWithMasks) | Acc]).

% same as above...
generate_subset(M, ListWithMasks) ->
    [Element || {Mask, Element} <- ListWithMasks, M band Mask =/= 0].

请注意,在生成子集列表时,您需要将新元素放在列表的开头。列表是单链接且不可变的,因此如果您想将元素放在任何位置而不是开头,则必须更新“下一个”指针,这会导致列表被复制。这就是帮助函数将Acc列表放在尾部而不是Acc ++ [generate_subset(...)]的原因。在这种情况下,由于我们倒计时而不是向上,我们已经倒退了,所以最终会以相同的顺序出现。

所以,总之,

  1. 在Erlang中循环是通过尾递归函数或使用lists:map的变体来惯用的。
  2. 在许多(大多数?)函数式语言中,包括Erlang,尾递归不会消耗额外的堆栈空间,因为它是使用跳转实现的。
  3. 出于效率原因,列表构造通常是向后完成的(即[NewElement | ExistingList])。
  4. 您通常不希望在列表中找到第N个项目(使用list comprehension),因为列表是单链接的:它必须一遍又一遍地重复列表。相反,找到一种迭代列表的方法,例如我如何预处理上面的位掩码。