Wie finde ich die Überlappung zweier Zeichenfolgen in Bash? [geschlossen]


11

Ich habe zwei Saiten. Für das Beispiel sind sie wie folgt gesetzt:

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

Ich möchte, dass die Überlappung am Anfang der Zeichenfolgen beginnt. Mit Überlappung meine ich die Zeichenfolge "test t" in meinem obigen Beispiel.

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

Wenn die Zeichenfolgen string1="atest toast"; string2="test test"wären, hätten sie keine Überlappung, da die Prüfung am Anfang und das "a" am Anfang beginnt string1.



Dies ist genau der Grund, warum Leute keine Cross-Posts machen sollen. Jetzt gibt es auf jeder Site mehrere Antworten, die unterschiedlich sind, und es ist für beide Sites themenbezogen. Ich denke, ich werde es trotzdem hier lassen
Michael Mrozek

Antworten:


10

Sie können sich eine Funktion wie diese vorstellen, bei der eine Fehlerprüfung hinzugefügt werden muss

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

Ich habe gerade bemerkt, dass es mit zwei leeren / null-Argumenten in eine ∞-Schleife eintritt. [[ -z "$1$2" ]] && returnbehebt es.
Peter.O

Diese Methode ist exponentiell langsamer (anstatt linear). Wenn sich die Länge der Saite verdoppelt, erhöht sich die Zeit um den Faktor 4 (ungefähr). Hier sind einige der String-Länge / Zeit - Vergleiche Gilles' binary-Split : .. 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

2
@ Peter.O Quadratisch, nicht exponentiell.
Gilles 'SO - hör auf böse zu sein'

Ich denke, bash speichert Zeichenfolgen intern mit impliziter Länge. Um das nth-Zeichen zu erhalten, müssen Zeichen gescannt werden, num zu überprüfen, ob es sich nicht um das nullbyte mit Zeichenfolge handelt. Dies steht im Einklang damit, dass bash kein Null-Byte in einer Variablen speichern kann.
Peter Cordes

8

Dies kann vollständig innerhalb von Bash erfolgen. Obwohl die Manipulation von Zeichenfolgen in einer Schleife in Bash langsam ist, gibt es einen einfachen Algorithmus, der in Bezug auf die Anzahl der Shell-Operationen logarithmisch ist. Daher ist reine Bash auch für lange Zeichenfolgen eine praktikable Option.

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

Die Standard-Toolbox enthält cmpden Vergleich von Binärdateien. Standardmäßig gibt es den Byte-Offset der ersten unterschiedlichen Bytes an. Es gibt einen Sonderfall, wenn eine Zeichenfolge ein Präfix der anderen ist: cmpErzeugt eine andere Nachricht auf STDERR; Eine einfache Möglichkeit, damit umzugehen, besteht darin, die kürzeste Zeichenfolge zu verwenden.

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

Beachten Sie, dass dies cmpmit Bytes funktioniert, die String-Manipulation von bash jedoch mit Zeichen. Dies macht einen Unterschied bei Multibyte-Gebietsschemas, beispielsweise bei Gebietsschemas, die den UTF-8-Zeichensatz verwenden. Die obige Funktion gibt das längste Präfix einer Byte-Zeichenfolge aus. Um Zeichenfolgen mit dieser Methode zu verarbeiten, können wir die Zeichenfolgen zunächst in eine Codierung mit fester Breite konvertieren. Angenommen, der Zeichensatz des Gebietsschemas ist eine Teilmenge von Unicode, passt UTF-32 in die Rechnung.

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

Bei einem erneuten Besuch dieser Frage (1 Jahr später) habe ich die beste Antwort erneut bewertet . Es ist alles ganz einfach: Stein bricht Schere, Schere schneidet Papier, Papier wickelt Stein. und binär isst sequentiell! .. selbst für ziemlich kurze Zeichenfolgen .. und was eine moderate 10000-Zeichenfolge betrifft, die sequentiell über verarbeitet wird while char-by-char, warte ich immer noch darauf, während ich dies schreibe .. die Zeit vergeht .. wartet immer noch (vielleicht gibt es etwas falsch mit meinem System) .. die Zeit vergeht .. es muss etwas falsch sein; es sind nur 10.000 iterationen! Ah! Geduld ist eine Tugend (in diesem Fall vielleicht ein Fluch). 13m53.755s .. vs, 0m0.322s
Peter.O

Die 3 hier angegebenen Methoden sind die absolut schnellsten aller vorgestellten Antworten. Grundsätzlich cmpsind sie die schnellsten (basieren jedoch nicht auf Zeichen). Das nächste ist iconvund dann die sehr respektabel schnelle binary-splitAntwort. Danke Gilles. Ich habe ein Jahr gebraucht, um an diesen Punkt zu gelangen, aber besser spät als nie. (PS. 2 Tippfehler Mods im iconvCode: $in =$LC_CTYPE}und \ in UTF-32) \ ) ... PPS. Tatsächlich war die oben erwähnte Zeichenfolge länger als 10.000 Zeichen. Es war das Ergebnis von {1..10000}, nämlich 48.894, aber das ändert nichts am Differential
Peter.O

6

Angenommen, die Zeichenfolgen enthalten in sed keine Zeilenumbrüche:

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

Aber duplizieren Sie damit .
jfg956

Brillant! geht direkt zu meiner Tipps & Tricks Bibliothek :-)
hmontoliu

Oder für eine Bash- Zeichenfolge, die nicht enthalten kann \0. Mit trund \0kann die Methode Zeilenumbrüche in der Zeichenfolge verarbeiten, ....{ 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

Ich habe diese sedMethode gerade etwas weiter getestet , und es scheint, dass die Verwendung von Rückverweisen auf diese Weise (im Suchmuster) sehr teuer ist. Es übertrifft immer noch die sequentielle Byte-für-Byte-Schleife (um den Faktor 3), aber hier ist ein Beispiel: Für zwei 32-KB-Zeichenfolgen (wobei das letzte Byte unterschiedlich ist) dauert es 2m4.880sim Vergleich zu Gilles ' Binärsplit Methode0m0.168s
Peter.O

2

Das scheint mir grob, aber Sie können es mit brutaler Gewalt tun:

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

Ich möchte, dass es einen cleveren Algorithmus gibt, aber mit einer kurzen Suche kann ich keinen finden.



2
Als allgemeine Referenz ist es etwas langsam. Zwei 32768-Zeichenfolgen (wobei das letzte Zeichen unterschiedlich ist) dauerten 6m27.689s.
Peter.O
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.