对于 wikidata,我想创建 QuickStatments 来为具有相同名称的堡垒分配 P1889 值。这可以简化为。
对于具有以下内容的文件:
Fort George,Q12
Fort George,Q56
Fort George,Q678
Fort Anne,Q3
我想要在第一列上进行精确匹配,然后以 X 参见 Y 的形式输出所有匹配对。
Q12 see also Q56
Q12 see also Q678
Q56 see also Q12
Q56 see also Q678
Q678 see also Q12
Q678 see also Q56
文件按第一列排序。这感觉像是一个 awk 问题,但我放弃了写一个
答案1
使用磨坊主( mlr
) 首先根据第一个字段折叠第二个字段(实际上不需要排序),然后循环折叠的第二个字段以打印字符串的组合:
$ mlr --csv -N nest --ivar : -f 2 then put -q 'a = splita($2, ":"); for (i in a) { for (j in a) { i != j { print i . " see also " . j } } }' file
Q12 see also Q56
Q12 see also Q678
Q56 see also Q12
Q56 see also Q678
Q678 see also Q12
Q678 see also Q56
运行后的中间结果nest
如下所示:
$ mlr --csv -N nest --ivar : -f 2 file
Fort George,Q12:Q56:Q678
Fort Anne,Q3
这个put
表达很漂亮:
a = splita($2, ":");
for (i in a) {
for (j in a) {
i != j { print i . " see also " . j }
}
}
您可以在 中遵循相同的模式awk
,但您必须首先假设您的输入是简单的CSV(即不包含嵌入的逗号或换行符等),并且它变得更加冗长:
$ awk -F , '{ d[$1] = d[$1] == "" ? $2 : d[$1] ":" $2 } END { for (k in d) { split(d[k],a,":"); for (i in a) for (j in a) if (i != j) printf "%s see also %s\n", a[i], a[j] } }' file
Q56 see also Q678
Q56 see also Q12
Q678 see also Q56
Q678 see also Q12
Q12 see also Q56
Q12 see also Q678
代码awk
打印得很漂亮:
{
d[$1] = d[$1] == "" ? $2 : d[$1] ":" $2
}
END {
for (k in d) {
split(d[k], a, ":")
for (i in a) for (j in a)
if (i != j) printf "%s see also %s\n", a[i], a[j]
}
}
请注意,使用in
in 访问关联数组的索引awk
并不保证任何特定的顺序。要修复顺序,请改用算术循环。我不会在这里展示它,因为它更加冗长。
答案2
使用任何支持(大多数都支持)的 awk delete array
,并且一次只在内存中存储一个 $1 的 $2 值:
awk '
BEGIN { FS=","; OFS=" see also " }
$1 != prev {
delete a
prev = $1
}
{
for ( i in a ) {
print $2, i ORS i, $2
}
a[$2]
}
' file
Q56 see also Q12
Q12 see also Q56
Q678 see also Q56
Q56 see also Q678
Q678 see also Q12
Q12 see also Q678
如果你的 awk 不支持的delete a
话得到一个新的 awk但您也可以split("",a)
在任何 awk 中使用。
sort
如果您想要不同的输出行顺序,请通过管道将输出连接到。
答案3
以下 perl 脚本使用哈希数组(“HoA”)数据结构(请参阅perldoc perldsc和紫苏醇及相关文件)。
简而言之,“HoA”是一个关联数组(又名“哈希”),其中每个元素存储一个包含零个或多个数据元素的索引数组(数组可以为空 - 在这种情况下,每个数组将至少有一个元素,因为除非有要添加的元素,否则不会创建它)。
#!/usr/bin/perl
while (<<>>) {
chomp;
my @F = split /\s*,\s*/;
push @{ $a{$F[0]} }, $F[1];
}
foreach my $k (sort keys %a) {
my @values = @{ $a{$k} };
foreach my $v1 (@values) {
foreach my $v2 (@values) {
print "$v1 see also $v2\n" unless $v1 eq $v2;
}
};
};
首先,它读取每行输入,去掉每行末尾的换行符,并将该行分割成@F
逗号数组(忽略可能与逗号相邻或不相邻的任何空格)。然后,它使用@F 的第一个元素作为a 散列的键%a
,并将@F 的第二个元素推入(即追加)到存储在%a 的该元素中的数组中。
当它读取所有输入时,它会迭代 %a 的键,然后迭代两次,嵌套地遍历这些键内数组中的值,仅当值不同时才打印行。
我已经对 %a 的键进行了排序,但这不是必需的 - 它只是保证输出的一致性,因为 perl 哈希本质上是无序的(就像在大多数语言中一样)。如果不进行排序,它们在每次运行中都会以看似随机的顺序出现。
您也可以对 @values 数组进行排序,但是(因为这些值是字母和数字的混合)除非您想实现自然排序算法(有时称为“版本排序”)或使用排序::自然或者排序::版本或其他自然排序模块之一CPAN。照原样,这些值将按照读取的顺序输出。
从技术上讲,@values
输出循环中的数组是不需要的。我可以只在and@{ $a{$k} }
循环中使用,但在我看来,这种方式更容易阅读和理解。$v1
$v2
将脚本另存为see-also.pl
并使其可执行后,示例输入的示例输出chmod
:
$ ./see-also.pl input.txt
Q12 see also Q56
Q12 see also Q678
Q56 see also Q12
Q56 see also Q678
Q678 see also Q12
Q678 see also Q56
答案4
您还可以使用datamash
对数据进行分组并使用 bash 大括号扩展来获取组合,例如:
<infile datamash -st, --output-delimiter=$'\t' -g1 collapse 2 |
cut -f2 |
while read; do
bash -c "printf '%s\n' {$REPLY}$'\t'{$REPLY}"
done |
awk '$1 != $2 { print $1 " see also " $2 }'