对于第一列中匹配的行,使用第二列创建“X see Also Y”列表

对于第一列中匹配的行,使用第二列创建“X see Also Y”列表

对于 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]
    }
}

请注意,使用inin 访问关联数组的索引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 }'

相关内容