给定一个字符串序列,检查它是不是匹配一个模式

Posted

技术标签:

【中文标题】给定一个字符串序列,检查它是不是匹配一个模式【英文标题】:Given a string sequence of words, check if it matches a pattern给定一个字符串序列,检查它是否匹配一个模式 【发布时间】:2016-03-23 22:03:55 【问题描述】:

我在一次采访中遇到了这个问题,我被困在最好的解决方法上。问题如下:

给定一个单词的字符串序列和一个字符串序列模式,如果单词序列与模式匹配则返回 true,否则返回 false。

匹配的定义:替换变量的单词必须始终跟在该替换之后。例如,如果“f”被替换为“monkey”,那么任何时候我们看到另一个“f”,那么它必须匹配“monkey”,并且任何时候我们再次看到“monkey”,它必须匹配“f”。

示例 输入:“ant dog cat dog”、“a d c d” 输出:真 这是真的,因为每个变量都映射到一个单词,反之亦然。 一只 -> 蚂蚁 d -> 狗 c -> 猫 d -> 狗

输入:“ant dog cat dog”、“a d c e” 输出:假 这是错误的,因为如果我们将“d”替换为“dog”,那么您不能也将“e”替换为“dog”。 一只 -> 蚂蚁 d, e -> dog(d 和 e 都不能同时映射到 dog 所以错误) c -> 猫

输入:“monkey dog eel eel”、“e f c c” 输出:真 这是真的,因为每个变量都映射到一个单词,反之亦然。 e -> 猴子 f -> 狗 c -> 鳗鱼

一开始,我也想过做如下的事情……

function matchPattern(pattern, stringToMatch) 
  var patternBits = pattern.split(" ");
  var stringBits = stringToMatch.split(" ");
  var dict = ;

  if (patternBits.length < 0 
      || patternBits.length !== stringBits.length) 
    return false;
  

  for (var i = 0; i < patternBits.length; i++) 
    if (dict.hasOwnProperty(patternBits[i])) 
      if (dict[patternBits[i]] !== stringBits[i]) 
        return false;
      
     else 
      dict[patternBits[i]] = stringBits[i];
    
  
  return true;


var ifMatches = matchPattern("a e c d", "ant dog cat dog");
console.log("Pattern: " + (ifMatches ? "matches!" : "does not match!"));

但是,我意识到这不起作用并且示例 #2 失败,因为它错误地返回 true。处理此问题的一种方法是使用双向字典或两个字典,即同时存储 "a": "ant" 和 "ant": "a" 并在 if 检查中检查这两种情况。然而,这似乎是浪费空间。有没有更好的方法来解决这个问题而不使用正则表达式?

【问题讨论】:

你对匹配的定义不清楚。写一些解释为什么第一个例子通过,第二个失败。我可以猜到为什么,但很可能你不是来听猜测的。您只是比较单词的首字母吗? 【参考方案1】:

我认为一个简单的选择,即单词列表长度的二次方是验证每个列表索引对在两个列表中具有相同的相等特征。我假设您已经将“单词”和“模式”作为列表获得,并且不需要解析空格和其他任何内容 - 无论如何这应该是一个单独的函数的责任。

function matchesPatternReference(words, pattern) 
    if(words.length !== pattern.length) return false;
    for(var i = 0; i < words.length; i++)
        for(var j = i+1; j < words.length; j++)
            if((words[i] === words[j]) !== (pattern[i] === pattern[j]))
                return false;
    return true;

更好的方法是对两个列表进行规范化,然后比较规范化的列表是否相等。要规范化列表,请将每个列表元素替换为在列表中第一次出现之前出现的唯一列表元素的数量。假设您认为哈希查找和列表追加需要恒定时间,这将与较长列表的长度呈线性关系。我不知道足够多的 javascript 来知道这些是否有必要;当然,在最坏的情况下,即使不相信哈希查找是恒定时间(无论是哪种语言,这个假设都有些可疑),也可以在 n*log(n) 时间内使用合适的数据结构在 n*log(n) 时间内实现该算法背后的想法。

function normalize(words) 
    var next_id = 0;
    var ids = ;
    var result = [];
    for(var i = 0; i < words.length; i++) 
        if(!ids.hasOwnProperty(words[i])) 
            ids[words[i]] = next_id;
            next_id += 1;
        
        result.push(ids[words[i]]);
    
    return result;


function matchesPatternFast(words, pattern) 
    return normalize(words) === normalize(pattern);

注意: 正如 cmets 中所指出的,应该手动检查规范化数组的深度相等性,因为数组上的 === 在 Javascript 中进行身份比较,而不是按元素进行比较。另见How to check if two arrays are equal with Javascript?。

附录: 下面我认为matchesPatternFastmatchesPatternReference 计算相同的函数——但是使用了错误的假设,即数组上的=== 逐点比较元素而不是指针比较。

我们可以定义如下函数:

function matchesPatternSlow(words, pattern) 
    return matchesPatternReference(normalize(words), normalize(pattern));

我观察到normalize(x).length === x.lengthnormalize(x)[i] === normalize(x)[j] 当且仅当x[i] === x[j];因此matchesPatternSlow 计算与matchesPatternReference 相同的函数。

我现在将争辩说matchesPatternSlow(x,y) === matchesPatternFast(x,y)。当然如果normalize(x) === normalize(y) 那么我们将拥有这个属性。 matchesPatternFast 将明显返回 true。另一方面,matchesPatternSlow 通过对其两个输入进行多次查询并验证这些查询对于两个列表是否总是返回相同的结果进行操作:在循环外,查询是 function(x) return x.length ,而在循环内,查询是function(x, i, j) return x[i] === x[j]; 。由于相等的对象将对任何查询做出相同的响应,因此两个规范化列表上的所有查询都将对齐,matchesPatternSlow 也将返回 true

如果normalize(x) !== normalize(y) 怎么办?然后matchesPatternFast 将明显返回false。但是如果它们不相等,那么它们的长度不匹配——在这种情况下,matchesPatternSlow 也将像我们希望的那样从matchesPatternReference 的第一次检查中返回false——或者某个索引处的元素不匹配平等的。假设最小的不匹配索引是inormalize 的一个属性是,索引i 处的元素将等于索引j&lt;i 处的元素,否则它将比索引0i-1 中的最大元素大一个。所以我们现在有四种情况需要考虑:

我们有j1&lt;ij2&lt;i,其中normalize(x)[j1] === normalize(x)[i]normalize(y)[j2] === normalize(y)[i]。但是自从normalize(x)[i] !== normalize(y)[i] 之后,我们就知道normalize(x)[j1] !== normalize(y)[i]。所以当matchesPatternReference 选择索引j1i 时,我们会发现normalize(x)[j1] === normalize(x)[i]truenormalize(y)[j1] === normalize(y)[i]false 并立即返回false,正如我们试图展示的那样。李> 我们有j&lt;i,其中normalize(x)[j] === normalize(x)[i]normalize(y)[i] 不等于normalize(y) 的任何先前元素。然后matchesPatternReference 将在选择索引ji 时返回false,因为normalize(x) 匹配这些索引但normalize(y) 不匹配。 我们有j&lt;i,其中normalize(y)[j] === normalize(y)[i]normalize(x)[i] 不等于normalize(x) 的任何先前元素。与前一种情况基本相同。 我们知道normalize(x)[i]normalize(x) 中的最大早期元素大一,normalize(y)[i]normalize(y) 中的最大早期元素大一。但由于 normalize(x)normalize(y) 同意所有先前的元素,这意味着 normalize(x)[i] === normalize(y)[i],这与我们假设标准化列表在此索引处不同的假设相矛盾。

所以在所有情况下,matchesPatternFastmatchesPatternSlow 都同意 - 因此 matchesPatternFastmatchesPatternReference 计算相同的函数。

【讨论】:

标准化列表的有趣想法。我相信这是可行的,但直接比较两个数组是行不通的(即 normalize(words) === normalize(pattern)),但我明白了。 @skalidindi 你能说说“行不通”是什么意思吗?你担心什么问题?您认为这会给出错误的答案吗? @skalidindi 我添加了一个编辑,我在其中证明了我提出的规范化方案有效——至少与参考实现一样。 我是说在 javascript 中你不能像这样比较数组。例如,如果您打开 chrome 开发工具并执行 ([0,1] === [0,1]) 您将得到错误。请注意,数组的元素和长度相同,但比较仍然返回 false。甚至,更宽松的 == 仍然会返回 false。您必须手动循环遍历数组并单独检查元素。然而,减去这个事实,归一化方案确实有效。 另外,我认为您可以在 python 中使用列表来执行此操作,但即使在幕后,我认为 == 运算符正在逐个检查元素。见docs.python.org/2/tutorial/…。第 5.8 节。【参考方案2】:

对于这种特殊情况,我假设模式是指匹配第一个字符。如果是这样,您可以简单地压缩并比较。

# python2.7

words   = "ant dog cat dog"
letters = "a d c d"
letters2 = "a d c e"

def match(ws, ls):
    ws = ws.split()
    ls = ls.split()
    return all(w[0] == l for w, l in zip(ws + [[0]], ls + [0]))

print match(words, letters)
print match(words, letters2)

最后搞笑的[[0]]和[0]是为了保证pattern和words长度一致。

【讨论】:

使用无参数拆分可能会更好,例如split(),否则像 'ant(double space)dog cat' 这样的输入会因为双空格而引发 IndexError 我不是 Python 专家,所以 + [[0]]+ [0] 看起来确实很有趣。它们似乎并没有改变wsls 之间的长度差异,因此它们“确保模式和单词具有相同长度”的评论非常令人惊讶。你能详细说明它们为什么有用吗?与这些添加一起使用但不是没有添加的输入示例是什么? 你知道当你实现归并排序时,你可以使用一个技巧并在每个列表的末尾添加'+infinity'吗?这是相似的。 zip 迭代到最短列表的长度,因此 'apple', 'a b' 将匹配,除非您检查长度。但是0 永远不能等于字符串,所以'b' == 0 会失败。除了节省空间之外,这在这里并不是非常有用;但是,如果输入是惰性的,那么当模式和字符串的长度不同时,这可能会更有效。 我很抱歉,我对这个问题的理解并不像我应该做的那样清楚,但我并不是说匹配第一个字符。我更新了问题以更清楚。基本上,我对匹配的定义是,如果我用一个词替换一个变量,比如我曾经用“g”替换为狗。然后,如果我看到变量“g”,那么这个词一定是“dog”,或者如果我看到“dog”这个词,那么变量一定是“g”。 @YakymPirozhenko 哦。伊克。对于支持split但可以包含数字的对象,return len(ws) == len(ls) and all(w[0] == l for w, l in zip(ws, ls)) 不是更易读、更健壮吗?

以上是关于给定一个字符串序列,检查它是不是匹配一个模式的主要内容,如果未能解决你的问题,请参考以下文章

Go 语言入门很简单:正则表达式

检查字符串是不是包含模式,忽略位置。并返回项目匹配的索引

检查字符串是不是是字谜

如何检查String是否与Groovy中的模式匹配

字符串的模式匹配算法

这个 Python 神器,能让你摸半天鱼!