awk(またはPerl)内の一意の関連付け値の数を計算します。

awk(またはPerl)内の一意の関連付け値の数を計算します。

私は見つけた「列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の固有の関連値の数が印刷されます。

入力は最初のフィールドでソートされますが、他のフィールドでソートすることはできません(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++}上記と同じですが、3番目のフィールドとd変数があります。
  • {l=$1}l最初のフィールドに(次の繰り返しのために..上)
  • END{p()}最後の行を処理した後、awk最後のブロックの値を印刷する必要があります。

与えられた入力に基づいて、出力は次のようになります。

apple 3 2
banana 4 5
cucumber 2 3

答え2

私は空白と説明的な変数名が好きです。また何を言うのですか?とても久しぶりに文を書いてシャバンawkも忘れてしまいましたね。-fしかし、こうすると本当に禪状態にあるような感じがします。 俳句コード。

私は最小限のコーディングロジックがあるので、このソリューションが大好きです。配列インデックスを繰り返すforループは2つだけです。 3つの部分で構成されるステップforループ、ifステートメント、および明示的な値の比較はありません。これらすべては、ソフトウェアの欠陥(バグ)と統計的に相関があります。興味深いことに、明示的な割り当てはなく、1つの数学的操作と数の増加だけがあります。これはすべて言語機能の最大限の活用を示すものだと思います。

何か抜けたような気がしますが、まだ抜け穴が見つかりませんでした。

ご意見をお知らせください。コメントと建設的な批判を求めてください。このスクリプトのパフォーマンスに関する考慮事項について聞きたいです。

#!/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

この関数は、入力レコード(フィールド1と2、またはフィールド1と3)で見つかった2つのフィールド値でインデックス付き配列を取得し、そのcountレコードを含む最初のフィールドでインデックス付けされた内部呼び出し配列を返します。一意のフィールド値の数。 2番目のインデックス累積列:seenunique_count

function count(seen, unique_count) {

このcount関数は配列のインデックスを繰り返しますseen

    for (ij in seen) {

インデックスを2つの元の値(フィールド1とフィールド2またはフィールド3)に分割します。

        split(ij, fields, SUBSEP)

フィールド1で索引付けされた要素の数を増やします。

        ++unique_count[fields[1]]
    }
}

各入力ラインで、最初のフィールドと2番目または3番目のフィールドにインデックス付きの空の配列要素(まだ存在しない場合)を作成します。seen2計算する各フィールド番号に対して別々の配列(合計)を維持します。seen3与えられた列(2または3)の各固有値には1つの配列要素しかありません。

{
    seen2[$1,$2]
    seen3[$1,$3]
}

データの最後に、各列に表示される固有フィールドの数を数えます。

END {

入力から蓄積された配列をcount関数に渡し、一意のフィールド数を受け取るか入力しますcount2count3

    count(seen2, count2)
    count(seen3, count3)

count2または、配列を段階的に実行しcount3(両方とも各行の最初のフィールドを持っているため、どちらも問題ありません)、フィールド1を印刷してから、フィールド1を含む各行に見つかった一意の値の数を印刷します。

    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()
}

+ 0printcounts関数の重要な点は、数値が常に印刷されるようにすることです。実際のユースケースには、カンマフィールドの区切り文字と空のフィールドを無視することが含まれているため、実際にゼロカウントを達成できるためです。)

関連情報