在C#中如何从字符串生成一个非随机数?

3

我想要从一个字符串中生成一个0-9的数字。生成的数字不必可预测,但同一个字符串必须始终生成相同的数字。

我的初始想法是只需使用string.GetHashCode()并取最后一位数字即可。

如果我这样做,那么(a)我是否保证总是得到相同的数字,(b)我是否会在0-9之间获得合理均匀分布的数字?

或者,有没有更好的方法来实现我的目标?


1
你想要类似于校验和生成器的东西吗? - user1666620
4
你不能依赖GetHashCode方法的一致性。 - theB
正如@johnny5所说,您可以使用string.GetHashCode,但这种复杂的操作并没有太多意义,您只是想从0-9范围内获取哈希值,这将在任何情况下都会产生大量碰撞。因此,例如,您可以只取字符串的第一个字节,并取其十进制表示中的第一个数字,或者对字符串长度执行相同的操作。 - Ilia Maskov
最常见的随机数是47。你可以硬编码它。开个玩笑! - Jerry Nixon
3
为了明确,GetHashCode 在你的应用程序 进程 的生命周期内肯定是“一致”的(即对于具有相同字符串值的每个 string 实例,都会返回相同的值)。因此,在应用程序运行期间,给定字符串值的哈希是固定的。如果不是这种情况,GetHashCode 将无用. 但是:如果您退出进程,将 .NET Framework 更新为新版本,然后再次启动应用程序,则该字符串值在新的 BCL 版本下可能具有不同的哈希值。 - Jeppe Stig Nielsen
显示剩余2条评论
4个回答

8
这应该能解决问题 - 我用它进行确定性模拟:
public static long GetDeterministicId(string m)
{
    return (long) m.ToCharArray().Select((c, i) => Math.Pow(i, c%5)*Math.Max(Math.Sqrt(c), i)).Sum();
}

编辑

如果您只需要0-9之间的数字,则可以通过取模10来进一步处理:

public static long GetDeterministicId(string m)
{
    return (longg) m.ToCharArray().Select((c, i) => Math.Pow(i, c%5)*Math.Max(Math.Sqrt(c), i)).Sum() % 10;
}

我已经运行了这个程序,针对英语中最常用的1000个单词(https://gist.github.com/deekayen/4148741#file-1-1000-txt),0-9的分布情况如下:
0 -> 156
1 -> 163
3 -> 114
7 -> 79
6 -> 72
9 -> 55
2 -> 128
8 -> 45
5 -> 89
4 -> 99

这并不完美,但还可以。

编辑2

进一步测试表明,将第一个模数替换为8(即Math.Pow(i, c%8)*)可以产生更好的分布:

0 -> 95
1 -> 113
2 -> 148
3 -> 91
4 -> 68
5 -> 92
6 -> 119
7 -> 79
8 -> 99
9 -> 96

编辑 3

好的,获胜者是

return (int)m.ToCharArray().Select((c, i) => Math.Pow(i+2, c % 8) * Math.Max(Math.Sqrt(c), i+2)).Sum() % 10;

而 0-9 的分布为

0 -> 90
1 -> 96
2 -> 100
3 -> 99
4 -> 97
5 -> 106
6 -> 110
7 -> 90
8 -> 103
9 -> 109

这足够接近均匀分布!


2
对于一种非常“低技术”的方法,比起rbm的回答略逊一筹……你可以这样做:
string strEntry = "lol"; //Your String Here
int intNum = (int)strEntry[strEntry.Length - 1]; //To Convert last letter to its numeric equivalent. Jeppe Stig Nielsen's suggestion
intNum = int.Parse(intNum.ToString().Substring(intNum.ToString().Length - 1)); //Get the last digit of the number you got from previous step

你得到的数字一定是从0到9之间的一个数字,并且始终相同。另外,我想你也很容易理解代码在做什么。

或者...你可以使用稍微高级一点的方法,它只是将字符串中每个字母的数值相加,然后返回该值的最后一位数字:

string strEntry = "lol";
List<int> intList = new List<int>();
foreach (char c in strEntry)
{
   intList.Add((int)c);
}
int intNum = intList.Sum();
intNum = int.Parse(intNum.ToString().Substring(intNum.ToString().Length - 1));

如果你不想仅仅使用上面第二个选项中提供的最后一位数字……你可以这样做:

string strEntry = "lol";
List<int> intList = new List<int>();
foreach (char c in strEntry)
{
   intList.Add((int)c);
}
int intNum = intList.Sum();
while (intNum.ToString().Length != 1)
{
   intList.Clear();
   foreach (char c in intNum.ToString())
   {
       intList.Add(int.Parse(c.ToString()));
   }
   intNum = intList.Sum();
}
//You can just get the number you required from intNum

我猜,但是当你将一个字符转换为数字时,它会给你该字符的ASCII十进制值。这就是这里使用的内容。我也不是直接转换字符串,而是将它们分解成字符。 - Kaitlyn
1
不是的。转换为int会产生Unicode代码点。对于ASCII /基本拉丁字符,这与ASCII代码点相同,但Unicode远不止ASCII。 - Sebastian Negraszus
好的,也许我错了,它可能不是ASCII码,但至少它能完成工作。我会删除任何关于ASCII码的引用。 - Kaitlyn
1
为了获取字符串strEntry的最后一个字符(即最后一个UTF-16 代码单元),更容易使用索引器,因此可以使用以下代码:int intNum = strEntry[strEntry.Length - 1]; - Jeppe Stig Nielsen

0

有很多方法可以实现这个功能。例如,您可以取所有字符的总和除以10的余数。

public static int HashString(string str)
{
   if(string.IsNullOrEmpty(str)) return 0;
   return str.ToCharArray().Sum(c => (int)c) % 10;
}

0
“我是否保证对于相同的字符串始终得到相同的数字?”
不是的。正如B在评论中提到的,GetHashCode的值是实现细节,不一定在不同版本的.NET中一致。您最好编写自己的函数。
那么简单的校验和呢?
public static int CheckSum(string s)
{
    int sum = 0;
    foreach (char c in s)
    {
        sum = (sum + c)%10;
    }
    return sum;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接