bash에서 두 문자열의 겹침을 어떻게 찾습니까? [닫은]


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.



이것이 바로 사람들이 교차 게시하지 않아야하는 이유입니다. 이제 각 사이트마다 다른 답변이 여러 개 있으며 두 사이트 모두에 대한 주제입니다. 어쨌든 여기에 그대로
두겠다고

답변:


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.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.004s vs 0m0.085s - 32768 6m34.721s vs 0m0.168s - 65536 27m34.012s vs 0m0.370s
Peter.O

2
@ Peter.O 2 차적으로, 지수 적으로는 아닙니다.
Gilles 'SO- 악마 그만'

bash는 문자열을 암시 적 길이로 내부에 저장하므로 문자를 n스캔 n하려면 문자열을 종료하는 0 바이트가 아닌지 확인하기 위해 문자를 스캔해야합니다 . 이것은 bash가 변수에 0 바이트를 저장할 수없는 것과 일치합니다.
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바이트에서 작동하지만 배쉬의 문자열 조작 문자에서 작동합니다. 따라서 UTF-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 .. vs. 0m0.322s
Peter.O

여기에 제시된 3 가지 방법은 제시된 모든 답변 cmp중에서 가장 빠릅니다 . 기본적으로 가장 빠릅니다 (그러나 문자 기반은 아님). 다음은 iconv다음 매우 respectibly 빠른 binary-split대답. 고마워 질. 이 시점에 도달하는 데 1 년이 걸렸지 만 결코 늦지 않는 것이 좋습니다. ( iconv코드의 PS. 2 오타 개조 : $in =$LC_CTYPE}\ in 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

훌륭한! 내 tips & tricks 라이브러리로 직접 이동 :-)
hmontoliu

또는을 포함 할 수없는 bash 문자열의 경우 \0. trand를 사용 \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.880sGilles의 이진 분할과 비교하여 방법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
반을 비교하고 반복 은 n ^ 2가 아니라 n * log (n)입니다.
Gilles 'SO- 악마 그만해'

2
일반적으로 느린 편입니다. 두 개의 32768 문자열 (마지막 문자가 다름)에는 6m27.689가 걸렸습니다.
Peter.O
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.