bashで2つの文字列の重複を見つけるにはどうすればよいですか?[閉まっている]


11

弦が2本あります。例として、これらは次のように設定されています。

string1="test toast"
string2="test test"

私が欲しいのは、弦の始めから始まるオーバーラップを見つけることです。オーバーラップとは、上記の例では文字列「test t」を意味します。

# I look for the command 
command "$string1" "$string2"
# that outputs:
"test t"

文字列であった場合、string1="atest toast"; string2="test test"チェックは最初から始まり、「a」はの最初から始まるため、文字列は重複しませんstring1



これこそ、人々がクロスポストするべきではない理由です。今では、各サイトに異なる複数の回答があり、両方のサイトでトピックに対応しています。とにかく、ここから離れるつもりです
マイケル・ムロゼック

回答:


10

追加するいくつかのエラーチェックを使用して、このような関数を考えることができます

common_prefix() {
  local n=0
  while [[ "${1:n:1}" == "${2:n:1}" ]]; do
    ((n++))
  done
  echo "${1:0:n}"
}

2つの空/ null引数で実行すると、∞ループに入ることに気づきました。 [[ -z "$1$2" ]] && returnそれを修正します。
Peter.O

この方法は、(線形よりも)指数関数的に遅くなります。ストリングの長さが2倍になると、時間は4倍(約)増加します。ここでジルにいくつかの文字列の長さ/時間の比較ですバイナリ分割は .. 64 0m0.005s0m0.003s - 128 0m0.013s0m0.003s - 256 0m0.041s0m0.003s - 512 0m0.143s0m0.005s - 1024 0m0.421s0m0.009s - 2048 0m1.575s0m0.012s - 4096 0m5.967s0m0.022s - 8192 0m24.693s0m0.049s -16384 1m34.004s0m0.085s - 32768 6m34.721s0m0.168s - 65536 27m34.012s0m0.370s
Peter.O

2
@ Peter.O指数的にではなく、二次的に。
Gilles「SO-悪をやめる」

bashは文字列を暗黙的な長さで内部的に格納していると思います。そのため、nth文字を取得するには、文字をスキャンnして、文字列で終了するゼロバイトでないことを確認する必要があります。これは、bashが変数にゼロバイトを格納できないことと一致しています。
Peter Cordes

8

これは完全にbash内で行うことができます。bashのループで文字列操作を行うのは遅いですが、シェル操作の数が対数である単純なアルゴリズムがあるため、純粋なbashは長い文字列でも実行可能なオプションです。

longest_common_prefix () {
  local prefix= n
  ## Truncate the two strings to the minimum of their lengths
  if [[ ${#1} -gt ${#2} ]]; then
    set -- "${1:0:${#2}}" "$2"
  else
    set -- "$1" "${2:0:${#1}}"
  fi
  ## Binary search for the first differing character, accumulating the common prefix
  while [[ ${#1} -gt 1 ]]; do
    n=$(((${#1}+1)/2))
    if [[ ${1:0:$n} == ${2:0:$n} ]]; then
      prefix=$prefix${1:0:$n}
      set -- "${1:$n}" "${2:$n}"
    else
      set -- "${1:0:$n}" "${2:0:$n}"
    fi
  done
  ## Add the one remaining character, if common
  if [[ $1 = $2 ]]; then prefix=$prefix$1; fi
  printf %s "$prefix"
}

標準ツールボックスにはcmp、バイナリファイルを比較するためのものが含まれています。デフォルトでは、最初の異なるバイトのバイトオフセットを示します。一方の文字列がもう一方の文字列の接頭辞である場合は、特別な場合がありcmpます。STDERRで異なるメッセージが生成されます。これに対処する簡単な方法は、最も短い文字列を取得することです。

longest_common_prefix () {
  local LC_ALL=C offset prefix
  offset=$(export LC_ALL; cmp <(printf %s "$1") <(printf %s "$2") 2>/dev/null)
  if [[ -n $offset ]]; then
    offset=${offset%,*}; offset=${offset##* }
    prefix=${1:0:$((offset-1))}
  else
    if [[ ${#1} -lt ${#2} ]]; then
      prefix=$1
    else
      prefix=$2
    fi
  fi
  printf %s "$prefix"
}

cmpはバイトを操作しますが、bashの文字列操作は文字を操作することに注意してください。これにより、マルチバイトロケール、たとえばUTF-8文字セットを使用するロケールに違いが生じます。上記の関数は、バイト文字列の最長の接頭辞を出力します。このメソッドで文字列を処理するには、まず文字列を固定幅エンコーディングに変換します。ロケールの文字セットがUnicodeのサブセットであると想定すると、UTF-32はその目的に適合します。

longest_common_prefix () {
  local offset prefix LC_CTYPE="${LC_ALL:=$LC_CTYPE}"
  offset=$(unset LC_ALL; LC_MESSAGES=C cmp <(printf %s "$1" | iconv -t UTF-32) \
                                           <(printf %s "$2" | iconv -t UTF-32) 2>/dev/null)
  if [[ -n $offset ]]; then
    offset=${offset%,*}; offset=${offset##* }
    prefix=${1:0:$((offset/4-1))}
  else
    if [[ ${#1} -lt ${#2} ]]; then
      prefix=$1
    else
      prefix=$2
    fi
  fi
  printf %s "$prefix"
}

この質問を再検討して(1年後)、私は最良の答えを再評価しました。それはすべて非常に単純です:岩がはさみを壊し、はさみが紙を切り、紙が岩を包みます。そして、バイナリはシーケンシャルを食べます!..非常に短い文字列でも..そしてを介して順番に処理される中程度の10000文字の文字列while char-by-charについては、これを書き込んでいる間、私はまだそれを待っています..時間が経過します..まだ待っています(おそらく何かがある私のシステムに問題がある。..時間が経つ..何か問題があるに違いない。たったの10,000回だけです。ああ!忍耐は美徳です(おそらくこの場合は呪いです)。13m53.755s。対0m0.322s
Peter.O

ここに示した3つの方法は、提示されたすべての回答cmpの中で最も高速です。基本的に、最も高速です(ただし、charベースではありません)。次はiconv、そして非常に立派に速い binary-split答えです。Gilles、ありがとう。この時点に達するまでに1年かかりましたが、遅れることはありません。(PS。2 typo mods in iconvcode:$in =$LC_CTYPE}and \ in UTF-32) \ )... PPS。実際、上記の文字列は10,000文字を超えていました。これは48,894である{1..10000}の結果でしたが、それは差を変え
ません

6

sedでは、文字列に改行文字が含まれていないと仮定します。

string1="test toast"
string2="test test"
printf "%s\n" "$string1" "$string2" | sed -e 'N;s/^\(.*\).*\n\1.*$/\1/'

しかし、これ同じです。
jfg956 2011

鮮やかさ!私のtips&tricksライブラリに直接行きます:-)
hmontoliu

または、bash文字列の場合、を含めることはできません\0trand を使用すると\0、メソッドは文字列の改行を処理できます....{ printf "%s" "$string1" |tr \\n \\0; echo; printf "%s" "$string2" |tr \\n \\0; echo; } | sed -e 'N;s/^\(.*\).*\n\1.*$/\1/' |tr \\0 \\n
Peter.O

このsed方法をもう少しテストしたところ、このように(検索パターンで)後方参照を使用すると非常にコストがかかるようです。それでも、逐次的なバイト単位のループのパフォーマンスは(約3倍)優れていますが、ここに例があります。2つの32kb文字列(最後のバイトが異なる)の場合、2m4.880sGillesのバイナリ分割と比較すると、メソッド0m0.168s
Peter.O 2012

2

これは私には粗雑に思えますが、総当たりでそれを行うことができます:

#!/bin/bash

string1="test toast"
string2="test test"

L=1  # Prefix length

while [[ ${string1:0:$L} == ${string2:0:$L} ]]
do
    ((L = L + 1))
done

echo Overlap: ${string1:0:$((L - 1))}

巧妙なアルゴリズムが存在するようにしたいのですが、短い検索ではアルゴリズムを見つけることができません。



2
一般的な参考として、少し遅いです。2つの32768文字列(最後の文字が異なる)は6m27.689秒かかりました。
Peter.O 2012
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.