質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

Q&A

解決済

3回答

10022閲覧

正規表現における重複削除

LUCIA

総合スコア20

正規表現

正規表現とは特定の文字列によるパターンマッチングを行う際に用いられる宣言型プログラミングです。

0グッド

0クリップ

投稿2018/01/26 07:09

編集2018/01/26 07:10

Androidのテキストエディター(以下のふたつ)を使って、文章を編集しています。

QuickEdit テキストエディタープロ - Google Play の Android アプリ
https://play.google.com/store/apps/details?id=com.rhmsoft.edit.pro
Jota+ (Text Editor) - Google Play の Android アプリ
https://play.google.com/store/apps/details?id=jp.sblo.pandora.jota.plus

例えば、

@aaa こんにちは!@aaaです @bbb テスト @ccc @aaa @ddd @eee @aaa …(続く)

のような文があったとき、@からはじまる行だけ残そうと

^(?!@).+$(@から始まらない行)⇒空白に置き換え
空行がいくつか残るので
^[ \t]*[\r\n]+⇒空白に置き換え
として、

@aaa @bbb @ccc @aaa @ddd @eee @aaa …(続く)

とするところまでは加工ができました。
しかしここで、@aaaが重複してしまっています。
このような場合、重複を削除して@aaaをひとつだけ残すようにするには、どんな正規表現を使えばよいのでしょうか。

上にあげた2つのアプリについてなのですが、quickedit(海外アプリ)は構文の種類がいろいろ選べるいっぽうで、はじめの正規表現【特定の文字から始まらない行削除】が動作しませんでした…(正規表現対応と書いてあるのですが、検索ができませんでした。【特定の文字を含む】ほうは動作したので、バグの可能性もあります。)

もうひとつのjota(国産アプリ)のほうは、上の書き方で@始まりの行だけ残すことができましたが、正規表現に使われる言語の種類が不明で、重複削除のためにどんな書き方をすればよいのかわかりません…

あと少しのところで詰まってしまっています。
もし、正規表現に詳しい方いらっしゃいましたら、お知恵をお貸し願えれば幸いです。宜しくお願い致します。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

未検証版

正規表現でも実装できますが、先読みのコストが高いのでお勧めはしません。
以下、未検証ですが、考え方の参考になれば。 (下記は期待通りに動きません)

JavaScript

1@(.*)(?:\n|$)(?=(?:@(?!\1\n).*\n)*(?!@\1(?:\n|$)))

動作確認版1 (前方一致で重複判定、一番最後の重複行を残す)

下記コードで期待通りに動作する事を確認しました。
mattnさんが掲示された事例にもマッチするよう、汎用的に行の重複削除するように(行頭が@で始まらなくても良い)書き直しました。

JavaScript

1'use strict'; 2var string = '@aaa\n@bbb\n@ccc\n@aaa\n@ddd\n@eee\n@aaa', 3 search = /(?:\n|^)(.*)(?=(?:\n(?!\1).*)*\n\1)/g, 4 replace = ''; 5 6console.log(string.replace(search, replace));

これは…順番に照合していって重複かどうか判断していくような構文でしょうか。

一行ずつ照合していくという意味ではそうですね。
テキストエディタの場合は「検索」と「置換」で文字列処理をしますので、重複削除するには空文字('')に置換する事になります。

「動作確認版1」に存在する問題点

今回は簡易的なサンプルであるが故に、比較的シンプルな正規表現となる事を目指しました。
あくまでもサンプルであり、叩き台にしてもらうべく書いた正規表現です。
仮に、「私が考える実用的な正規表現を書くこと」を試みるならば、「動作確認版1」にはいくつかの問題があるので、私はこの問題の修正を試みます。

  • (問題1) 文頭(^) にマッチした場合に末尾の \n が残る(一番初めに空行が一つ残る)
  • (問題2) 前方一致で重複検索する (固定長文字列でなかった場合に誤爆する)
  • (問題3) 前方から重複行を削除していき、最後に重複していない行を残す

(問題1) は「^で始まる行」と「\n で始まる行」を同一処理とし、\n の削除が「\n で始まる行」でしか行われない事に起因します。
解決するには、「^で始まる行」と「\n で始まる行」を双方とも行末の \n を削除し、行頭の \n を削除しない方向に修正すれば良いでしょう。

(問題2) は例題がほぼ「固定長文字列」であった為、重複判定処理をさぼりました。
具体的には、@aa\n@aaa の文字列に対して、\n@aaa を出力するという問題です。
@aaa@aa で始まる為に重複判定が働いてしまいます。
行末判定が疎かなので、しっかりと行末を先読みすればこの問題は回避できます。

(問題3) は私は問題とは認識していませんでした。
要件では「重複行を削除する」としかなく、**「重複があった場合に、どの行を残すかまでは指定されていなかった」**からです。
仮に一番初めにマッチした行を残す仕様とするならば、先読みの代わりに後読みを使う事で解決できるでしょう。

動作確認版2 (完全一致で重複判定、一番最後の重複行を残す)

次の修正を施したとします。

  • (問題1), (問題2) を解消する
  • (問題3) はそのまま残す

コードをGitHubにUPしました。

v0.1.1 は後読みを使用している為、Google Chrome 62 以上でなければ動作しません。
v0.2.1 は後読みを使用せず、修飾子m(multiline)を使用しており、一般的なブラウザ全般で動作します。
テキストエディタで動作させる場合は、multilineオプションに対応しているかどうかで使い分けるといいでしょう。

一番初めにマッチした重複行を残すには

(問題3) ですが、「一番初めにマッチした行を残す仕様」にする場合は先読みを後読みに変更する事で改善する案が想像できますが、これはかなり難易度が高いようです(私が「後読みに関して熟知していない」という理由もあります)。
途中までは作成したので、興味がある方は挑んでみると面白いかもしれません。

JavaScript

1'use strict'; 2function deleteDuplicateLine (string) { 3 return string.replace(/(?<=(?:^|\r\n|[\n\r])(.+)(?:(?:\r\n|[\n\r])(?!\1[\n\r])(.*))*)(?:\r\n|[\n\r])\1(?=[\n\r]|$)/g, ''); 4}

更新履歴

  • 2018/01/27 00:21 動作確認版1を追記
  • 2018/01/28 14:19 「動作確認版1」に存在する問題点、動作確認版2、一番初めにマッチした重複行を残すには、を追記
  • 2018/01/28 17:14 行末処理が不完全だった為、GitHubリンクを delete-duplicate-line-0.1.1.js, delete-duplicate-line-0.2.1.js に更新した

Re: LUCIA さん

投稿2018/01/26 08:50

編集2018/01/28 08:17
think49

総合スコア18156

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LUCIA

2018/01/26 08:56

回答ありがとうございます! これは…順番に照合していって重複かどうか判断していくような構文でしょうか。 まだまだ初歩の初歩なので、参考にしつつお勉強していきたいと思います。
mattn

2018/01/26 08:58

foo bar foo bar の様な場合、重複が無くなるまで何回か実行しないといけなくないでしょうか?
think49

2018/01/26 15:24

To: LUCIA さん 親記事に追記しました。 ただ、先程も書いたようにコストが高い(処理時間が長い)ので、テキストの容量によっては遅く感じると思います。 To: mattn さん 一般的なテキストエディタであれば、「一括置換」で一度に置換処理を実行できると思います。
LUCIA

2018/01/26 15:38

ありがとうございます! @ aaa @ aaa @りんご @りんご @みかん のようなちいさなデータを作って、テキストエディターの置き換え画面にコピペしてみましたが、アプリの問題なのか反応が全くありませんでした。 検索:/(?:\n|^)(.*)(?=(?:\n(?!\1).*)*\n\1)/g 置き換え:何も入れない であっているはずなのですが… pcとスマホのできることの差なのでしょうか… せっかく書いてくださったのに、うまく処理できず申し訳ありません。もう少し頑張ってみますね!
think49

2018/01/26 15:41

私が書いたコードはJavaScriptであり、正規表現部分は正規表現リテラルの文法で書かれています。 ですので、テキストエディタで正規表現を扱うには「正規表現部分」を抽出する必要があります。 検索: (?:\n|^)(.*)(?=(?:\n(?!\1).*)*\n\1) 置換: (何も入れない)
LUCIA

2018/01/27 13:56

お返事が遅くなり申し訳ありません。 上記の方法を試してみたところ、無事、求めていた動作結果になりました! 本当にありがとうございます! また何かありましたら、どうぞ宜しくお願い致します。
mattn

2018/01/27 15:22

期待通りなのであれば良いのですが、空の行が出来たり順が変わってしまう場合があるのでご注意を。 例) @bbb @aaa @bbb @ccc @aaa @bbb
think49

2018/01/28 05:26

To: mattn さん 親記事に追記しました。 限られた時間で正規表現を書いていたので、割り切った出来栄えとなっており、私が確認する限りでは2つの問題、1つの仕様(問題3)がありました。 > 順が変わってしまう場合がある mattn さんの意図は「重複行がある場合に一番初めにマッチした重複行を残す」という事でしょうか。 削除対象は要求仕様に含まれていないと考えていたので、そこは考慮していませんでした。 後読みはかなりテクニカルなようで、私にはまだ完全に使いこなせるレベルにはないようです。 もし、興味がありましたら、代替案となる正規表現を提案して頂けると面白いと思います。
LUCIA

2018/01/28 06:25

LUCIAです! 複雑な事例にも対応できるような改善案の提案が続けられていて、見ていてとても勉強になりました。 今回のトピックは、具体的にいいますと、Twitterでリプライをくれた方の整理に使うためのものでした。 どんな画面でも文字がコピペできるアプリがあって、そこから全文字列選択→コピー→いらないものを取るという流れです。 そのため、取得したい文字列の先頭が必ずアットマークであることは確実で、なおかつ順不同(重複値がひとつにマージしてくれたらそれで大丈夫)という条件でした! 今後のために、順番がある場合についても、私も考えてみたいと思います。
LUCIA

2018/01/28 07:53

追記 think49さま バージョン0.2.0を使ってみました! @みかん @みかんジュース @みかん @みかん @みかん @りんご @りんご @みかんジュース こんな文字列に対し、 ^(.*)(?:\r\n|[\n\r])(?=(?:(?!\1).*(?:\r\n|[\n\r]))*\1$) を適応したら、見事に最後の文字のみ残るような反応を示してくれました("@みかん"と、"@みかんジュース"は別物と認識してくれました。)が、一番上の"@みかん"だけ、重複と判断されなかったようです。 離れたところにある"@みかんジュース"は重複と判断されたので、きっと私が、リテラル表現からの抜き出しを間違えているだけなのだとは思いますが… とりあえず、一歩進展しました。ありがとうございます。
think49

2018/01/28 08:15

To: LUCIA さん 行末処理が不完全だった為、更新しました。 GitHubリンクは親記事から辿って下さい。
guest

0

重複削除は正規表現では難しいと思います。理由は、並べ替えられていないからです。@aaa が飛び石になっている場合、最初のだけを採用しなければなりません。もし並び替えて良いのなら、ソートして ^(.*)\n\1\1 で置き換えれば良いです。エディタによっては \1 だったり $1 だったりします。

もしソートしたくないのであれば、UNIX シェルが実行出来る環境で以下の様に実行して下さい。

$ cat [対象のファイル].txt | awk '!x[$1]++{print $1}' > [新しいファイル].txt

もし最初から UNIX コマンドが使える環境なのでしたら、先頭が @ で始まる部分も

cat [対象のファイル].txt | grep '^@' | awk '!x[$1]++{print $1}' > [新しいファイル].txt

で処理出来ます。

投稿2018/01/26 07:19

編集2018/01/26 07:23
mattn

総合スコア5030

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LUCIA

2018/01/26 07:26

回答ありがとうございます! ソートがネックでしたか。 このデータは並び替えしても良いタイプのデータなので、一度excelあたりのデータ並び替えできるソフトウェアに持っていってデータを順番に並べてから、提示していただいた置き換えコマンドを使うしかないですね… コピペが増えてアプリの行き来が大変なので、テキストエディターで完結させたかったのですが… もう少し頑張ってみますね!
LUCIA

2018/01/26 09:19

データをソートした状態(重複データが連続した状態)で、 ^(.*)\n\1を$1に置き換えてみましたら、ダブったデータがふたつとも消えてしまいますね…なかなか強敵のようです。 データのソートに関しては別問題と捉えて、とりあえず重複削除だけでもできるように、頑張って勉強してみます!
guest

0

@で始まる行だけ、それぞれ1行だけ残すってことならもっと単純にこんなでどうでしょう。

$ grep '^@' filename.txt | sort -u

質問にあるデータで上記実行するとこうなります。

$ grep '^@' hoge.txt | sort -u @aaa @bbb @ccc @ddd @eee

投稿2018/01/26 07:45

rogueref

総合スコア727

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

LUCIA

2018/01/26 07:48

回答ありがとうございます。 すごく簡潔なコードですね! 無知で申し訳ないのですが、これはもしかして、スマホの他にパソコン等が必要なケースでしょうか…?
rogueref

2018/01/26 07:52

mattn さんが書いているようにUNIXコマンドが使える環境なら上記でいけます。 vimが使えるなら、編集対象ファイルをvimで開いたうえで、 :%! grep '^@' | sort -u でできます。
LUCIA

2018/01/26 10:49

AndroidでもvimやUNIX環境を構築できるアプリがあるようですので、時間はかかりそうですが、いろいろ試してみたいと思います!
LUCIA

2018/01/27 14:04

roguerefさま あれから進展があり、ソートがなくとも正規表現だけでできる方法を提案してくださった方がいらっしゃいました。 今回はこの方法を使おうと思いますが、UNIX環境という手段もあることを、改めて知ることができ貴重な体験となりました。 お時間をいただき、ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問