列表比较

时间:2008-09-16 00:53:59

标签: list perl

我在采访中使用这个问题,我想知道最佳解决方案是什么。

编写一个带有 n 列表的Perl子,然后返回2 ^ n -1列表,告诉您哪些项目在哪些列表中;也就是说,哪些项目仅在第一个列表,第二个列表,第一个和第二个列表以及列表的所有其他组合中。假设 n 相当小(小于20)。

例如:

list_compare([1, 3], [2, 3]);
  => ([1], [2], [3]);

此处,第一个结果列表给出了仅在列表1中的所有项目,第二个结果列表给出了仅在列表2中的所有项目,第三个结果列表给出了两个列表中的所有项目。

list_compare([1, 3, 5, 7], [2, 3, 6, 7], [4, 5, 6, 7])
  => ([1], [2], [3], [4], [5], [6], [7])

这里,第一个列表给出了仅在列表1中的所有项目,第二个列表给出了仅在列表2中的所有项目,第三个列表给出了列表1和2中的所有项目,如第一个例子。第四个列表给出了仅在列表3中的所有项目,第五个列表给出了仅在列表1和3中的所有项目,第六个列表给出了仅在列表2和3中的所有项目,第七个列表给出了所有项目在所有3个列表中都有。

我经常将这个问题作为 n = 2的问题子集的后续内容。

解决方案是什么?

跟进:列表中的项目是字符串。可能存在重复,但由于它们只是字符串,因此应在输出中压缩重复项。输出列表中的项目顺序无关紧要,列表本身的顺序也是如此。

3 个答案:

答案 0 :(得分:2)

您的解决方案可以简化一点。

在第一个循环中,您可以使用普通加法,因为您只使用单个位进行ORing,并且可以通过迭代索引来缩小$bit的范围。在第二个循环中,您可以从索引中减去1,而不是生成需要shift关闭的不必要的第0个输出列表元素,并且您不必要地迭代m * n次(其中m是输出的数量)列表和n是唯一元素的数量),迭代唯一元素会将迭代次数减少到n(这在m大于n的典型用例中是一个重要的胜利),会简化代码。

sub list_compare {
    my ( @list ) = @_;
    my %dest;

    for my $i ( 0 .. $#list ) {
        my $bit = 2**$i;
        $dest{$_} += $bit for @{ $list[ $i ] };
    }

    my @output_list;

    for my $val ( keys %dest ) {
        push @{ $output_list[ $dest{ $val } - 1 ] }, $val;
    }

    return \@output_list;
}

另请注意,一旦想到这一点,结果收集过程可以借助List::Part模块非常简洁地编写:

use List::Part;

sub list_compare {
    my ( @list ) = @_;
    my %dest;

    for my $i ( 0 .. $#list ) {
        my $bit = 2**$i;
        $dest{$_} += $bit for @{ $list[ $i ] };
    }

    return [ part { $dest{ $_ } - 1 } keys %dest ];
}

但请注意list_compare是一个可怕的名字。像part_elems_by_membership这样的东西要好得多。此外,Ben Tilly指出的问题中的不精确性需要纠正。

答案 1 :(得分:1)

首先,我想指出,没有答案根本不起作用。尝试运行它,并查看Data :: Dumper中的输出以验证。

那就是说,你的问题并不恰当。看起来你正在使用集合作为数组。你想如何处理重复?您想如何处理复杂的数据结构?你想要什么样的元素?为了方便起见,我假设答案是压缩重复,可以对复杂的数据结构进行字符串化,顺序无关紧要。在这种情况下,以下是一个非常恰当的答案:

sub list_compare {
  my @lists = @_;

  my @answers;
  for my $list (@lists) {
    my %in_list = map {$_=>1} @$list;
    # We have this list.
    my @more_answers = [keys %in_list];
    for my $answer (@answers) {
      push @more_answers, [grep $in_list{$_}, @$answer];
    }
    push @answers, @more_answers;
  }

  return @answers;
}

如果您想调整这些假设,则需要调整代码。例如,不能压缩复杂的数据结构而不是压缩重复数据可以通过以下方式完成:

sub list_compare {
  my @lists = @_;

  my @answers;
  for my $list (@lists) {
    my %in_list = map {$_=>1} @$list;
    # We have this list.
    my @more_answers = [@$list];
    for my $answer (@answers) {
      push @more_answers, [grep $in_list{$_}, @$answer];
    }
    push @answers, @more_answers;
  }

  return @answers;
}

然而,这是使用数据结构的字符串化来检查一个中存在的东西是否存在于另一个中。放宽这种状况需要更多的工作。

答案 2 :(得分:0)

这是我的解决方案:

构造一个哈希,其键是输入列表中所有元素的并集,值是位串,如果元素存在于列表 i中,则设置位 i 。位串使用按位或。然后,通过迭代哈希的键来构造输出列表,将键添加到关联的输出列表。

sub list_compare {
    my (@lists) = @_;
    my %compare;
    my $bit = 1;
    foreach my $list (@lists) {
        $compare{$_} |= $bit foreach @$list;
        $bit *= 2; # shift over one bit
    }


    my @output_lists;
    foreach my $item (keys %compare) {
        push @{ $output_lists[ $compare{$item} - 1 ] }, $item;
    }

    return \@output_lists;

}

更新为包含亚里士多德建议的反向输出列表生成