Given a 2-column CSV dictionary file that contains translation of certain words, this c# code needs to replace the first occurrence of a word in the dictionary.
- Once a segment of string has been replaced, it cannot be matched or overwritten by a new dictionary word.
This sounded like a fairly simple task but the code I came up with runs horribly slow and I'm looking for ways to improve it.
All the offending code is in the TestReplace
function, I build up a hashset that keeps track of what character Ids in the string have been touched. When you apply a rule that changes the lenght of the string, all the character ids switch around so they have to be recalculated and I believe it costs a lot of performance. Wish there was a simpler way to do this !
Here is a super simple case of what the code tries to do :
Dictionary file:
hello
>>> hi
hi
>>> goodbye
Input: hello, world!
First rule is applied so string becomes -> hi, world
. The word hi
is now locked.
Second rule is applied but the string does not become goodbye, world
since this part is locked.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace stringReplacer
{
class Program
{
public static Dictionary<string, string> ReplacementRules = new Dictionary<string, string>()
{
{"John","Freddy" },
{"John walks","Freddy runs" },
{"brown dog","gray dog" },
{"dog","cat" },
{"- not -", "(not)" },
{"(","" },
{")","" },
{"whenever", "sometimes, when"},
{"raining", "snowing" },
{"his", "many" }
};
static void Main(string[] args)
{
string Input = "John walks his brown dog whenever it's - not - raining";
string ExpectedOutput = "Freddy walks many gray dog sometimes, when it's (not) snowing";
string TestReplaceOutput = TestReplace(Input, ReplacementRules);
ValidateReplacement("TestReplace", TestReplaceOutput, ExpectedOutput);
}
public static string TestReplace(string input, Dictionary<string, string> ReplacementRules)
{
HashSet<int> LockedStringSegment = new HashSet<int>();
foreach (var rule in ReplacementRules)
{
string from = Regex.Escape(rule.Key);
string to = rule.Value;
var match = Regex.Match(input, from);
if (match.Success)
{
List<int> AffectedCharacterPositions = Enumerable.Range(match.Index, match.Length).ToList();
if (!AffectedCharacterPositions.Any(x => LockedStringSegment.Contains(x)))
{
input = Regex.Replace(input, from, to);
int LengthDelta = to.Length - rule.Key.Length;
LockedStringSegment
.Where(x => x > match.Index + rule.Key.Length).OrderByDescending(x => x).ToList()
.ForEach(x =>
{
//We shuffle the locked character's place depending on the replacement delta.
LockedStringSegment.Remove(x);
LockedStringSegment.Add(x + LengthDelta);
});
//Add all the new locked character's position to the hashset.
Enumerable.Range(match.Index, to.Length).ToList().ForEach(x => LockedStringSegment.Add(x));
}
}
}
return input;
}
public static void ValidateReplacement(string MethodName, string Actual, string Expected)
{
Console.Write($"{MethodName} : ");
if (Expected != Actual)
Console.WriteLine("String replacement doesn't work");
else
Console.WriteLine("It works");
Console.WriteLine($"Expected : {Expected}");
Console.WriteLine($"Actual : {Actual} \n\n");
}
}
}