【问题标题】:Generate a powerset with the help of a binary representation借助二进制表示生成幂集
【发布时间】:2011-12-26 17:52:10
【问题描述】:

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

(Hynek -Pichi- Vychodil)

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

如何使用 Erlang 做到这一点?

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

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

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

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

【问题讨论】:

  • 问题有点不清楚。给定集合的所有可能子集的集合中的幂集。格雷码是从一个数字到另一个数字的映射。这两个术语有什么共同点?你想达到什么目的?你能举个例子吗?
  • @werewindle,我已经更新了问题。
  • 我明白了。所以你需要两个函数:一个会产生从 0 到 2^N-1 的数字,另一个对每个数字执行以下操作:对于数字的每个 (nth) 位,它将测试它是否被设置。如果设置了位,则必须将原始集合中的相应(第 n 个)元素附加到结果集中。
  • 是的,类似的,但不幸的是,我不知道如何制作第二个函数,即如何将十进制数转换为二进制表示;然后将所有 1 与 set 的相应子集映射并将这些子集添加到结果中。

标签: binary erlang subset powerset gray-code


【解决方案1】:

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

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

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

与其将一个数字拆分为它的二进制表示,不如考虑将其转过来,并通过生成一个幂列表来检查每个 M 值(在 0 到 2^N-1 中)中是否设置了相应的位of-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 个项目(使用 lists:nth),因为列表是单链接的:它必须一遍又一遍地迭代列表。相反,找到一种方法来迭代列表一次,例如我如何预处理上面的位掩码。

【讨论】:

  • 谢谢!如何修改它以便能够将每个子集转储到文件/ETC/任何地方? (我的内存使用量还在不断增长,我猜是因为 Acc 在函数返回之前累积了所有结果)
  • 对。它将把这一切都留在记忆中,直到最后。如果您使用尾递归版本,您可以完全删除 Acc 参数并在递归步骤之前写入文件。如:generate_powerset(ListWithMasks, M) -&gt; write_to_file(generate_subset(...)), generate_powerset(ListWithMasks, M-1).
猜你喜欢
  • 2012-03-04
  • 2019-05-03
  • 2014-09-19
  • 2011-07-01
  • 2016-11-07
  • 2021-09-02
  • 1970-01-01
  • 1970-01-01
  • 2014-01-22
相关资源
最近更新 更多