Como encontro a sobreposição de duas strings no bash? [fechadas]

11

Eu tenho duas cordas. Para o exemplo, eles são definidos assim:

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

O que eu quero é encontrar a sobreposição começando no início das strings. Com a sobreposição quero dizer a string "test t" no meu exemplo acima.

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

Se as strings forem string1="atest toast"; string2="test test" , elas não terão sobreposição, pois a verificação começa no começo e a "a" no início de string1 .

    
por con-f-use 07.08.2011 / 15:22

4 respostas

10

Você pode pensar em uma função como essa, com uma verificação de erro para adicionar

common_prefix() {
  local n=0
  while [[ "${1:n:1}" == "${2:n:1}" ]]; do
    ((n++))
  done
  echo "${1:0:n}"
}
    
por 07.08.2011 / 16:13
8

Isso pode ser feito inteiramente dentro do bash. Embora a manipulação de strings em um loop no bash seja lenta, existe um algoritmo simples que é logarítmico no número de operações do shell, portanto o bash puro é uma opção viável mesmo para strings longas.

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

A caixa de ferramentas padrão inclui cmp para comparar arquivos binários. Por padrão, indica o deslocamento de byte dos primeiros bytes diferentes. Há um caso especial quando uma string é um prefixo da outra: cmp produz uma mensagem diferente em STDERR; uma maneira fácil de lidar com isso é pegar a string que for mais curta.

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

Observe que cmp opera em bytes, mas a manipulação de string do bash opera em caracteres. Isso faz diferença em localidades multibyte, para exemplos de locales usando o conjunto de caracteres UTF-8. A função acima imprime o prefixo mais longo de uma cadeia de bytes. Para manipular cadeias de caracteres com esse método, podemos primeiro converter as cadeias em uma codificação de largura fixa. Assumindo que o conjunto de caracteres do locale é um subconjunto do Unicode, o UTF-32 é adequado.

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"
}
    
por 13.08.2011 / 23:55
6

No sed, supondo que as strings não contenham nenhum caractere de nova linha:

string1="test toast"
string2="test test"
printf "%s\n" "$string1" "$string2" | sed -e 'N;s/^\(.*\).*\n.*$//'
    
por 07.08.2011 / 17:24
2

Isso parece grosseiro para mim, mas você pode fazer isso por força bruta:

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

Eu quero que algum algoritmo inteligente exista, mas não consigo encontrar nenhum com uma breve pesquisa.

    
por 07.08.2011 / 16:21