创建任意分组的子列表
Creating sublists of arbitrary grouping
我正在尝试根据每个字符串的第一部分(即第一个制表符之前的部分,如果有制表符或整个字符串,如果没有制表符)对字符串列表中的几个项目进行分组).
这个有效:
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;
}
您可以使用 categorize(或 categorize-list
,如果您希望元素出现在多个类别中,则可以使用 classify
变体)。由于您的分组是动态的,具体取决于之前出现的键,请使用 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)
.
这是一些受功能启发的解决方案,尽管可能有点令人费解:
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 是独一真神,car
和 cdr
是她的先知,我们可以内联该决定,只需使用数组索引访问我们需要时的前一个元素:
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;
我正在尝试根据每个字符串的第一部分(即第一个制表符之前的部分,如果有制表符或整个字符串,如果没有制表符)对字符串列表中的几个项目进行分组).
这个有效:
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;
}
您可以使用 categorize(或 categorize-list
,如果您希望元素出现在多个类别中,则可以使用 classify
变体)。由于您的分组是动态的,具体取决于之前出现的键,请使用 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)
.
这是一些受功能启发的解决方案,尽管可能有点令人费解:
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 是独一真神,car
和 cdr
是她的先知,我们可以内联该决定,只需使用数组索引访问我们需要时的前一个元素:
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;