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

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

ただいまの
回答率

90.62%

  • 正規表現

    773questions

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

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

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,027

LUCIA

score 8

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

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

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 3

checkベストアンサー

+3

 未検証版

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

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

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

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

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

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

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

 更新履歴

  • 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 17:56

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

    キャンセル

  • 2018/01/26 17:58

    foo
    bar
    foo
    bar

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

    キャンセル

  • 2018/01/27 00:24

    To: LUCIA さん
    親記事に追記しました。
    ただ、先程も書いたようにコストが高い(処理時間が長い)ので、テキストの容量によっては遅く感じると思います。

    To: mattn さん
    一般的なテキストエディタであれば、「一括置換」で一度に置換処理を実行できると思います。

    キャンセル

  • 2018/01/27 00:38

    ありがとうございます!
    @ aaa
    @ aaa
    @りんご
    @りんご
    @みかん

    のようなちいさなデータを作って、テキストエディターの置き換え画面にコピペしてみましたが、アプリの問題なのか反応が全くありませんでした。
    検索:/(?:\n|^)(.*)(?=(?:\n(?!\1).*)*\n\1)/g
    置き換え:何も入れない
    であっているはずなのですが…

    pcとスマホのできることの差なのでしょうか…
    せっかく書いてくださったのに、うまく処理できず申し訳ありません。もう少し頑張ってみますね!

    キャンセル

  • 2018/01/27 00:41

    私が書いたコードはJavaScriptであり、正規表現部分は正規表現リテラルの文法で書かれています。
    ですので、テキストエディタで正規表現を扱うには「正規表現部分」を抽出する必要があります。

    検索: (?:\n|^)(.*)(?=(?:\n(?!\1).*)*\n\1)
    置換: (何も入れない)

    キャンセル

  • 2018/01/27 22:56

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

    キャンセル

  • 2018/01/28 00:22

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

    例)

    @bbb
    @aaa
    @bbb
    @ccc
    @aaa
    @bbb

    キャンセル

  • 2018/01/28 14:26

    To: mattn さん
    親記事に追記しました。
    限られた時間で正規表現を書いていたので、割り切った出来栄えとなっており、私が確認する限りでは2つの問題、1つの仕様(問題3)がありました。

    > 順が変わってしまう場合がある
    mattn さんの意図は「重複行がある場合に一番初めにマッチした重複行を残す」という事でしょうか。
    削除対象は要求仕様に含まれていないと考えていたので、そこは考慮していませんでした。
    後読みはかなりテクニカルなようで、私にはまだ完全に使いこなせるレベルにはないようです。
    もし、興味がありましたら、代替案となる正規表現を提案して頂けると面白いと思います。

    キャンセル

  • 2018/01/28 15:25

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

    キャンセル

  • 2018/01/28 16:53

    追記
    think49さま
    バージョン0.2.0を使ってみました!

    @みかん
    @みかんジュース
    @みかん
    @みかん
    @みかん
    @りんご
    @りんご
    @みかんジュース

    こんな文字列に対し、
    ^(.*)(?:\r\n|[\n\r])(?=(?:(?!\1).*(?:\r\n|[\n\r]))*\1$)

    を適応したら、見事に最後の文字のみ残るような反応を示してくれました("@みかん"と、"@みかんジュース"は別物と認識してくれました。)が、一番上の"@みかん"だけ、重複と判断されなかったようです。
    離れたところにある"@みかんジュース"は重複と判断されたので、きっと私が、リテラル表現からの抜き出しを間違えているだけなのだとは思いますが…

    とりあえず、一歩進展しました。ありがとうございます。

    キャンセル

  • 2018/01/28 17:15

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

    キャンセル

+1

重複削除は正規表現では難しいと思います。理由は、並べ替えられていないからです。@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 16:26

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

    キャンセル

  • 2018/01/26 18:19

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

    キャンセル

0

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

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

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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/01/26 16:48

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

    キャンセル

  • 2018/01/26 16:52

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

    キャンセル

  • 2018/01/26 19:49

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

    キャンセル

  • 2018/01/27 23:04

    roguerefさま
    あれから進展があり、ソートがなくとも正規表現だけでできる方法を提案してくださった方がいらっしゃいました。

    今回はこの方法を使おうと思いますが、UNIX環境という手段もあることを、改めて知ることができ貴重な体験となりました。
    お時間をいただき、ありがとうございました!

    キャンセル

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

  • ただいまの回答率 90.62%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • 正規表現

    773questions

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