データの例を以下に示します。最初の2つの列はIDで、3番目の列は頻度です。
1 2 99
2 3 62
4 5 80
4 4 98
5 5 79
6 1 98
最初と2番目の列は、同一のIDまたは重複したIDです。たとえば、1、2、3、6は同じ人です。 1==2、2==3などなので、1==3です。したがって、データをこのように分割できます。
人1
1 2 99
2 3 62
6 1 98
人2
4 5 80
4 4 98
5 5 79
上記のようにデータをどのように分割できますか?ここでは、行全体を比較する必要があります。これは私にとって混乱した部分です。次に、各グループ内の3番目の列の頻度に基づいてIDを選択したいと思います。ここでは、他のファイルからこれらのIDを削除するために最も頻度の低い動物を取得します。推奨される最終出力は次のとおりです。
2 3 62
6 1 98
4 5 80
5 5 79
私は答えを探していますが、私にとっては複雑に見えます。たぶん、データを分割するよりも良い方法があるかもしれません。どんなアイデアでもお願いします。
答え1
最初の質問を解決するには、すべてのUnixシステムのbourne派生シェルでawk + sortを使用して入力を分割する方法は次のとおりです(私はshebangでbashを使用しますが、bashである必要はありません)。
$ cat tst.sh
#!/usr/bin/env bash
awk '{ print $0 ORS $2, $1, $3 }' "${@:--}" |
sort -n -k1,1 -k2,2 |
awk '
!seen[($1 > $2 ? $1 FS $2 : $2 FS $1)]++ {
out = ""
for ( i=1; i<=2; i++ ) {
if ( $i in map ) {
out = map[$i]
break
}
}
if ( out == "" ) {
out = "person_" (++numPeople)
}
for ( i=1; i<=2; i++ ) {
map[$i] = out
}
print >> out
close(out)
}
'
次の行を含むように公開した入力例を変更する必要があります。私のコメント分割が機能していることを実際にテストするには、次の手順を実行します。
$ cat file
1 2 99
2 3 62
4 5 80
4 4 98
5 5 79
6 1 98
7 8 99
9 10 98
9 7 97
$ ./tst.sh file
$ head person*
==> person_1 <==
1 2 99
1 6 98
2 3 62
==> person_2 <==
4 4 98
4 5 80
5 5 79
==> person_3 <==
7 8 99
7 9 97
9 10 98
1 2 x
上記はと同じであるため、各行の最初の2つのIDの順序は重要ではないと仮定します2 1 x
。
答え2
「分割データ」部分への部分的な答えです。 GNUawk
機能を使用してください。配列の配列だからgawk
解決策があります。
1列と2列のIDを重ねてグループ化し、各人に一意のIDを付与し、次の名前のファイルで印刷しますperson_ID
。
gawk '
#id=array of arrays with unique ids UID and alias
#IDs aID as taken from the file: id[UID][aID]
#manually create first entry in line 1
NR==1 { id[1][$1]=1 ; id[1][$2]=1 ; next }
#on other input: scan array id for a match in aIDs
#use related UID it if match is found
FNR==NR {
for (i=1 ; i<=length(id) ; i++ ) {
if ($1 in id[i] || $2 in id[i] ) {
id[i][$1]=i ; id[i][$2]=i ; next }
}
#if no match was found, create a new UID:
new=length(id)+1
id[new][$1]=new ; id[new][$2]=new
}
#rerun through id arrays to check for doubles:
FNR!=NR && !b {
for ( i in id ) {
for ( j in id[i] ) {
if ( seen[j] ) {
for ( k in id[i] ) { id[seen[j]][k]=id[seen[j]][k] }
delete id[i]
}
else { seen[j]=i }
}
}
delete seen
#adjust UIDs as they may be out of order now to new id array nid,
#delete old id array:
for ( i in id ) {++n ; for (j in id[i]) { nid[n][j]=id[i][j] } }
delete id
b=!b
}
#write to separate files per UID
FNR!=NR {
for (i in nid) {
if ($1 in nid[i] || $2 in nid[i] ) { print > "person_"i }
}
}
#This is just to print the aID vs UID map
END {
for (i in nid) {
print "aIDs for person UID=",i ; b=1
for (j in nid[i]) {
if (b) {printf j ; b=0}
else {printf ","j}
}
print ""
}
}
' infile infile
ここで、行削除の問題には、次の非常に簡単なアプローチをお勧めします。
上記で作成されたファイルをインポートし、person_i
各ファイルのフィールド3の最小値を持つ行を選択します。次の行をdelete_me
ファイルに書き込み、元のファイルでリバース grep を使用します。
for file in person_* ; do
sort -n -k3 ${file} | head -n1
done > delete_me
grep -xvf delete_me original
sort
同一または類似の番号の場合は最小限の選択のみで行われるため、別途の精製作業は行われません。一致に完全な行を含める必要があるかどうかを確認するには、-x
forを使用します(そうでない場合はと一致します)。grep
1 2 3
1 2 3
1 2 33
次のバリアントでは、ID ごとに人をグループ化し、列 3 で最大値を持つ行をフィルタリングして一意の人を見つけます。入力ファイルを2番目に読み込むと、最大行がない行のみが印刷されます。したがって、追加ファイルがない単一のスクリプトソリューションは次のとおりです。
#id=array of arrays with unique ids UID and alias
#IDs aID as taken from the file: id[UID][aID]
#manually create first entry in line 1, print to "person_1"
NR==1 { id[1][$1]=1 ; id[1][$2]=1 ; next }
#manually create first entry for line deletion selection
NR==1 { max[1]=$3 ; line[1]=$0 }
#on other input: scan array id for a match in aIDs
#use related UID it if match is found
FNR==NR {
for (i=1 ; i<=length(id) ; i++ ) {
if ($1 in id[i] || $2 in id[i] ) {
id[i][$1]=i ; id[i][$2]=i
#select line for deletion
if ($3>=max[i]) { max[i]=$3 ; line[i]=$0 }
; next
}
}
#if no match was found, create a new UID:
new=length(id)+1
id[new][$1]=new ; id[new][$2]=new
max[new]=$3 ; line[new]=$0
}
#rerun through id arrays to check for doubles:
FNR!=NR && !b {
for ( i in id ) {
for ( j in id[i] ) {
if ( seen[j] ) {
for ( k in id[i] ) { id[seen[j]][k]=id[seen[j]][k] }
delete id[i]
#adjust line deletion selection:
if (max[seen[j]]>=max[i]) { delete line[i]} else {delete line[seen[j]]}
}
else { seen[j]=i }
}
}
delete seen
#adjust UIDs as they may be out of order now:
for ( i in id ) {++n ; for (j in id[i]) { nid[n][j]=id[i][j] } }
delete id
#swap line deletion marker from array element to index for better processability
for (i in line) { line[line[i]]="" ; delete line[i]}
delete max
#set flag for running this block once only
b=!b
}
#write to separate files per UID (commented out)
#FNR!=NR {
# for (i in nid) {
# if ($1 in nid[i] || $2 in nid[i] ) {print > "person_"i}
# }
#}
#print lines that have not been selected for deletion
#to STDOUT use the alternative to print it to a separate file
FNR!=NR && ! ($0 in line)
#alternative:
#FNR!=NR && ! ($0 in line) {print > "myoutfile"}
#This is just to print the aID vs UID map
END { print "\n------\nUID vs aID map:\n"
for (i in nid) {
print "aIDs for person UID=",i ; b=1
for (j in nid[i]) {
if (b) {printf j ; b=0}
else {printf ","j}
}
print ""
}
}
次に実行しますawk -f script.awk infile infile
。つまり、infileを2回読みます。