如何找到最短的文本,如自动完成?

时间:2015-05-08 14:58:42

标签: string algorithm

我有一个字符串列表,我想找到最短的唯一方法来识别它们。它有点像自动完成,但对于给定的集合,它将始终是最短的可识别方式。

作为一个例子。

PA  for Paddington
PE  for Penryn
PLO for Plymouth
PLP for Plympton
PO  for Portsmouth
Q   for Quebec

我有几千个名字(他们不是城市,但程序名称)。

我需要一个相对较短的序列(对于上面的列表,键和值都按顺序排列)。

任何技术/算法都很有用。

我知道我必须编写它(使用PHP),但只要我能理解算法,我就开心。

我认为我必须按照目前的情况构建一个值树,然后开始一次导航该树一个字符,忽略具有单个选项的序列(例如Plymouth / Plympton中的L和Y)。 / p>

所以,从魁北克的Q开始,我发现在整个树中,所有后续字母只使用一次,所以在那个阶段Q就够了。

2 个答案:

答案 0 :(得分:0)

您可以从创建哈希表结构开始,该结构将可能的子字符串映射到以该子字符串开头的所有名称的列表。这可能最终成为一个非常大的数据结构,但由于您可以在达到唯一子字符串的那一刻短路,因此可以防止大小变得过大。以下是使用C#的示例:

var names = new[]{
"Paddington",
"Penryn",
"Plymouth",
"Plympton",
"Portsmouth",
"Quebec"};
// First, for any given subsequence, find groups of names that
// start with it.
var groups = new Dictionary<string, List<string>>();
ILookup<string, string> newGroups;
List<string> namesToProcess = names.ToList();
int i = 0;
do
{
    // Stop looking at names once we're getting substrings too long for them.
    namesToProcess = namesToProcess.Where(n => n.Length >= i).ToList();
    newGroups = namesToProcess.ToLookup(n => n.Substring(0, i));
    foreach(var g in newGroups)
    {
        groups.Add(g.Key, g.ToList());
    }
    // stop looking at names once we find that they're the only ones
    // matching a given substring.
    namesToProcess = namesToProcess
        .Except(newGroups
            .Where(g => g.Count() == 1)
            .Select(g => g.Single()))
        .ToList();
    i++;
} while (newGroups.Any());

现在很容易查找有多少项匹配给定的子序列,为任何给定的名称构建最佳代码是一项简单的任务。您从一个空字符串开始,并添加每个字母,以帮助您缩小可能性的数量:

// Now build the best code to use for each name
var codeNamePairs = names.ToDictionary(n => 
{
    var sb = new StringBuilder();
    for(int j = 0; j < n.Length; j++)
    {
        var prefix = n.Substring(0, j+1);
        var withSamePrefix = groups[prefix];
        // Only add the next letter if it helps to narrow down
        // the possibilities
        if(withSamePrefix.Count != groups[sb.ToString()].Count)
        {
            sb.Append(n[j]);
        }
        if(withSamePrefix.Count == 1)
        {
            // Once we reach a prefix that's unique to this name,
            // then we know we've built the code we want.
            break;
        }
    }
    return sb.ToString();
});

我不确定代码将如何轻松地转换为PHP,但希望我能够很好地传达这个概念。

答案 1 :(得分:-1)

我首先按字母顺序对字符串进行排序。然后你有一个像你的列表:

Paddington
Penryn
Plymouth
Plympton
Portsmouth
Quebec

现在,对于从顶部开始的每个元素,我找到前一个元素和下一个元素都不以的最短字符串。在我们的例子中,它是这样的:

Paddigton无法P,因为下一个元素以它开头,但它可以有Pa,因为下一个元素不会以它开头。

对于Penryn,我们从先前的ID开始减去足够的Penryn字母开头的字母 - 我们将a带走并保留P。现在我们重复一遍:前一个元素以P开头,因此我们添加一个字母并获得Pe。在这种情况下,上一个和下一个都不会从它开始,因此我们将此ID分配给Penryn

使用Plymouth,重复上述步骤,我们会得到Plymo ID。

在分析Plympton时,最初的先前ID减少步骤会给我们Plym,我们只需要添加一个字母,这样前一个和下一个都不会从这个id开始。

等等。

现在,这并没有像你提出的那样生成相同的ID,但是在我看来,PLO从算法的角度来看并不是普利茅斯的好身份。