文字列から隣接する重複語を削除する

文字列から隣接する重複語を削除する

次の文字列があります。

one one tow tow three three tow one three

重複した単語を削除して次のように作成するにはどうすればよいですか?

one tow three tow one three

重要なのは、隣接する重複語だけを削除するスクリプトを作成したいということです。

私は試した:

echo "$string" | awk '{for (i=1;i<=NF;i++) if (!a[$i]++) printf("%s%s",$i,FS)}{printf("\n")}'

ただし、隣接していない重複語も削除します。

答え1

複数文字RS\s速記にGNU awkを使用する:

$ echo 'one one tow tow three three tow one three' |
awk -v RS='\\s+' '
    $0 != prev { out = (NR>1 ? out OFS : "") $0; prev = $0 }
    END { print out }
'
one tow three tow one three

またはまだGNU awkですが、以下からインスピレーションを受けました。@nezabudkaの返信ただし、入力フィールドを区切るスペースの順序と入力フィールドに含まれる文字に関係なく正しく機能し、出力が終了するようにいくつかの変更が行われたため、\n有効なPOSIXテキストです。文書:

$ echo one one tow tow three three tow one three |
awk -v RS='[[:blank:]]+' '
    $1 != prev { out = out $1 RT; prev=$1 }
    END { print out }
'
one tow three tow one three

それ以外の場合は awk を使用してください。

$ echo 'one one tow tow three three tow one three' |
awk '{
    out = $1
    for ( i=2; i<=NF; i++ ) {
        if ( $i != $(i-1) ) {
            out = out OFS $i
        }
    }
    print out
}'
one tow three tow one three

答え2

行に2500個(例:1000個)以下の列がある場合:

echo one one tow tow three three tow one three |
    fmt -1 | uniq | fmt -1000

GNU awk:

echo one one tow tow three three tow one three |
    awk -v RS=' ' '$1 != D {printf "%s", $1 (RT?RS:ORS); D=$1}'

更新(改行で終わることが確実な場合):

echo one one tow tow three three tow one three |
    awk -v RS='[[:space:]]' '$1 != D {printf "%s", $1 RT; D=$1}'

そうでない場合(一般的な方法):

echo -n one one tow tow three three tow one three |
    awk -v RS='[[:space:]]' '$1 != D {printf "%s", $1 (RT?RT:ORS); D=$1}'

注:
GNUバージョンには、RSのテンプレートに対応する実際の値が割り当てられる組み込み変数RTがあります。たとえば、[[:space:]]RS変数にテンプレートが定義されている場合、RT変数には各ケース(スペース、タブ、または改行)でレコードを終了する文字が動的に割り当てられます。 RS変数に文字クラステンプレートが割り当てられている場合は、三項RS=[[:space:]]演算子を(RT?RT:ORS)またはに変更する必要があります。RT

答え3

uniq別の行の後にすべての単語を入れるには、次のようにします。

string='one one tow tow three three tow one three'
printf '%s\n' "${string// /
}" | uniq | paste -sd ' ' -

またはを使用してperl複数の空白文字を許可して単語を区切り、繰り返しグループ間の空白を保持します。

string='  one one tow   tow  three three tow one three '
perl -le 'print s/(?<!\S)(\S+)(\s+\1)+(?!\S)/\1/gr for @ARGV' -- "$string"

以下を提供します。

  one tow  three tow one three 

ksh93の${var//pattern/replacement}パラメータ拡張演算子と同じです(bashを含む他の一部のシェルはこの演算子をコピーしましたが、高度なパターン一致演算子はコピーしませんでした)。

$ string='  one one tow   tow  three three tow one three '
$ print -r - "${string//~(<!\S)+(\S)+(+(\s)\1)~(!\S)/\1}"
  one tow  three tow one three

またはzsh(他のシェルはPerlに似たパターンマッチング演算子をサポートしています)を使用してその変数を変更します。

$ string='  one one tow   tow  three three tow one three '
$ autoload regexp-replace
$ set -o rematchpcre
$ regexp-replace string '(?<!\S)(\S+)(\s+\1)+(?!\S)' '$match[1]'
$ print -r - "$string"
  one tow  three tow one three 

またはfish:

$ set string '  one one tow   tow  three three tow one three '
$ string replace -a --regex '(?<!\S)(\S+)(\s+\1)+(?!\S)' '$1' $string
  one tow  three tow one three 

例の単語がすべて数字(またはアンダースコア)で構成されている場合は、ビジボックスの実装と同様のアプローチを取ることができます。awkここで、負の検索perl演算子は、\<および\>単語境界演算子に置き換えることができます(perlに似ているため、/\bに近い)。 Perlツアー演算子として):(?<!\w)(?!\w)

$ printf '%s\n' "$string" | busybox awk '{print gensub("\\<(\\S+)(\\s+\\1)+\\>", "\\1", "g")}'
  one tow  three tow one three

単語に数字や下線以外の文字が含まれている場合、この方法は使用できません。たとえば、 と の間に単語の境界があるone-two two threeため、 に変更されます。one-two three-two

答え4

パールを使用してください。たとえば、次は行の境界を超えて隣接する重複する単語を削除することもできます(perlの-0777オプションを使用して入力全体を一度に吸収します)。

$ printf 'one one two\n two two\ntwo three three two\none\nthree\nthree\n' |
    perl -0777 -p -e 's/\b(\w+)(?:\s+\1)+\b/$1/g'
one two three two
one
three

\1ジョブの左側(LHS)は、s/search (LHS)/replace (RHS)/以前に一致したパターングループの逆参照です(\w+)$1置換ジョブまたはジョブの右側にある同じキャプチャグループ。

しかし、これをPerlに入力しないと、入力は次のようになります。複数行には繰り返される隣接する単語が含まれています。

$ printf 'one one two\n two two\ntwo three three two\none\nthree\nthree\n' 
one one two
 two two
two three three two
one
three
three

メモ:

  1. \b^orに似たアンカーですが、$行の先頭または末尾を一致させるのではなく、単語間の(幅が0の)境界と一致します。

  2. \wマニュアルページで、次のように定義されているすべての単語文字と一致しますperlre

\w[3] は「単語」文字と一致します(英数字と「_」、その他の接続句読点、Unicode マーカー)。

...

[3] 詳細については、perlunicode の「Unicode 文字属性」を参照してください。

アルファベット(アルファベットなど)の文字(数字や下線を除く)のみを厳密に一致させる場合は、代わりに[[:alpha:]]+使用できます\w+

  1. 入力テキストにUnicode文字が含まれている場合、これを処理する方法はいくつかありますが、最も簡単な方法はPerlの-Cオプションを使用することです。
$ echo 'öne öne öne two öne one' |
    perl -C -0777 -p -e 's/\b([[:alpha:]]+)(?:\s+\1)+\b/$1/g'
öne two öne one

このオプションの詳細を表示してman perlrun検索してください。-Cこのトピックに本当に興味がある場合は、およびperlunicodeperlunitutマニュアルperluniintroページも参照してくださいperlunifaq。広範な文書で推測できるように、Unicodeを扱うことはほとんどの場合簡単で簡単ですが、さまざまな状況では非常に複雑で微妙です。

関連情報