テキストファイルから2つ以上の単語(スペースで区切られていない)を含む行を削除する方法は? [閉鎖]

テキストファイルから2つ以上の単語(スペースで区切られていない)を含む行を削除する方法は? [閉鎖]

テキストファイルから2つ以上の単語(スペースで区切られていない)を含む行を削除する方法は?

文書には、これらの単語の「シングルバージョン」もあります。

たとえば、

alpha
beta
gama
alphabeta
zeta
gamabeta

出力は次のようになります。

alpha
beta
gama
zeta

編集する:私のファイルには150万行が含まれています。

答え1

かなり短いファイルの場合、行にERE演算子が含まれていないと仮定すると、次のようになります。

$ LC_ALL=C grep -vxE "($(paste -sd '|' file)){2,}" file
alpha
beta
gama
zeta

2つ以上の行シーケンスを含まない行を返しますfile

どのように機能するかは、grep次のコマンドを書くことです。

LC_ALL=C grep -vxE '(alpha|beta|gama|alphabeta|zeta|gamabeta){2,}' file

より大きなファイルの場合は、長さまたはパラメータ+環境(またはLinuxの単一パラメータ)制限に直面します。引数の代わりに標準入力を使用して正規表現を渡すことでこの問題を解決できますが-f -、それでも正規表現のサイズに制限があります。

perl代わりに、以下を使用してgrepより大きな入力を処理できます。

perl -le '
  chomp (@words = <>);
  $re = "^(" . join("|", map {qr{\Q$_\E}} @words) . "){2,}\\z";
  for (@words) {print unless m/$re/}' file

(これは上記の他の制限も防止します)。

各単語を他の単語と比較する必要があるため(おそらく2回以上)、とにかく時間がかかります。

答え2

これにより、ファイル内の2つの単語の組み合わせではなく、ファイル内のすべての単語が印刷されます。

$ awk '{one[NR]=$1} END{for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]; for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]}' file
alpha
beta
gama
zeta

コマンドを複数行に分けたい人のために:

awk '
    {
    one[NR]=$1
    }

    END{
        for (i=1;i<=length(one);i++)
            for (j=1;j<=length(one);j++)
                two[one[i] one[j]]
        for (i=1;i<=length(one);i++)
            if (!(one[i] in two))
                print one[i]
     }' file

他の例

同様の単語を含むファイルを考えてみましょう。しかし、時には個々の単語の前に組み合わせが現れることがあります。

$ cat file2
alphabeta
alpha
gammaalpha
beta
gamma

同じコマンドを実行しても、正しい結果が生成されます。

$ awk '{one[NR]=$1} END{for (i=1;i<=length(one);i++) for (j=1;j<=length(one);j++) two[one[i] one[j]]; for (i=1;i<=length(one);i++) if (!(one[i] in two)) print one[i]}' file2
alpha
beta
gamma

どのように動作しますか?

  • one[NR]=$1

    oneこれにより、キーが行番号でNR値がその行の単語である配列が生成されます。

  • END{...}

    中括弧内のコマンドは、ファイルの読み取りが完了した後に実行されます。このコマンドは2つのループで構成されています。最初のループは次のとおりです。

     for (i=1;i<=length(one);i++)
          for (j=1;j<=length(one);j++)
              two[one[i] one[j]]
    

    twoこれにより、ファイル内の2つの単語のすべての組み合わせで構成されるキーを含む配列が作成されます。

    2番目のループは次のとおりです。

      for (i=1;i<=length(one);i++)
          if (!(one[i] in two))
              print one[i]
    

    このループは、配列のキーとして表示されないファイル内のすべての単語を印刷しますtwo

短くてシンプルなバージョン

このバージョンはより短いコードを使用し、同じ単語を印刷します。欠点は、単語の順序が入力ファイルと同じであることは保証されないことです。

$ awk '{one[$1]} END{for (w1 in one) for (w2 in one) two[w1 w2]; for (w in one) if (!(w in two)) print w}' file1
gama
zeta
alpha
beta

メモリを節約する方法

大容量ファイルの場合、上記の方法でメモリオーバーフローが発生する可能性があります。このような場合は、次の点を考慮してください。

$ sort -u file | awk '{one[$1]} END{for (w1 in one) for (w2 in one) print w1 w2}' >doubles
$ grep -vxFf doubles file
alpha
beta
gama
zeta

これはsort -u、file1から重複した単語を削除し、という名前の二重単語を含むことができるファイルを作成するために使用されますdoubles。それから。grepfiledoubles

答え3

<file awk 'NF {print length "\t" $0}' | sort -k1n,1 | cut -f2- |
awk 'NR==1 {min=length}
(l=length) >= 2*min {
  delete k; # clear k array
  k[1];
  while (length(k))
    for (i in k) {
      for (j=l-i+1; j>=min; --j)
        if (substr($0,i,j) in seen) {
          if (i+j-1==l)
            next;
          k[i+j];
        }
      delete k[i];
    }
}
!seen[$0]++'

以前に見た行だけで構成された行は印刷されません。

すでに表示されている文字列に部分文字列があることを確認して動作します。

入力ファイルを行の長さに応じて、最短から最長までソートする必要があります。awk | sort | cutこの方法。

次のawkプログラムは最初に最短行の長さを記録します(として保存されますmin)。長さが次より小さい行は、対応する部分2*min文字列をチェックする必要はありません。代わりに、seen配列ハッシュに追加して印刷することができます(!seen[$0]++重複していない項目を印刷するための条件として使用されます。awk '!a[$0]++' はどのように機能しますか?)。min部分文字列を確認するときは、カットオフの長さとしても使用できます。

部分文字列の行をスキャンするときは、可能な新しい開始位置をすべて記録する必要があります。これはk、これらのオフセットを格納する配列を使用して行われます。部分文字列を検索し、その文字列が配列のハッシュとして存在することを確認してくださいseen。表示された文字列が見つかった場合:

  • 部分文字列が行の末尾にある場合は、next入力行に移動します。その行は印刷されないか、表示配列に追加されません。
  • それ以外の場合は、次の開始位置を追加し、kより多くの部分文字列を検索し続けます。
  • 新しい開始位置を探している間も試してください(while (length(k)))。
  • 上記のループが次の行に進まない場合は、その行が配列ハッシュにseen追加されます(またはまだ表示されていない場合は印刷されます)。

答え4

awk '{for (i in a) if (index($0,i)) next; print; a[$0]}' file

関連情報