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에 대한 고유한 관련 값의 개수를 인쇄합니다.

입력은 첫 번째 필드를 기준으로 정렬되지만 다른 필드를 기준으로 정렬할 수는 없습니다(두 번째 및 세 번째 필드를 모두 처리해야 하므로 소용이 없습니다).

나는 그것을 선호합니다 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하지만 이렇게 하면 정말 선(禪) 상태에 있는 것 같은 느낌이 듭니다. 하이쿠 코드.

나는 최소한의 코딩 논리가 있기 때문에 이 솔루션을 좋아합니다. 배열 인덱스를 반복하는 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

이 함수는 입력 레코드(필드 1과 2 또는 필드 1과 3)에서 발견된 2개의 필드 값으로 인덱싱된 count배열을 가져와 해당 레코드가 포함된 첫 번째 필드로 인덱싱된 내부 호출 배열을 반환합니다. 고유한 필드 값의 개수 . 두 번째 인덱스 누적 열:seenunique_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(둘 다 각 행의 첫 번째 필드를 갖고 있으므로 어느 것이든 상관 없음) 필드 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 함수의 중요한 점은 숫자가 항상 인쇄되도록 하는 것입니다. 실제 사용 사례에는 쉼표 필드 구분 기호와 빈 필드 무시가 포함되어 실제로 0 카운트가 달성될 수 있기 때문입니다.)

관련 정보