一意の文字を含む10文字すべての単語の正規表現


23

私は10文字のすべての単語を表示する正規表現を作成しようとしていますが、どの文字も繰り返されていません。

これまでのところ、私は持っています

grep --colour -Eow '(\w{10})'

これは、質問の最初の部分です。「一意性」を確認するにはどうすればよいですか?後方参照を使用する必要があることを除けば、本当に手がかりはありません。


1
これは正規表現で行う必要がありますか?
ホークレイジング14

私は正規表現を練習しているので、できればはい:)
ディランミース14

3
これをコンピューターサイエンススタイルの正規表現で実行できるとは思わない:必要なものは、先行する一致文字の「記憶」を必要とし、正規表現にはそれがない。そうは言っても、PCREスタイルのマッチングで実行できる後方参照と非正規表現を使用して、それを実行できる場合があります。
ブルースエディガー14

3
@BruceEdigerは、言語の文字数(26)と文字列の文字数(10)に限りがある場合に限り可能です。多くの州がありますが、通常の言語ではありません。

1
「すべての英語の単語...」という意味ですか?ハイフンとアポストロフィで綴られたものを含めることを意味しますか?カフェ、ナイーブ、ファサードなどの言葉を含めるつもりですか?
ヒッピートレール14

回答:


41
grep -Eow '\w{10}' | grep -v '\(.\).*\1'

2つの同一の文字を持つ単語を除外します。

grep -Eow '\w{10}' | grep -v '\(.\)\1'

繰り返し文字を含むものを除外します。

POSIXly:

tr -cs '[:alnum:]_' '[\n*]' |
   grep -xE '.{10}' |
   grep -v '\(.\).*\1'

tr単語s以外の文字(c英数字とアンダースコアの組み合わせ)の配列を改行文字に変換することにより、単語を独自の行に配置します。

または1つgrep

tr -cs '[:alnum:]_' '[\n*]' |
   grep -ve '^.\{0,9\}$' -e '.\{11\}' -e '\(.\).*\1'

(10文字未満および10文字を超える行、および文字が少なくとも2回現れる行を除外します)。

1 grepつのみ(PCREサポート付きGNU grepまたはpcregrep):

grep -Po '\b(?:(\w)(?!\w*\1)){10}\b'

つまり、単語境界(\b)の後に10個の単語文字のシーケンスが続きます(各単語の後に、負の先読みPCRE演算子を使用した単語文字とそのシーケンスが続かない場合(?!...))。

多くの正規表現エンジンが繰り返し部分内の後方参照で動作しないため、ここで動作することは幸運です。

(少なくとも私のバージョンのGNU grepでは)

grep -Pow '(?:(\w)(?!\w*\1)){10}'

動作しませんが、

grep -Pow '(?:(\w)(?!\w*\2)){10}'

echo aa | grep -Pw '(.)\2'バグのように聞こえる(as )

あなたがしたいことがあります:

grep -Po '(*UCP)\b(?:(\w)(?!\w*\1)){10}\b'

ASCII文字以外のロケールのASCII文字だけでなく、文字を単語コンポーネントと見なしたい、\wまたは\b考慮したい場合。

別の選択肢:

grep -Po '\b(?!\w*(\w)\w*\1)\w{10}\b'

これは、10個の単語文字が続く単語境界(単語文字のシーケンスの1つが繰り返される文字列が後に続かない単語境界)です。

おそらく心の奥にあるもの:

  • 比較では大文字と小文字が区別されるのでBabylonish、たとえば、小文字と大文字が2つずつある場合でもすべての文字が異なるため、一致しますB(これ-iを変更するために使用します)。
  • 以下のため-w\wそして\b、言葉は文字(ASCIIのもののみGNUのためにあるgrep 今のところ[:alpha:]あなたのロケールで文字クラスが使用している場合-P(*UCP))、小数点以下の桁またはアンダースコアが
  • つまり、c'est(フランス語の単語の定義によると2つの単語)またはit's(英語の単語の定義によると1つの単語)または(フランス語の単語の定義によるrendez-vousと1つの単語)は1つの単語とは見なされません。
  • であっても(*UCP)、Unicode結合文字は単語コンポーネントと見なされないため、téléphone$'t\u00e9le\u0301phone')は10文字と見なされ、そのうちの1つは非アルファ文字です。défavorisé$'d\u00e9favorise\u0301')は2つéあるにもかかわらず一致します。これは、すべての異なるアルファ文字とそれに続く鋭いアクセント(非アルファなので、eとの間に単語境界があるため)があるためです。

1
驚くばかり。しかし\w一致しません-
グレアム14

@Stephane最後の2つの式の簡単な説明を投稿できますか。
mkc

REでは不可能だったすべてのものに対する解決策が、見回しのように思えることがあります。
バーマー14

1
@Barmar正規表現ではまだ不可能です。「正規表現」とは、リテラル文字、文字クラス、および「|」、「(...)」、「?」、「+」、および「*」演算子のみを明示的に許可する数学的構造です。上記のいずれでもない演算子を使用するいわゆる「正規表現」は、実際には正規表現ではありません。
ジュール14

1
@Julesこれは、math.stackexchange.comではなく、unix.stackexchange.comです。数学的なREはこのコンテキストでは無関係です
。grep

12

さて... 5文字の文字列の扱いにくい方法は次のとおりです。

grep -P '^(.)(?!\1)(.)(?!\1|\2)(.)(?!\1|\2|\3)(.)(?!\1|\2|\3|\4).$'

文字クラス(たとえば[^\1|\2])に後方参照を配置できないため、負の先読み -を使用する必要があります(?!foo)。これはPCRE機能なので、-Pスイッチが必要です。

もちろん、10文字の文字列のパターンはかなり長くなりますが、先読みで可変長( '。*')に一致する可変長を使用するより短い方法があります。

grep -P '^(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!.*\4)(.)(?!.*\5).$'

Stephane Chazelasの啓発的な答えを読んだ後、grepの-vスイッチを介して使用できるこれに類似した単純なパターンがあることに気付きました。

    (.).*\1

チェックは一度に1文字ずつ行われるため、指定された文字の後にゼロ個以上の文字(.*)が続き、後方参照が一致するかどうかが確認されます。 このパターンに一致しない-vものだけを印刷して反転します。これにより、後方参照は文字クラスで無効にできないため、より便利になります。

grep -v '\(.\).*\1'

一意の文字で任意の長さの文字列を識別するために動作しますが、

grep -P '(.)(?!.*\1)'

それは(例えば独特の文字で何でも接尾辞と一致しますから、しませんabcabcので、の試合をabc最後に、とaaaa理由をa-したがって、最後に任意の文字列)。これは、ゼロ幅であるルックアラウンド(それらは何も消費しない)によって引き起こされる複雑さです。


よくやった!ただし、これはQにあるものと組み合わせてのみ機能します。
グレアム14

1
私はあなたの正規表現エンジンが可変長の負の先読みを可能にする場合は、最初のものを簡素化することができると信じて:(.)(?!.*\1)(.)(?!.*\2)(.)(?!.*\3)(.)(?!\4).
クリストファーCreutzig

@ChristopherCreutzig:絶対にいい電話だ。私はでそれを追加しました。
ゴルディロックス

6

正規表現ですべてを実行する必要がない場合は、2つのステップで実行します。最初にすべての10文字の単語を照合し、次に一意性のためにそれらをフィルタリングします。私がこれを行う方法を知っている最短の方法は、Perlの場合です。

perl -nle 'MATCH:while(/\W(\w{10})\W/g){
             undef %seen;
             for(split//,$1){next MATCH if ++$seen{$_} > 1}
             print
           }' your_file

追加の\Wアンカーに注意して、正確に10文字の単語のみが一致するようにします。


ありがとう、しかし、私は正規表現のワンライナーとしてそれが欲しい:)
ディラン

4

他の人は、これは実際には規則的ではない特定の正規表現システムへのさまざまな拡張なしでは不可能だと示唆しています。ただし、一致させる言語は有限であるため、明らかに規則的です。4文字のアルファベットから3文字の場合、簡単です。

(abc|abd|acb|acd|bac|bad|bcd|bdc|cab|cad|cbd|cdb|dab|dac|dbc|dcb)

明らかに、これはより多くの文字とより大きなアルファベットで急いで手に負えなくなります。:-)


これが実際にうまくいく答えなので、私はこれを支持しなければなりませんでした。P:それは実際には誰もが今までに正規表現を書いた以上に効率的な方法かもしれませんが
ディランMeeusの

4

GNUのオプション--perl-regexp(short -P)は、grep先読みパターンを含むより強力な正規表現を使用します。次のパターンは、この文字が単語の残りに表示されない各文字を探します。

grep -Pow '((\w)(?!\w*\g{-1})){10}'

ただし、実行時の動作は、\w*ほぼ無限の長さになる可能性があるため、非常に不適切です。に制限できますが\w{,8}、10文字の単語制限を超えてチェックすることもできます。したがって、次のパターンは最初に正しい語長をチェックします。

grep -Pow '(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}'

テストファイルとして、約500 MBの大きなファイルを使用しました。

  • 最初のパターン:≈43秒
  • 後期パターン:≈15秒

更新:

貪欲でない演算子(\w*?)または所有演算子((...){10}+)の実行時の動作に大きな変化はありませんでした。オプションの置き換えは少し速いようです-w

grep -Po '\b(?=\w{10}\b)((\w)(?!\w*\g{-1})){10}\b'

バージョン2.13から2.18へのgrepの更新は、はるかに効果的でした。テストファイルの所要時間はわずか6秒です。


パフォーマンスはデータの性質に大きく依存します。私のテストを行ったとき、欲張りでない演算子(\w{,8}?)を使用すると、ある種の入力に役立つことがわかりました(あまり重要ではありませんが)。\g{-1}GNU grepのバグを回避するための良い使用法。
ステファンシャゼル

@StephaneChazelas:フィードバックをありがとう。また、欲張りでない所有者演算子を試してみましたが、実行時の動作に大きな変化はありませんでした(バージョン2.13)。バージョン2.18ははるかに高速で、少なくともわずかな改善が見られました。GNU grepのバグは両方のバージョンに存在します。とにかく、相対参照を好むの\g{-1}は、パターンが場所に依存しなくなるためです。この形式では、より大きなパターンの一部として使用できます。
Heiko Oberdiek 14

0

Perlソリューション:

perl -lne 'print if (!/(.)(?=$1)/g && /^\w{10}$/)' file

しかし、それは動作しません

perl -lne 'print if (!/(.)(?=\1)/g && /^\w{10}$/)' file

または

perl -lne 'print if ( /(.)(?!$1)/g && /^\w{10}$/)' file

perl v5.14.2およびv5.18.2でテスト済み


1番目と3番目は何もせず、2番目は10文字以上の任意の行を出力します。スペースは2つ以下です。pastebin.com/eEDcy02D
manatwork

おそらくperlバージョンです。v5.14.2とv5.18.2でテスト

Linuxではv5.14.1、Cygwinではv5.14.2で試しました。どちらも、先ほどリンクしたpastebinサンプルのように動作しました。
マナトワーク14

最初の行は、perlの指定バージョンで動作します。後者の2つは動作するはずです。なぜなら、それらは同じ日時ですが、動作しなかったからです。perlreは、いくつかの貪欲な表現が非常に実験的であることにしばしば注意します。

最新の更新で再テストされました。2番目のものだけが正しく出力されます。(ただし、単語は行に単独である必要がありますが、質問は行全体ではなく単語の一致に関するものです。)
manatwork 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.