如何比较排列

时间:2017-07-11 22:02:03

标签: c# permutation string-comparison

上下文

我正在构建一个基于Enigma的加密应用程序(为了好玩)。

(我把这个问题放在codegolf.stackexchange.com上,但是他们说这不是主题,并在这里提出建议。)

设计的核心是虚拟转子(Enigma中使用的物理转换器的数字等效物),其中(简单来说)可见字母映射到秘密字母。

例如,如果给定转子上的可见字母是A-Z,那么我们可以像这样描述秘密映射:

ABCDEFGHIJKLMNOPQRSTUVWXYZ - A-Z
EKMFLGDQVZNTOWYHXUSPAIBRCJ - Rotor I
AJDKSIRUXBLHWTMCQGZNPYFVOE - Rotor II
BDFHJLCPRTXVZNYEIWGAKMUSQO - Rotor III

问题

我生成新的随机映射没有问题 - 但我怎么能比较它们的相似程度呢?

我想要做的是生成一组潜在的新转子映射,并通过某种方式评估它们的相似程度,例如:

BDFHJLCPRTXVZNYEIWGAKMUSQO - Rotor III
ZGFHJLCPRXTVDNYEIWBAMUKOQS - Random Rotor A
VZBRGITYUPSDNHLXAWMJQOFECK - Random Rotor B
NYEIWGAKMUSQOBDFHJLCPRTXVZ - Random Rotor C

与转子III相比,随机转子A不同,但B明显更加多变。转子C看起来与转子III不同 - 但不是,它只是转子III切成两半而第二部分放在第一部分前面。

我如何比较这种性质的字符串(或字符数组)?

仅供参考,我将使用C#来构建它,但很高兴接受我可以实现的任何合适的逻辑。

更新

比较不必非常精确。字符串的长度将超过26 - 虽然所有字符串的长度都相同,但会在100+左右变化。排列的数量也会有所不同,但可能是100 +。

2 个答案:

答案 0 :(得分:1)

我建议你看看使用Levenshtein距离。我认为你错了,转子C与转子B的不同,基于使用它来映射可见字母的结果。相似性/差异性的重要性在于对最终输出的影响。

https://www.dotnetperls.com/levenshtein

答案 1 :(得分:1)

一种选择是简单地编写自己的方法来确定两个字符串之间的相似性。

考虑到字符串将始终具有相同的长度并包含相同的字符,以下是您可以考虑相似性的几种方法。一个是简单的位于相同位置的字符的百分比,另一个是第二个字符串中所有字符与第一个字符串中位置的距离的总百分比。

第一个很直接:

private static int GetSimilarity1(string first, string second)
{
    if (first == null) return second == null ? 100 : 0;

    // Set similarity to the percentage of characters in the same position
    var matches = first.Count(chr => first.IndexOf(chr) == second.IndexOf(chr));
    return (int)(matches / (decimal)first.Length * 100);
}

第二个字符串将取第二个字符串中每个字符与第一个字符串中索引的距离,并将其除以索引的最大距离(最差情况)。这将导致精确匹配的低值(0),或者远离它的值越高。然后,通过从一个数字中减去它来转换为百分比(从"坏"百分比(完全匹配为0)转换为"良好"百分比(完全匹配为1)并乘以100.然后将此数字添加到运行总计中。

最后,将该总数除以最终"相似度的字符数"百分比。

为了计算一个值与实际位置的最大距离,我将它在第一个数组中的索引值与(length - index - 1)的值进行比较,其中较大者是字符最远可能是索引:

private static int GetSimilarity2(string first, string second)
{
    if (first == null) return second == null ? 100 : 0;

    int distance = 0;

    for(int i = 0; i < first.Length; i++)
    {
        var thisDist = Math.Abs(second.IndexOf(first[i]) - i);
        var worstDist = Math.Max(first.Length - i - 1, i);
        distance += (int)((1 - thisDist / (decimal) worstDist) * 100);
    }

    return distance / first.Length;
}

为了测试这些方法,我使用了以下代码:

private static void Main()
{
    var rotorIII = "BDFHJLCPRTXVZNYEIWGAKMUSQO";
    var randRotorA = "ZGFHJLCPRXTVDNYEIWBAMUKOQS";
    var randRotorB = "VZBRGITYUPSDNHLXAWMJQOFECK";
    var randRotorC = "NYEIWGAKMUSQOBDFHJLCPRTXVZ";

    Console.WriteLine("Method 1: Rotor III -> Random Rotor A: {0}", 
        GetSimilarity1(rotorIII, randRotorA));
    Console.WriteLine("Method 1: Rotor III -> Random Rotor B: {0}", 
        GetSimilarity1(rotorIII, randRotorB));
    Console.WriteLine("Method 1: Rotor III -> Random Rotor C: {0}", 
        GetSimilarity1(rotorIII, randRotorC));

    Console.WriteLine("-----------------------------------------");

    Console.WriteLine("Method 2: Rotor III -> Random Rotor A: {0}", 
        GetSimilarity2(rotorIII, randRotorA));
    Console.WriteLine("Method 2: Rotor III -> Random Rotor B: {0}", 
        GetSimilarity2(rotorIII, randRotorB));
    Console.WriteLine("Method 2: Rotor III -> Random Rotor C: {0}", 
        GetSimilarity2(rotorIII, randRotorC));

    // Wait for input before closing
    Console.WriteLine("\nDone!\nPress any key to exit...");
    Console.ReadKey();
}

<强>输出

enter image description here