String 全局最优字符串替换

String 全局最优字符串替换,string,algorithm,encoding,String,Algorithm,Encoding,我正在寻找一些指针来编写一个函数(我们称之为replaceGlobal),该函数接受输入字符串和子字符串到替换值的映射,并应用这些映射,以便替换输入字符串中尽可能多的字符。例如: replaceGlobal("abcde", { 'a' -> 'w', 'abc' -> 'x', 'ab' -> 'y', 'cde' -> 'z' }) 将通过应用'ab'->'y'和'cde'->'z'返回“yz” 该函数将只应用一轮替换,因此它不能替换

我正在寻找一些指针来编写一个函数(我们称之为
replaceGlobal
),该函数接受输入字符串和子字符串到替换值的映射,并应用这些映射,以便替换输入字符串中尽可能多的字符。例如:

replaceGlobal("abcde", {
    'a' -> 'w',
    'abc' -> 'x',
    'ab' -> 'y',
    'cde' -> 'z'
})
将通过应用
'ab'->'y'
'cde'->'z'
返回
“yz”

该函数将只应用一轮替换,因此它不能替换一个值,然后将替换值的一部分用作另一次替换的一部分

贪婪方法会产生非最佳结果(如Javascript所示):

返回
'xde'

有什么好的起点吗

我认为问题归结为在加权DAG中找到成本最低的路径,该DAG由输入字符串作为脊椎和替换提供的其他边构成:

   /------x------------\
  /-----y------\        \
 /---w--\       \        \ /-------z------\
0 -----> a ----> b -----> c -----> d ----> e ----> $
其中沿脊椎的边的成本为1,但其他边的成本为零

但这可能会使事情变得过于复杂。

在我看来,这是前进的方向。这是由于以下限制:

函数将只应用一轮替换,因此它不能 替换一个值,然后将替换值的一部分用作 另一个替代品


具体来说,假设您有一些随机字符串abcdefg作为输入。现在应用一些规则来替换中间部分,比如de->x。现在您有了abcxfg,其中现在允许您操作的唯一(较小的子问题)字符串是abcfg。对于重复的子字符串,您可以利用

基于@Matt Timmermans的评论和最初的DAG想法,我第一次尝试用Javascript实现了以下内容(我对算法本身比对任何特定语言实现更感兴趣):

它通过了一些简单的单元测试,但我可能忽略了一些愚蠢的事情,它看起来仍然比需要的复杂


您还可以将
dict
设置为一组字符,以便更轻松地查找匹配项(并对
open
执行相同操作)。即使使用trie,我相信这种方法仍然是
O(str.length*dict.length)

你能为
“abcde”->“yz”
,等等添加一个规则吗?@BrentWashburne如果我理解正确,不。从初始集生成所有可能的替换置换是不合理的。如果这不是你的建议,请澄清。是的,这是我的建议。把
'cde'->'z'
放在第一位怎么样?这个问题有一个非常简单的从左到右扫描动态编程解决方案,需要O(N*M)时间,其中N是字符串的长度,M是搜索的最长字符串的长度。细节取决于实现语言。你用什么语言工作?另外,你想在真实的生产代码中这样做吗?哦,解决方案看起来很像你画的图片。从左到右工作,你只需考虑跳过当前位置的边。
   /------x------------\
  /-----y------\        \
 /---w--\       \        \ /-------z------\
0 -----> a ----> b -----> c -----> d ----> e ----> $
const replaceGlobal = (str, dict) => {
    let open = []; // set of substitutions being actively explored
    let best = { value: [], weight: 0 }; // optimal path info

    // For each character in the input string, left to right
    for (let c of str) {
        // Add new nodes to `open` for all `substitutions` that
        // start with `c`
        for (let entry of dict)
            if (entry.match[0] === c)
                open.push({
                    value: best.value.concat(entry.sub),
                    rest: entry.match,
                    weight: best.weight
                });

        // Add current character onto best path
        best.value.push(c);
        ++best.weight;

        // For each `open` path, try to match against the current character
        let new_open = [];
        for (let o of open) {
            if (o.rest[0] === c) {
                if (o.rest.length > 1) { // still more to match
                    new_open.push({
                        rest: o.rest.slice(1),
                        value: o.value,
                        weight: o.weight
                    });
                } else { // full match found
                    if (o.weight < best.weight)
                        best = o;
                }
            }
        }
        open = new_open;
    }
    return best.value.join('');
};
replaceGlobal('abcde', [
    { match: 'a', sub: 'w' },
    { match: 'abc', sub: 'x' },
    { match: 'ab', sub: 'y' },
    { match: 'cde', sub: 'z' }
])) === 'yz'