我已经找到了“如何打印第 1 列中唯一值出现的增量计数”,这与我的问题类似,但答案不足以满足我的目的。
首先让我阐明我想做的事:
# Example input
apple abc jkl
apple xyz jkl
apple abc xyz
apple qrs xyz
apple abc jkl
banana abc lmno
banana lmnop xyz
banana lmnopq jkl
banana abc jkl
banana lmnop pqrs
banana abcdefg tuv
cucumber abc lmno
cucumber abc jkl
cucumber abc xyz
cucumber abcd jkl
cucumber abc jkl
cucumber abc lmno
# Desired output
apple 3 2
banana 4 5
cucumber 2 3
因此,对于字段 1 的每个单独值,打印该字段以及字段 2 和字段 3 的唯一关联值的计数。
输入按第一个字段排序,但不允许按其他字段排序(并且没有任何用处,因为第二和第三个字段都需要处理)。
我更愿意在awk
;在 perl 中可能要容易得多,我也有兴趣学习如何做到这一点,但我正在处理 awk 脚本,我不想重写整个事情。
我想出了一种方法作品,但相当冗长,对我来说似乎很老套。我会将其作为答案发布(当我回到办公室时),但很想看到任何实际的好的接近。 (我不认为我的“好”。)
答案1
和awk
:
awk 'function p(){print l,c,d; delete a; delete b; c=d=0}
NR!=1&&l!=$1{p()} ++a[$2]==1{c++} ++b[$3]==1{d++} {l=$1} END{p()}' file
解释:
function p()
:定义一个名为 的函数p()
,该函数打印值并删除使用的变量和数组。NR!=1&&l!=$1
如果它不是第一行并且变量 l 等于第一个字段$1
,则运行该p()
函数。++a[$2]==1{c++}
a
如果索引数组的元素值的增量$2
等于1
,则首先看到该值,因此递增c
变量。元素之前++
返回新值,因此在与 进行比较之前会导致增量1
。++b[$3]==1{d++}
与上面相同,但有第三个字段和d
变量。{l=$1}
到l
第一个字段(用于下一次迭代..上面)END{p()}
处理最后一行后,awk
必须打印最后一个块的值
根据您给定的输入,输出为:
apple 3 2
banana 4 5
cucumber 2 3
答案2
我喜欢空格和描述性变量名称。还有什么可说的呢?好久没写这么多了awk
,我什至都忘记了-f
shebang的事情。然而,当我这样做时,我真的感觉自己处于禅宗之中。 俳句代码。
我喜欢这个解决方案,因为编码逻辑最少。只有两个 for 循环迭代数组索引。没有 3 部分步进for
循环,没有if
语句,没有显式的值比较。所有这些事情在统计上都与软件缺陷(错误)相关。有趣的是,没有明确的赋值,只有一种数学运算,即计数增量。我认为这都表明了语言功能的最大程度的利用。
我感觉好像缺少了一些东西,但我还没有找到其中的漏洞。
请给出意见。要求提出意见和建设性批评。我想听听这个脚本的性能考虑因素。
#!/usr/bin/awk -f
function count(seen, unique_count) {
for (ij in seen) {
split(ij, fields, SUBSEP)
++unique_count[fields[1]]
}
}
{
seen2[$1,$2]
seen3[$1,$3]
}
END {
count(seen2, count2)
count(seen3, count3)
for (i in count3) {
print i, count2[i], count3[i]
}
}
注解
我想这个脚本的一个独特功能是seen2
和seen3
数组不包含数据,只包含索引。这是因为我们只计算唯一值,因此,唯一重要的是这些值已经被看到,我们不关心它们出现了多少次。
#!/usr/bin/awk -f
该count
函数采用一个数组,seen
由输入记录中遇到的 2 个字段值(字段 1 和 2,或字段 1 和 3)索引,并返回一个内部调用的unique_count
由第一个字段索引的数组,其中包含该记录的唯一字段值的计数。第二个索引累积的列:
function count(seen, unique_count) {
该count
函数迭代数组的索引seen
:
for (ij in seen) {
将索引拆分为两个原始值:字段 1 以及字段 2 或字段 3:
split(ij, fields, SUBSEP)
增加字段 1 索引的元素的计数:
++unique_count[fields[1]]
}
}
在遇到的每个输入行上,我们创建一个空数组元素(如果尚不存在),并按第一个字段以及第二个或第三个字段进行索引。为每个要计数的字段编号保留一个单独的数组 (seen2
和)。seen3
给定列(2 或 3)中的每个唯一值只有一个数组元素:
{
seen2[$1,$2]
seen3[$1,$3]
}
在数据末尾,计算每列中看到的唯一字段的数量:
END {
将从输入累积的数组传递给count
函数,并接收count2
或count3
填充唯一字段计数。
count(seen2, count2)
count(seen3, count3)
逐步遍历count2
或count3
数组(哪个并不重要,因为它们都具有每行的第一个字段),并打印字段一,然后是为包含字段一的每行找到的唯一值的计数:
for (i in count3) {
print i, count2[i], count3[i]
}
}
单行版本
awk 'function c(s,u){for(x in s){split(x,f,SUBSEP); ++u[f[1]];}}
{a[$1,$2]; b[$1,$3];} END {c(a,d); c(b,e); for(i in d){print i,d[i],e[i];}}'
答案3
perl -lane 'undef $h{ $F[0] }[ $_ - 1 ]{ $F[$_] } for 1,2
}{
for my $k (keys %h) {
print join " ", $k, map scalar keys $_, @{ $h{$k} }
}' < input
基本上,您创建一个像这样的哈希:
'apple' => [
{
'abc' => undef,
'xyz' => undef,
'qrs' => undef
},
{
'jkl' => undef,
'xyz' => undef
}
],
'banana' => [
{
'abcdefg' => undef,
'lmnop' => undef,
'lmnopq' => undef,
'abc' => undef
},
{
'lmno' => undef,
'pqrs' => undef,
'tuv' => undef,
'jkl' => undef,
'xyz' => undef
}
],
'cucumber' => [
{
'abcd' => undef,
'abc' => undef
},
{
'lmno' => undef,
'jkl' => undef,
'xyz' => undef
}
]
然后计算每个内部哈希的键。
答案4
正如所承诺的,这是我的方法做过在写这个问题之前先搞清楚。它有效并且或许这是一个很好的方法,但对于这个看似简单的任务来说似乎过于复杂。现在看来,其实也没有那么糟糕。 :)
function printcounts() {
printf "%s", currentf1
for (i = 2; i <= 3; i++ ) {
printf "%s", FS countuniq [ i ]
}
printf "\n"
}
function resetvars() {
delete already_seen_value
for ( i = 2; i <= 3; i++ ) {
countuniq [ i ] = 0
}
}
$1 != currentf1 {
if ( NR != 1 ) {
printcounts()
}
currentf1 = $1
resetvars()
}
{
for ( i = 2; i <= 3; i++ ) {
if ( ! already_seen_value [ i ":" $i ] ) {
already_seen_value [ i ":" $i ] ++
countuniq [ i ] ++
}
}
}
END {
printcounts()
}
并基于修改混沌的答案:
function printcounts() {
printf "%s", currentf1
for (i = 2; i <= 3; i++ ) {
printf "%s", FS countuniq [ i ] + 0
}
printf "\n"
# Reset vars
delete seenthis
delete countuniq
}
NR != 1 && currentf1 != $1 {
printcounts()
}
{
for ( i = 2; i <= 3; i++ ) {
if ( ++ seenthis [ i ":" $i ] == 1 ) {
countuniq [ i ] ++
}
}
currentf1 = $1
}
END {
printcounts()
}
(+ 0
printcounts 函数中的 是确保始终打印数字,因为实际用例涉及逗号字段分隔符并忽略空字段,因此实际上可以实现零计数。)