Skip to content

Instantly share code, notes, and snippets.

@OpenSrcerer
Created March 31, 2025 22:12
Show Gist options
  • Save OpenSrcerer/103eb12fd83201d595a89d5780bfe91b to your computer and use it in GitHub Desktop.
Save OpenSrcerer/103eb12fd83201d595a89d5780bfe91b to your computer and use it in GitHub Desktop.
An out of order replacer for strings/text. Works nicely for cases where you cannot guarantee a specific sort.
// You must at least know this information:
type IMatch = {
startIndex: number; // Position of the replacement start
endIndex: number; // Position of the replacement end
replacement: string; // What you wish to replace the substring with
hash: string; // Hash or unique identifier for replacement case
}
/**
* A function that when given a string and the appropriate replacement information (see above),
* replaces all occurrences of the string in the given positions.
*
* I had to create this when working on an AI text clarity suggester (like grammarly).
* In that use case, I had a list of replacements that needed to be accessed in a random order.
* This means that we cannot apply a sort order, therefore rendering the reverse algorithm for replacement unusable here.
*
* With this approach, earlier indices can be modified and then later ones... or whatever order the user chooses.
*/
function applyReplacements(text: string, replacements: IMatch[]): string {
// Create a copy of the text to modify
let modifiedText = text;
// We'll track all adjustments made by previous replacements
const adjustments = new Map<string, number>();
replacements.forEach(match => {
// Get parameters
const adj = adjustments.get(match.hash);
const startIndex = !adj ?
match.startIndex : (match.startIndex + adj);
const endIndex = !adj ?
match.endIndex : (match.endIndex + adj);
const replacementDelta = match.replacement.length - text.substring(startIndex, endIndex + 1).length;
// Replace the text for this specific replacement
modifiedText = modifiedText.substring(0, startIndex) +
match.replacement +
modifiedText.substring(endIndex + 1);
// Update changes map for future iterations
replacements
.filter(r => r.hash !== match.hash && match.endIndex <= r.startIndex)
.forEach(r => {
const adj2 = adjustments.get(r.hash);
adjustments.set(r.hash, adj2 ? adj2 + replacementDelta : replacementDelta)
})
})
return modifiedText;
}
// Test case
const originalText = "Ths is a smple txt with erors.";
const corrections: IMatch[] = [
{
startIndex: 24,
endIndex: 28,
replacement: "errors",
hash: "error1"
},
{
startIndex: 0,
endIndex: 2,
replacement: "This",
hash: "error2"
},
{
startIndex: 9,
endIndex: 13,
replacement: "sample",
hash: "error3"
},
{
startIndex: 15,
endIndex: 17,
replacement: "text",
hash: "error4"
}
];
// Correct output: "This is a sample text with errors."
console.log(applyReplacements(originalText, corrections));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment