visual textos studio programa para online entre encontrar diferencias comparar codigo code carpetas archivos c# string algorithm

c# - textos - Detectar diferencias entre dos cuerdas.



programa para comparar archivos y carpetas (5)

Aquí podría haber otra solución, código completo y comentado. Sin embargo, el resultado de su primer ejemplo original se invierte:

class Program { enum CharState { Add, Equal, Remove } struct CharResult { public char c; public CharState state; } static void Main(string[] args) { string a = "asdfghjk"; string b = "wsedrftr"; while (true) { Console.WriteLine("Enter string a (enter to quit) :"); a = Console.ReadLine(); if (a == string.Empty) break; Console.WriteLine("Enter string b :"); b = Console.ReadLine(); List<CharResult> result = calculate(a, b); DisplayResults(result); } Console.WriteLine("Press a key to exit"); Console.ReadLine(); } static List<CharResult> calculate(string a, string b) { List<CharResult> res = new List<CharResult>(); int i = 0, j = 0; char[] array_a = a.ToCharArray(); char[] array_b = b.ToCharArray(); while (i < array_a.Length && j < array_b.Length) { //For the current char in a, we check for the equal in b int index = b.IndexOf(array_a[i], j); if (index < 0) //not found, this char should be removed { res.Add(new CharResult() { c = array_a[i], state = CharState.Remove }); i++; } else { //we add all the chars between B''s current index and the index while (j < index) { res.Add(new CharResult() { c = array_b[j], state = CharState.Add }); j++; } //then we say the current is the same res.Add(new CharResult() { c = array_a[i], state = CharState.Equal }); i++; j++; } } while (i < array_a.Length) { //b is now empty, we remove the remains res.Add(new CharResult() { c = array_a[i], state = CharState.Remove }); i++; } while (j < array_b.Length) { //a has been treated, we add the remains res.Add(new CharResult() { c = array_b[j], state = CharState.Add }); j++; } return res; } static void DisplayResults(List<CharResult> results) { foreach (CharResult r in results) { Console.WriteLine($"''{r.c}'' - {r.state}"); } } }

Tengo 2 cuerdas

string a = "foo bar"; string b = "bar foo";

y quiero detectar los cambios de a a b . ¿Qué personajes tengo que cambiar para ir de a a b ?

Creo que debe haber una iteración sobre cada personaje y detectar si se agregó, se eliminó o si se mantuvo igual. Así que este es mi resultado exprected

''f'' Remove ''o'' Remove ''o'' Remove '' '' Remove ''b'' Equal ''a'' Equal ''r'' Equal '' '' Add ''f'' Add ''o'' Add ''o'' Add

clase y enumeración para el resultado:

public enum Operation { Add,Equal,Remove }; public class Difference { public Operation op { get; set; } public char c { get; set; } }

Aquí está mi solución, pero el caso de "Eliminar" no me queda claro cómo debe verse el código

public static List<Difference> CalculateDifferences(string left, string right) { int count = 0; List<Difference> result = new List<Difference>(); foreach (char ch in left) { int index = right.IndexOf(ch, count); if (index == count) { count++; result.Add(new Difference() { c = ch, op = Operation.Equal }); } else if (index > count) { string add = right.Substring(count, index - count); result.AddRange(add.Select(x => new Difference() { c = x, op = Operation.Add })); count += add.Length; } else { //Remove? } } return result; }

¿Cómo debe verse el código para los caracteres eliminados?

Actualización - agregó algunos ejemplos más

Ejemplo 1:

string a = "foobar"; string b = "fooar";

Resultado Esperado:

''f'' Equal ''o'' Equal ''o'' Equal ''b'' Remove ''a'' Equal ''r'' Equal

ejemplo 2:

string a = "asdfghjk"; string b = "wsedrftr";

Resultado Esperado:

''a'' Remove ''w'' Add ''s'' Equal ''e'' Add ''d'' Equal ''r'' Add ''f'' Equal ''g'' Remove ''h'' Remove ''j'' Remove ''k'' Remove ''t'' Add ''r'' Add

Actualizar:

Aquí hay una comparación entre la respuesta de Dmitry e ingen: https://dotnetfiddle.net/MJQDAO


Está buscando la distancia de edición (mínima) / la secuencia de edición (mínima) . Puedes encontrar la teoría del proceso aquí:

web.stanford.edu/class/cs124/lec/med.pdf

Implementemos (más simple) el algoritmo Levenstein Distance / Sequence (para más detalles, consulte en.wikipedia.org/wiki/Levenshtein_distance ). Empecemos por las clases de ayuda (he cambiado un poco tu implementación de ellas):

public enum EditOperationKind : byte { None, // Nothing to do Add, // Add new character Edit, // Edit character into character (including char into itself) Remove, // Delete existing character }; public struct EditOperation { public EditOperation(char valueFrom, char valueTo, EditOperationKind operation) { ValueFrom = valueFrom; ValueTo = valueTo; Operation = valueFrom == valueTo ? EditOperationKind.None : operation; } public char ValueFrom { get; } public char ValueTo {get ;} public EditOperationKind Operation { get; } public override string ToString() { switch (Operation) { case EditOperationKind.None: return $"''{ValueTo}'' Equal"; case EditOperationKind.Add: return $"''{ValueTo}'' Add"; case EditOperationKind.Remove: return $"''{ValueFrom}'' Remove"; case EditOperationKind.Edit: return $"''{ValueFrom}'' to ''{ValueTo}'' Edit"; default: return "???"; } } }

Por lo que puedo ver en los ejemplos proporcionados, no tenemos ninguna operación de edición , pero agregamos + eliminamos ; por eso puse editCost = 2 cuando insertCost = 1 , int removeCost = 1 (en caso de empate : insert + remove vs. edit ponemos insert + remove ). Ahora estamos listos para implementar el algoritmo de Levenstein:

public static EditOperation[] EditSequence( string source, string target, int insertCost = 1, int removeCost = 1, int editCost = 2) { if (null == source) throw new ArgumentNullException("source"); else if (null == target) throw new ArgumentNullException("target"); // Forward: building score matrix // Best operation (among insert, update, delete) to perform EditOperationKind[][] M = Enumerable .Range(0, source.Length + 1) .Select(line => new EditOperationKind[target.Length + 1]) .ToArray(); // Minimum cost so far int[][] D = Enumerable .Range(0, source.Length + 1) .Select(line => new int[target.Length + 1]) .ToArray(); // Edge: all removes for (int i = 1; i <= source.Length; ++i) { M[i][0] = EditOperationKind.Remove; D[i][0] = removeCost * i; } // Edge: all inserts for (int i = 1; i <= target.Length; ++i) { M[0][i] = EditOperationKind.Add; D[0][i] = insertCost * i; } // Having fit N - 1, K - 1 characters let''s fit N, K for (int i = 1; i <= source.Length; ++i) for (int j = 1; j <= target.Length; ++j) { // here we choose the operation with the least cost int insert = D[i][j - 1] + insertCost; int delete = D[i - 1][j] + removeCost; int edit = D[i - 1][j - 1] + (source[i - 1] == target[j - 1] ? 0 : editCost); int min = Math.Min(Math.Min(insert, delete), edit); if (min == insert) M[i][j] = EditOperationKind.Add; else if (min == delete) M[i][j] = EditOperationKind.Remove; else if (min == edit) M[i][j] = EditOperationKind.Edit; D[i][j] = min; } // Backward: knowing scores (D) and actions (M) let''s building edit sequence List<EditOperation> result = new List<EditOperation>(source.Length + target.Length); for (int x = target.Length, y = source.Length; (x > 0) || (y > 0);) { EditOperationKind op = M[y][x]; if (op == EditOperationKind.Add) { x -= 1; result.Add(new EditOperation(''/0'', target[x], op)); } else if (op == EditOperationKind.Remove) { y -= 1; result.Add(new EditOperation(source[y], ''/0'', op)); } else if (op == EditOperationKind.Edit) { x -= 1; y -= 1; result.Add(new EditOperation(source[y], target[x], op)); } else // Start of the matching (EditOperationKind.None) break; } result.Reverse(); return result.ToArray(); }

Manifestación:

var sequence = EditSequence("asdfghjk", "wsedrftr"); Console.Write(string.Join(Environment.NewLine, sequence));

Salir:

''a'' Remove ''w'' Add ''s'' Equal ''e'' Add ''d'' Equal ''r'' Add ''f'' Equal ''g'' Remove ''h'' Remove ''j'' Remove ''k'' Remove ''t'' Add ''r'' Add


Me arriesgaré aquí y proporcionaré un algoritmo que no es el más eficiente, pero es fácil de razonar.

Vamos a cubrir un poco de terreno primero:

1) los asuntos de orden

string before = "bar foo" string after = "foo bar"

A pesar de que "bar" y "foo" aparecen en ambas cadenas, la "barra" deberá eliminarse y agregarse más tarde. Esto también nos dice que es la cadena after que nos da el orden de los caracteres que nos interesan, primero queremos "foo".

2) orden sobre cuenta

Otra forma de verlo es que algunos caracteres nunca pueden obtener su turno.

string before = "abracadabra" string after = "bar bar"

Solo los caracteres en negrita de " bar b a r" expresan su opinión en "a b r a cadab ra ". Aunque tenemos dos b en ambas cadenas, solo la primera cuenta . Cuando llegamos a la segunda b en "ba r b ar", la segunda b en "abracada br a" ya ha pasado, cuando estábamos buscando la primera aparición de ''r''.

3) Barreras

Las barreras son los caracteres que existen en ambas cadenas, teniendo en cuenta el orden y la cuenta. Esto ya sugiere que un conjunto podría no ser la estructura de datos más apropiada, ya que perderíamos la cuenta.

Para una entrada

string before = "pinata" string after = "accidental"

Obtenemos (pseudocódigo)

var barriers = { ''a'', ''t'', ''a'' }

"pin ata "

" a cciden ta l"

Sigamos el flujo de ejecución:

  • ''a'' es la primera barrera, también es el primer carácter de after por lo que todo lo que precede a la primera ''a'' puede eliminarse before . "pin a ta" -> " a ta"
  • la segunda barrera es ''t'', no está en la siguiente posición en nuestra cadena after , por lo que podemos insertar todo lo que está en el medio. "a t a" -> "acciden t a"
  • la tercera barrera ''a'' ya está en la siguiente posición, por lo que podemos pasar a la siguiente barrera sin hacer ningún trabajo real.
  • no hay más barreras, pero nuestra longitud de cadena es aún menor que la de after , por lo que habrá un procesamiento posterior. "Accidenta" -> "Accidenta l "

Tenga en cuenta que ''i'' y ''n'' no vuelven a jugar, nuevamente, el orden se cuenta en exceso.

Implementación

Hemos establecido que el orden y el conteo son importantes, una Queue viene a la mente.

static public List<Difference> CalculateDifferences(string before, string after) { List<Difference> result = new List<Difference>(); Queue<char> barriers = new Queue<char>(); #region Preprocessing int index = 0; for (int i = 0; i < after.Length; i++) { // Look for the first match starting at index int match = before.IndexOf(after[i], index); if (match != -1) { barriers.Enqueue(after[i]); index = match + 1; } } #endregion #region Queue Processing index = 0; while (barriers.Any()) { char barrier = barriers.Dequeue(); // Get the offset to the barrier in both strings, // ignoring the part that''s already been handled int offsetBefore = before.IndexOf(barrier, index) - index; int offsetAfter = after.IndexOf(barrier, index) - index; // Remove prefix from ''before'' string if (offsetBefore > 0) { RemoveChars(before.Substring(index, offsetBefore), result); before = before.Substring(offsetBefore); } // Insert prefix from ''after'' string if (offsetAfter > 0) { string substring = after.Substring(index, offsetAfter); AddChars(substring, result); before = before.Insert(index, substring); index += substring.Length; } // Jump over the barrier KeepChar(barrier, result); index++; } #endregion #region Post Queue processing if (index < before.Length) { RemoveChars(before.Substring(index), result); } if (index < after.Length) { AddChars(after.Substring(index), result); } #endregion return result; } static private void KeepChar(char barrier, List<Difference> result) { result.Add(new Difference() { c = barrier, op = Operation.Equal }); } static private void AddChars(string substring, List<Difference> result) { result.AddRange(substring.Select(x => new Difference() { c = x, op = Operation.Add })); } static private void RemoveChars(string substring, List<Difference> result) { result.AddRange(substring.Select(x => new Difference() { c = x, op = Operation.Remove })); }


Probé con los 3 ejemplos anteriores, y devuelve el resultado esperado de manera adecuada y perfecta.

int flag = 0; int flag_2 = 0; string a = "asdfghjk"; string b = "wsedrftr"; char[] array_a = a.ToCharArray(); char[] array_b = b.ToCharArray(); for (int i = 0,j = 0, n= 0; i < array_b.Count(); i++) { //Execute 1 time until reach first equal character if(i == 0 && a.Contains(array_b[0])) { while (array_a[n] != array_b[0]) { Console.WriteLine(String.Concat(array_a[n], " : Remove")); n++; } Console.WriteLine(String.Concat(array_a[n], " : Equal")); n++; } else if(i == 0 && !a.Contains(array_b[0])) { Console.WriteLine(String.Concat(array_a[n], " : Remove")); n++; Console.WriteLine(String.Concat(array_b[0], " : Add")); } else { if(n < array_a.Count()) { if (array_a[n] == array_b[i]) { Console.WriteLine(String.Concat(array_a[n], " : Equal")); n++; } else { flag = 0; for (int z = n; z < array_a.Count(); z++) { if (array_a[z] == array_b[i]) { flag = 1; break; } } if (flag == 0) { flag_2 = 0; for (int aa = i; aa < array_b.Count(); aa++) { for(int bb = n; bb < array_a.Count(); bb++) { if (array_b[aa] == array_a[bb]) { flag_2 = 1; break; } } } if(flag_2 == 1) { Console.WriteLine(String.Concat(array_b[i], " : Add")); } else { for (int z = n; z < array_a.Count(); z++) { Console.WriteLine(String.Concat(array_a[z], " : Remove")); n++; } Console.WriteLine(String.Concat(array_b[i], " : Add")); } } else { Console.WriteLine(String.Concat(array_a[n], " : Remove")); i--; n++; } } } else { Console.WriteLine(String.Concat(array_b[i], " : Add")); } } }//end for MessageBox.Show("Done"); //OUTPUT CONSOLE: /* a : Remove w : Add s : Equal e : Add d : Equal r : Add f : Equal g : Remove h : Remove j : Remove k : Remove t : Add r : Add */


Si desea tener una comparación precisa entre dos cadenas, debe leer y comprender Levenshtein Distance . Al usar este algoritmo, puede calcular con precisión la tasa de similitud entre dos cadenas y también puede retroceder el algoritmo para obtener la cadena de cambios en la segunda cadena. este algoritmo es una métrica importante para el procesamiento del lenguaje natural también.

Hay algunos otros beneficios y es tiempo de aprender.

En este enlace hay una versión C # de Levenshtein Distance:

https://www.dotnetperls.com/levenshtein