是否有循环散列函数?

时间:2010-04-06 13:24:53

标签: string rotation hash

考虑到这个question on testing string rotation,我想知道:是否存在循环/循环散列函数? E.g。

h(abcdef) = h(bcdefa) = h(cdefab) etc

用于此目的包括可扩展的算法,它可以相互检查 n 字符串,以查看其他字符串在哪里旋转。

我认为哈希的本质是提取特定于订单但不是特定于位置的信息。也许某些东西会找到确定性的“第一个位置”,旋转到它并散列结果?

这一切似乎都是合情合理的,但此刻略显超出我的掌握;它必须已经在那里......

8 个答案:

答案 0 :(得分:9)

我赞同你的确定性“第一个位置” - 找到“最少”的角色;如果它出现两次,请使用下一个字符作为平局破坏者(等)。然后,您可以旋转到“规范”位置,并以正常方式散列。如果断路器在弦乐的整个过程中运行,那么你有一个自身旋转的弦(如果你看到我的意思),你选择哪个“第一”并不重要。< / p>

所以:

"abcdef" => hash("abcdef")
"defabc" => hash("abcdef")
"abaac" => hash("aacab") (tie-break between aa, ac and ab)
"cabcab" => hash("abcabc") (it doesn't matter which "a" comes first!)

答案 1 :(得分:7)

更新:正如Jon指出的那样,第一种方法不能很好地处理重复的字符串。当遇到重复的字母对并且得到的XOR为0时出现问题。这是我认为修改原始算法的修改。它使用Euclid-Fermat sequences为字符串中每个额外出现的字符生成成对互质整数。结果是重复对的XOR不为零。

我还略微清理了算法。请注意,包含EF序列的数组仅支持0x00到0xFF范围内的字符。这只是演示算法的廉价方式。此外,算法仍然具有运行时O(n),其中n是字符串的长度。

static int Hash(string s)
{
    int H = 0;

    if (s.Length > 0)
    {
        //any arbitrary coprime numbers
        int a = s.Length, b = s.Length + 1;

        //an array of Euclid-Fermat sequences to generate additional coprimes for each duplicate character occurrence
        int[] c = new int[0xFF];

        for (int i = 1; i < c.Length; i++)
        {
            c[i] = i + 1;
        }

        Func<char, int> NextCoprime = (x) => c[x] = (c[x] - x) * c[x] + x;
        Func<char, char, int> NextPair = (x, y) => a * NextCoprime(x) * x.GetHashCode() + b * y.GetHashCode();

        //for i=0 we need to wrap around to the last character
        H = NextPair(s[s.Length - 1], s[0]);

        //for i=1...n we use the previous character
        for (int i = 1; i < s.Length; i++)
        {
            H ^= NextPair(s[i - 1], s[i]);
        }
    }

    return H;
}


static void Main(string[] args)
{
    Console.WriteLine("{0:X8}", Hash("abcdef"));
    Console.WriteLine("{0:X8}", Hash("bcdefa"));
    Console.WriteLine("{0:X8}", Hash("cdefab"));
    Console.WriteLine("{0:X8}", Hash("cdfeab"));
    Console.WriteLine("{0:X8}", Hash("a0a0"));
    Console.WriteLine("{0:X8}", Hash("1010"));
    Console.WriteLine("{0:X8}", Hash("0abc0def0ghi"));
    Console.WriteLine("{0:X8}", Hash("0def0abc0ghi"));
}

输出现在是:

7F7D7F7F
7F7D7F7F
7F7D7F7F
7F417F4F
C796C7F0
E090E0F0
A909BB71
A959BB71

第一个版本(未完成):使用可交换的XOR(顺序无关紧要)和另一个涉及互质的小技巧,以组合字符串中的有序字母对的哈希值。这是C#中的一个例子:

static int Hash(char[] s)
{
    //any arbitrary coprime numbers
    const int a = 7, b = 13;

    int H = 0;

    if (s.Length > 0)
    {
        //for i=0 we need to wrap around to the last character
        H ^= (a * s[s.Length - 1].GetHashCode()) + (b * s[0].GetHashCode());

        //for i=1...n we use the previous character
        for (int i = 1; i < s.Length; i++)
        {
            H ^= (a * s[i - 1].GetHashCode()) + (b * s[i].GetHashCode());
        }
    }

    return H;
}


static void Main(string[] args)
{
    Console.WriteLine(Hash("abcdef".ToCharArray()));
    Console.WriteLine(Hash("bcdefa".ToCharArray()));
    Console.WriteLine(Hash("cdefab".ToCharArray()));
    Console.WriteLine(Hash("cdfeab".ToCharArray()));
}

输出结果为:

4587590
4587590
4587590
7077996

答案 2 :(得分:2)

您可以通过始终从具有“最低”(按字母顺序排列)子字符串的位置开始找到确定性的第一个位置。所以在你的情况下,你总是从“a”开始。如果有多个“a”,则必须考虑两个字符等。

答案 3 :(得分:1)

我确信您可以找到一个可以生成相同哈希值的函数,而不管输入中的字符位置如何,但是,对于每个可以想到的输入,您将如何确保h(abc)!= h(efg)? (所有哈希算法都会发生冲突,所以我的意思是,你如何最大限度地降低这种风险。)

即使在生成散列之后,您还需要一些额外的检查,以确保字符串包含相同的字符。

答案 4 :(得分:1)

这是使用Linq的实现

public string ToCanonicalOrder(string input)
{
    char first = input.OrderBy(x => x).First();
    string doubledForRotation = input + input;
    string canonicalOrder 
        = (-1)
        .GenerateFrom(x => doubledForRotation.IndexOf(first, x + 1))
        .Skip(1) // the -1
        .TakeWhile(x => x < input.Length)
        .Select(x => doubledForRotation.Substring(x, input.Length))
        .OrderBy(x => x)
        .First();

    return canonicalOrder;
}

假设通用生成器扩展方法:

public static class TExtensions
{
    public static IEnumerable<T> GenerateFrom<T>(this T initial, Func<T, T> next)
    {
        var current = initial;
        while (true)
        {
            yield return current;
            current = next(current);
        }
    }
}

样本用法:

var sequences = new[]
    {
        "abcdef", "bcdefa", "cdefab", 
        "defabc", "efabcd", "fabcde",
        "abaac", "cabcab"
    };
foreach (string sequence in sequences)
{
    Console.WriteLine(ToCanonicalOrder(sequence));
}

输出:

abcdef
abcdef
abcdef
abcdef
abcdef
abcdef
aacab
abcabc

然后在必要时调用结果.GetHashCode()。

如果将ToCanonicalOrder()转换为扩展方法,则使用示例:

sequence.ToCanonicalOrder().GetHashCode();

答案 5 :(得分:1)

一种可能性是将输入的所有循环移位的散列函数组合成一个不依赖于输入顺序的元散列。

更正式的,请考虑

for(int i=0; i<string.length; i++) {
  result^=string.rotatedBy(i).hashCode();
}

您可以将^ =替换为任何其他可交换操作。

更多例外,请考虑输入

“ABCD”

获取我们的哈希值

hash(“abcd”)^ hash(“dabc”)^ hash(“cdab”)^ hash(“bcda”)。

正如我们所看到的,采用任何这些排列的哈希只会改变你评估XOR的顺序,这不会改变它的值。

答案 6 :(得分:0)

我为大学的一个项目做了类似的事情。我曾经尝试过两种方法来优化旅行商问题。我认为如果不保证元素是唯一的,那么第二个解决方案需要更多检查,但第一个应该可以工作。

如果你可以将字符串表示为关联矩阵,那么abcdef看起来像

  a b c d e f
a   x
b     x
c       x
d         x
e           x
f x

但这些协会的任何组合也是如此。比较这些矩阵将是微不足道的。


另一个更快捷的技巧是旋转字符串,使“第一个”字母成为第一个字母。然后,如果你有相同的起点,相同的字符串将是相同的。

这是一些Ruby代码:

def normalize_string(string)
  myarray = string.split(//)            # split into an array
  index   = myarray.index(myarray.min)  # find the index of the minimum element
  index.times do
    myarray.push(myarray.shift)         # move stuff from the front to the back
  end
  return myarray.join
end

p normalize_string('abcdef').eql?normalize_string('defabc') # should return true

答案 7 :(得分:0)

也许为每个偏移使用滚动哈希(RabinKarp之类)并返回最小哈希值?可能会有碰撞。