创建任意分组的子列表

时间:2017-12-11 23:03:33

标签: perl6

我试图根据每个字符串的第一部分从字符串列表中对几个项目进行分组(例如,如果有标签,则在第一个标签之前的部分;如果没有标签,则排在整个字符串之前的部分标签)。

这有效:

use Test;

my @lines    = "A\tFoo"
             , "A\tBar"
             , "B"
             , "B"
             , "A\tBaz"
             , "B"
             ;

my @expected = ["A\tFoo", "A\tBar"]
             , ["B", "B"]
             , ["A\tBaz"]
             , ["B"]
             ;

my @result = group-lines(@lines);

is @result, @expected, "Grouped correctly";

sub group-lines (@records) {
    my @groups;
    my @current-records;

    my $last-type;
    for @records -> $record {

        my $type = $record.split("\t")[0];

        once { $last-type = $type }

        if $type ne $last-type {
            @groups.push: [@current-records];
            @current-records = ();
        }
        @current-records.push: $record;

        LAST { @groups.push: [@current-records] }
    }

    return @groups;
}

但它似乎很冗长。在Perl 6中没有更短的方法吗?请注意,我只想对原始列表中连续成员的项目进行分组。

(更新)组内的顺序很重要。

更新

这是一个更加数字化的例子。它根据第一个数字后续数字的可分性对数字进行分组。

#!/bin/env perl6
use Test;

my @numbers = 2, 4, 6, 3, 6, 9, 12, 14;

my @expected = [2, 4, 6], [3, 6, 9, 12], [14];

my @result = group-nums(@numbers);

is @result, @expected, "Grouped correctly";

sub group-nums (@numbers) {
    my @groups;
    my @current-group;

    my $denominator = @numbers[0];

    for @numbers -> $num {

        if $num % $denominator {
            @groups.push: [@current-group];
            @current-group = ();
        }
        @current-group.push: $num;

    }
    @groups.push: [@current-group];

    return @groups;
}

2 个答案:

答案 0 :(得分:4)

如果您希望元素出现在多个类别中,则可以使用categorize(或categorize-listclassify版本。由于您的分组是动态的,根据以前的密钥,使用state变量来记住以前的变化。第二个例子很简单,因为虽然顺序很重要,但不会阻止它重新添加元素到旧组:

my @numbers = <2 4 6 3 6 9 12 14>;
@numbers.classify: {
  state $denom = $_; if $_ !%% $denom { $denom = $_ }; $denom;
};
# result: {2 => [2 4 6], 3 => [3 6 9 12], 14 => [14]}

您的第一个示例需要将每个分组与之前的分组区分开来,因此快速而肮脏的方法是为每个组编制索引,这样您就可以拥有两个A组:

my %result = @lines.classify: {
  state $index = 0; # first group is group 0
  state $prefix = .split("\t")[0]; # The first prefix is based on the first string
  if !.starts-with($prefix) {
    $prefix = .split("\t")[0]; # This is a new prefix. Remember it.
    ++$index; # start a new group
  };
  ($index<> => $prefix<>); # Classify this element with a decontainerized pair. See note.
};
# result: {0      A => [A Foo A   Bar], 1 B => [B B], 2   A => [A Baz], 3 B => [B]}
say %result.values; # output: ([B] [B B] [A   Baz] [A Foo A   Bar])

你需要这些有序吗?由于这两种方法使用哈希来存储数据,因此结果是无序的。

注意:我使用<>运算符显式去除了Pair中用作分类值的值。由于此值是散列键,因此在不进行去整合的情况下,对象ID(技术上是.WHICH值)用于散列,您会发现给定$one = 1(a => 1).WHICH !eqv (a => $one).WHICH。因此,您删除容器,以便将该对视为一对普通值,这些值将具有相同的哈希键。

注2:分类键可以是列表,这将导致嵌套的数据结构。您不需要对钥匙进行去包容,您不必担心忘记订单。唯一的烦恼是输出中的额外嵌套级别。要获得嵌套结果,您的分类键将为($index, $prefix)

答案 1 :(得分:3)

这是一个功能灵感的解决方案,虽然可能有点复杂:

use Test;

my @lines    = "A\tFoo"
             , "A\tBar"
             , "B"
             , "B"
             , "A\tBaz"
             , "B"
             ;

my @expected = ["A\tFoo", "A\tBar"]
             , ["B", "B"]
             , ["A\tBaz"]
             , ["B"]
             ;

my @eq = @lines.map(*.split("\t")[0]).rotor(2 => -1).map({ [eq] .list});
my @result = [@lines[0],],;
for @lines[1..*] Z @eq -> ($line, $eq) {
    @result.push([]) unless $eq;
    @result[*-1].push: $line;
}

plan 1;
is-deeply @result, @expected;

如果前一个元素与当前元素具有相同的前缀,则@eq包含每个位置(第一个除外)True

但我们并没有假装Lisp是唯一的真神,carcdr是她的先知,我们可以简单地通过使用数组索引访问我们需要时的前一个元素:

my @result;
for @lines.kv ->  $idx, $elem {
    @result.push([]) if $idx == 0 || $elem.split("\t")[0] ne @lines[$idx-1].split("\t")[0];
    @result[*-1].push: $elem;
}

plan 1;
is-deeply @result, @expected;