如何在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



这正是人们不应该交叉张贴的原因。现在,它在每个站点上都有多个不同的答案,并且两个站点都是主题。我想我还是要把它留在这里
Michael Mrozek

Answers:


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'2

此方法呈指数级速度(而不是线性方式)变慢。随着弦的长度加倍,时间增加了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 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 2012年

2
@ Peter.O平方而不是指数。
吉尔斯(Gillles)“所以-别再邪恶了”

我猜bash在内部以隐式长度存储字符串,因此要获取nth个字符,需要扫描n字符以检查它们是否不是以字符串结尾的零字节。这与bash无法将零字节存储在变量中相一致。
彼得·科德斯

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

回顾这个问题(一年后),我已经重新评估了最佳答案。一切都非常简单:用石头砸剪刀,用剪刀剪纸,用纸包裹石头。和二进制文件顺序地吃!..即使对于很短的字符串..以及通过依次处理的适度的10000个字符的字符串while char-by-char,我仍在等待它,因为我写这篇文章..时间在流逝..仍然在等待(也许有些东西我的系统有问题)..时间流逝..一定有问题;只有10,000信号!啊! 忍耐是一种美德(在这种情况下也许是个诅咒).. 13m53.755s .. vs,0m0.322s
Peter.O 2012年

这里给出的3种方法是所有提出的答案中最快的。基本上,cmp它是最快的(但不是基于char的)。其次是iconv,然后 respectibly快速 binary-split的答案。谢谢吉尔。我花了一年时间才达到这一点,但迟到总比没有好。(PS。2个错字mods在iconv代码中:$in =$LC_CTYPE}\ in UTF-32) \ )... PPS。实际上我上面提到的字符串超过10,000个字符。这是{1..10000}的结果,即48,894,但这并没有改变差异
Peter.O 2012年

6

在sed中,假设字符串不包含任何换行符:

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

但与重复。
jfg956 2011年

辉煌!直接转到我的提示和技巧库:-)
hmontoliu 2011年

或者,对于bash字符串,不能包含\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 2012年

我刚刚对这种sed方法进行了进一步的测试,看来以这种方式(在搜索模式中)使用反向引用非常昂贵。它仍然优于顺序逐字节循环(大约3倍),但这是一个示例:对于两个32kb字符串(最后一个字节是不同的),2m4.880s与Gilles的二进制拆分相比,它需要,方法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
比较一半并重复是n * log(n)而不是n ^ 2。
吉尔(Gilles)'所以

2
作为一般参考,它有点慢。两个32768个字符串(最后一个字符不同)花了6m27.689s。
Peter.O 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.