ฉันจะค้นหาการทับซ้อนของสองสายในทุบตีได้อย่างไร [ปิด]


11

ฉันมีสองสาย เพื่อเป็นตัวอย่างพวกเขาตั้งไว้เช่นนี้:

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จุดเริ่มต้นของ



นี่คือเหตุผลที่คนไม่ควรข้ามโพสต์; ตอนนี้มันมีหลายคำตอบในแต่ละไซต์ที่แตกต่างกันและเป็นหัวข้อสำหรับทั้งสองไซต์ ฉันคิดว่าฉันจะทิ้งมันไว้ที่นี่ต่อไป
Michael Mrozek

คำตอบ:


10

คุณสามารถคิดถึงฟังก์ชั่นเช่นนี้โดยมีการตรวจสอบข้อผิดพลาดเพื่อเพิ่ม

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

ฉันเพิ่งสังเกตเห็นว่าเมื่อทำงานกับสองที่ว่างเปล่า / ว่างเปล่ามันเข้าสู่ห่วง [[ -z "$1$2" ]] && returnแก้ไขมัน
Peter.O

วิธีนี้ช้าลงอย่างมาก (แทนที่จะเป็นเชิงเส้น) เมื่อสตริงยาวเป็นสองเท่าเวลาจะเพิ่มขึ้น 4 เท่า (โดยประมาณ) นี่คือบางส่วนการเปรียบเทียบสตริงที่มีความยาว / เวลาเพื่อกิลส์เป็นไบนารีแยก .. : 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เทียบกับ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
@ Peter.O แบบสองทิศทางไม่ใช่เลขยกกำลัง
Gilles 'หยุดความชั่วร้าย' ใน

ฉันเดาว่า bash เก็บสตริงไว้ภายในด้วยความยาวโดยนัยดังนั้นการได้nตัวอักษร th นั้นต้องใช้การสแกนnตัวอักษรเพื่อตรวจสอบว่าพวกมันไม่ใช่ศูนย์ไบต์ที่ลงท้ายด้วยสตริง สิ่งนี้สอดคล้องกับการทุบตีที่ไม่สามารถจัดเก็บศูนย์ไบต์ในตัวแปร
Peter Cordes

8

ซึ่งสามารถทำได้ทั้งหมดภายในทุบตี แม้ว่าการจัดการกับสตริงในลูปใน 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 ปี) ฉันได้ประเมินคำตอบที่ดีที่สุดแล้ว มันง่ายมาก: กรรไกรแบ่งหินกระดาษตัดกรรไกรกระดาษห่อหิน และเลขฐานสองจะกินตามลำดับ! .. แม้กระทั่งสำหรับสตริงที่สั้นมาก .. และสำหรับสตริงชาร์จำนวน 10,000 ตัวที่ถูกประมวลผลตามลำดับผ่านทางwhile char-by-charฉันยังคงรอคอยเมื่อฉันเขียนสิ่งนี้ .. เวลาผ่านไป .. ยังคงรออยู่ ผิดกับระบบของฉัน) .. เวลาผ่านไป .. ต้องมีบางอย่างผิดปกติ แค่ 10,000 ซ้ำเท่านั้น! อา! ความอดทนเป็นคุณธรรม (อาจเป็นคำสาปในกรณีนี้) .. 13m53.755s .. vs, 0m0.322s
Peter.O

3 วิธีที่ระบุในที่นี้เป็นวิธีที่เร็วที่สุดในคำตอบที่นำเสนอทั้งหมดโดยทั่วไปcmpเป็นวิธีที่เร็วที่สุด ถัดไปคือiconvแล้วมาก respectibly รวดเร็ว binary-splitคำตอบ ขอบคุณ Gilles ฉันใช้เวลาหนึ่งปีกว่าจะถึงจุดนี้ แต่จะดีกว่าไม่สาย (PS. 2 modo mods ในiconvรหัส: $ใน=$LC_CTYPE}และ\ ในUTF-32) \ ) ... PPS จริง ๆ แล้วสตริงที่ฉันพูดถึงข้างต้นมีความยาวมากกว่า 10,000 ตัวอักษร มันเป็นผลลัพธ์ของ {1..10000} ซึ่งก็คือ 48,894 แต่นั่นไม่ได้เปลี่ยนความแตกต่าง
Peter.O

6

ใน sed สมมติว่าสตริงไม่มีอักขระขึ้นบรรทัดใหม่:

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

แต่ซ้ำกับเรื่องนี้
jfg956

ยอดเยี่ยม! ไปที่เคล็ดลับและเทคนิคห้องสมุดของฉัน :-)
hmontoliu

หรือสำหรับbash string ซึ่งไม่สามารถมี\0ได้ การใช้trและ\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) แต่นี่คือตัวอย่าง: สำหรับสตริง 32kb สองสตริง (เมื่อไบต์สุดท้ายแตกต่างกัน) จะใช้2m4.880sเมื่อเทียบกับการแยกไบนารีของ Gilles วิธีการ0m0.168s
Peter.O

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
Peter.O
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.