मैं दो स्ट्रिंग्स के ओवरलैप को बैश में कैसे खोज सकता हूं? [बन्द है]


11

मेरे दो तार हैं। उदाहरण के लिए वे इस तरह सेट हैं:

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

मैं जो चाहता हूं वह स्ट्रिंग्स की शुरुआत में ओवरलैप खोजने के लिए है। ओवरलैप के साथ मेरा मतलब है कि स्ट्रिंग "टेस्ट टी" मेरे उपरोक्त उदाहरण में।

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

यदि string1="atest toast"; string2="test test"वे तार थे, तो चेक के शुरू होने और "a" के शुरू होने के बाद से उनका कोई ओवरलैप नहीं होगा string1



यह ठीक यही कारण है कि लोगों को क्रॉस-पोस्ट के लिए नहीं माना जाता है; अब इसके प्रत्येक साइट पर कई उत्तर हैं जो अलग-अलग हैं, और यह दोनों साइटों के लिए विषय है। मुझे लगता है कि मैं सिर्फ इसे यहाँ से बाहर जाना जा रहा हूँ
माइकल Mrozek

जवाबों:


10

आप इस तरह के एक फ़ंक्शन के बारे में सोच सकते हैं, कुछ त्रुटि जांच के साथ जोड़ने के लिए

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

मैंने अभी देखा कि जब दो खाली / अशक्त के साथ चलाया जाता है तो यह एक run लूप में प्रवेश करता है। [[ -z "$1$2" ]] && returnइसे ठीक करता है।
Peter.O

यह विधि तेजी से धीमी है (रैखिक के बजाय)। जैसे ही स्ट्रिंग लंबाई में दोगुनी हो जाती है, समय 4 (लगभग) के कारक से बढ़ जाता है। यहां कुछ स्ट्रिंग-लंबाई / समय की तुलना गाइल्स के बाइनरी-विभाजन से की गई है : .. 64 0m0.005s बनाम 0m0.003s - 128 0m0.013s बनाम 0m0.003s - 256 0m0.041s बनाम 0m0.003s - 512 0m0.143s बनाम 0m0.005s - 1024 0m0.421s बनाम 0m0.009s - 2048 0m1.575s बनाम 0m0.012s - 4096 0m5.967s बनाम 0m0.022s - 8192 0m24.693s बनाम 0m0.049s -16384 1m34.004s बनाम 0m0.085s - 32768 6m34.721s बनाम 0m0.168s - 65536 27m34.012s बनाम 0m0.370s
Peter.O

2
@ पीटर। चतुर्भुज, घातांक नहीं।
गिलेस का SO-

मुझे लगता है कि बैश स्टोर के तार आंतरिक रूप से निहित लंबाई के साथ हैं, इसलिए nवें चरित्र को स्कैन nकरने के लिए स्कैनिंग वर्णों की आवश्यकता होती है ताकि वे स्ट्रिंग-टर्मिनेटिंग शून्य-बाइट न हों। यह बैश एक चर में शून्य-बाइट को स्टोर करने में असमर्थ होने के साथ संगत है।
पीटर कॉर्ड्स

8

यह पूरी तरह से बैश के अंदर किया जा सकता है। हालांकि बैश में एक लूप में स्ट्रिंग हेरफेर करना धीमा है, एक सरल एल्गोरिथ्म है जो शेल ऑपरेशन की संख्या में लॉगरिदमिक है, इसलिए लंबे स्ट्रिंग्स के लिए भी शुद्ध बैश एक व्यवहार्य विकल्प है।

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बाइनरी फ़ाइलों की तुलना करना शामिल है। डिफ़ॉल्ट रूप से, यह पहले अलग-अलग बाइट्स की बाइट ऑफसेट को इंगित करता है। एक विशेष मामला है जब एक स्ट्रिंग दूसरे का उपसर्ग होता है: cmpSTDERR पर एक अलग संदेश उत्पन्न करता है; इससे निपटने का एक आसान तरीका है कि जो भी स्ट्रिंग हो वह सबसे कम हो।

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बाइट्स पर काम होता है, लेकिन बैश का स्ट्रिंग हेरफेर पात्रों पर संचालित होता है। यह यूटीएफ -8 वर्ण सेट का उपयोग करने वाले उदाहरण स्थानों के लिए, मल्टीबाइट स्थानों में अंतर करता है। ऊपर का कार्य बाइट स्ट्रिंग के सबसे लंबे उपसर्ग को प्रिंट करता है। इस विधि के साथ वर्ण स्ट्रिंग्स को संभालने के लिए, हम पहले स्ट्रिंग्स को निश्चित-चौड़ाई एन्कोडिंग में परिवर्तित कर सकते हैं। लोकेल के कैरेक्टर सेट को यूनिकोड का उपसमूह मानकर, 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सबसे तेज़ (लेकिन चार आधारित नहीं है)। अगला है iconvऔर फिर बहुत सम्मानजनक तेजी से binary-splitजवाब। धन्यवाद गिल्स। इस मुकाम पर पहुंचने में मुझे एक साल लग गया, लेकिन पहले से कहीं ज्यादा देर हो गई। (PS। 2 टाइपो मॉड्स इन iconvकोड: $इन =$LC_CTYPE}और \ इन UTF-32) \ ) ... पीपीएस। वास्तव में मैंने ऊपर जो तार का उल्लेख किया था वह 10,000 वर्णों से अधिक लंबा था। यह {1..10000} का परिणाम था, जो कि 48,894 है, लेकिन यह 'अंतर को नहीं बदलता है
पीटर।

6

Sed में, यह मानते हुए कि तार में कोई नई रेखा नहीं है:

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

लेकिन इस के साथ नकल ।
jfg956

प्रतिभाशाली! सीधे मेरी युक्तियों और ट्रिक्स लाइब्रेरी पर जाता है :-)
hmontoliu

या, एक बैश स्ट्रिंग के लिए, जिसमें सम्‍मिलित नहीं किया जा सकता है \0। का उपयोग कर trऔर \0, विधि स्ट्रिंग में newlines संभाल सकता है, ....{ printf "%s" "$string1" |tr \\n \\0; echo; printf "%s" "$string2" |tr \\n \\0; echo; } | sed -e 'N;s/^\(.*\).*\n\1.*$/\1/' |tr \\0 \\n
पीटर।

मैंने अभी इस sedविधि को थोड़ा और परीक्षण किया है , और ऐसा लगता है कि बैक-रेफ़रेंस का इस तरह (खोज पैटर्न में) उपयोग करना बेहद महंगा है। यह अभी भी अनुक्रमिक बाइट-बाइट लूपिंग (लगभग 3 के एक कारक) से बेहतर प्रदर्शन करता है, लेकिन यहां एक उदाहरण है: दो 32kb तार के साथ (अंतिम बाइट अलग होने के साथ), यह 2m4.880sगिल्स के बाइनरी-स्प्लिट की तुलना में लगता है । विधि0m0.168s
पीटर।

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
सामान्य संदर्भ के लिए, यह धीमी तरफ है। दो 32768 चरित्र के तार (अंतिम चार अलग होने) 6m27.689s लिया।
पीटर।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.