Bash'deki iki dizenin çakışmasını nasıl bulabilirim? [kapalı]


11

İki telim var. Örnek uğruna bunlar şu şekilde ayarlanır:

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

İstediğim dizelerin başında başlayan örtüşmeyi bulmak. Örtüşme ile yukarıdaki örnekte "test t" dizesini kastediyorum.

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

Eğer dizeler string1="atest toast"; string2="test test"olsaydı, kontrol başlangıcından ve başlangıcındaki "a" dan başladığından beri örtüşmezlerdi string1.



Tam da insanların çapraz gönderi yapmamalarının nedeni budur; artık her sitede farklı olan birden fazla yanıtı var ve her iki site için de konu başlığında. Sanırım yine de burada bırakacağım
Michael Mrozek

Yanıtlar:


10

Eklemek için bazı hata kontrolü ile böyle bir işlevi düşünebilirsiniz

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

Ben sadece iki boş / null argüman ile çalıştırıldığında bir ∞ döngüsüne girdiğini fark ettim. [[ -z "$1$2" ]] && returndüzeltir.
Peter.O

Bu yöntem katlanarak yavaştır (doğrusal olarak değil). Dize uzunluğu ikiye katlandığında, zaman 4 (yaklaşık) bir faktör artar. İşte Gilles' bazı dize uzunluğu / zaman karşılaştırmalar ikili bölünmüş ..: 64 0m0.005s vs 0m0.003s - 128 0m0.013s vs 0m0.003s - 256 0m0.041s vs 0m0.003s - 512 0m0.143s vs 0m0.005s - 1024 0m0.421s vs 0m0.009s - 2048 0m1.575s vs 0m0.012s - 4096 0m5.967s vs 0m0.022s - 8192 0m24.693s vs 0m0.049s -16384 1m34.004s vs 0m0.085s - 32768 6m34.721s vs 0m0.168s - 65536 27m34.012s vs 0m0.370s
Peter.O

2
@ Peter.O İkinci olarak, katlanarak değil.
Gilles 'SO- kötü olmayı bırak

Sanırım bash dizeleri dahili olarak örtülü uzunlukla saklar, bu yüzden nth karakterini elde netmek, karakterlerin dize sonlandırıcı sıfır bayt olmadığını kontrol etmek için tarama karakterleri gerektirir . Bu, bash değişkeninde sıfır bayt depolayamama ile tutarlıdır.
Peter Cordes

8

Bu tamamen bash içinde yapılabilir. Bash'da bir döngüde dize manipülasyonu yapmak yavaş olsa da, kabuk işlemlerinin sayısında logaritmik olan basit bir algoritma vardır, bu nedenle saf bash uzun dizeler için bile geçerli bir seçenektir.

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"
}

Standart araç kutusu cmpikili dosyaları karşılaştırmayı içerir . Varsayılan olarak, ilk farklı baytların bayt uzaklığını gösterir. Bir dize diğerinin öneki olduğunda özel bir durum vardır: cmpSTDERR üzerinde farklı bir mesaj üretir; Bununla başa çıkmanın kolay bir yolu, hangi dizenin en kısa olduğunu almaktır.

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"
}

Baytlarda cmpçalışır, ancak bash'ın dize manipülasyonu karakterler üzerinde çalışır. Bu, çok baytlı yerel ayarlarda, örneğin UTF-8 karakter kümesini kullanan yerel ayarlarda fark yaratır. Yukarıdaki işlev bir bayt dizesinin en uzun önekini yazdırır. Karakter dizelerini bu yöntemle işlemek için önce dizeleri sabit genişlikte kodlamaya dönüştürebiliriz. Yerel ayarın karakter kümesinin Unicode'un bir alt kümesi olduğu varsayıldığında, UTF-32 faturaya uyar.

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"
}

Bu soruyu tekrar gözden geçirerek (1 yıl sonra), en iyi cevabı tekrar değerlendirdim . Her şey oldukça basit: kaya makasları kırıyor, makas kesilmiş kağıt, kağıt kaya sarıyor. ve ikili sıralı yiyor! .. hatta oldukça kısa dizeleri için .. ve sıralı olarak işleniyor ılımlı 10000 char dize while char-by-chargelince, ben bunu yazarken hala bekliyorum .. zaman geçiyor .. hala bekliyor (belki bir şey var benim sistemimle yanlış) zaman .. zaman geçiyor .. yanlış bir şey olmalı; sadece 10.000 yinelemedir! Ah! sabır bir erdemdir (belki de bu durumda bir lanet) .. 13m53.755s .. vs, 0m0.322s
Peter.O

Burada verilen 3 yöntem, sunulan tüm cevapların cmpen hızlısıdır .. Temel olarak, en hızlıdır (ancak char tabanlı değildir). Bir sonraki iconvve sonra çok hızlı bir binary-splitcevap. Teşekkürler Gilles. Bu noktaya gelmem bir yıl sürdü, ama hiç olmadığı kadar iyi. (PS. 2 yazım hatası iconvkodu: $giriş =$LC_CTYPE}ve \ giriş UTF-32) \ ) ... PPS. aslında yukarıda bahsettiğim dize 10.000 karakterden uzundu. Bu, 48.894 olan {1..10000} sonucuydu, ama bu 'farkı değiştirmiyor
Peter.O

6

Sed'de, dizelerin yeni satır karakteri içermediği varsayılarak:

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

Ama, yinelenmeyecek bu .
jfg956

Parlak! doğrudan benim ipuçları ve hileler kütüphanesine gider :-)
hmontoliu

Ya da içeremeyen bir bash dizesi için \0. Kullanılması trve \0dizede yeni satır, yöntem işleyebilir, ....{ 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

Bu sedyöntemi biraz daha test ettim ve geri referansları bu şekilde (arama modelinde) kullanmanın çok pahalı olduğu görülüyor. Hala sıralı byte-byte-byte döngüsünden (yaklaşık 3 faktör kadar) daha iyi performans gösteriyor, ancak burada bir örnek: iki 32kb dize için (son bayt farklı olduğunda), 2m4.880sGilles'in ikili-bölünmesine kıyasla yöntemi0m0.168s
Peter.O

2

Bu bana kaba geliyor, ama kaba kuvvetle yapabilirsiniz:

#!/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))}

Bazı akıllı algoritmaların olmasını istiyorum, ancak kısa bir arama ile bulamıyorum.



2
Genel referans için, yavaş tarafta biraz. İki 32768 karakter dizisi (son karakter farklıdır) 6 milyon 27.689 saniye aldı.
Peter.O
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.