优惠券应该是唯一的(这意味着我们将它们作为键存储在数据库的表中,并且对于每个优惠券,我们都要检查其唯一性)
#1 楼
让我们看一些代码:Random random = new Random((int)DateTime.Now.Ticks);
您不需要从时钟为随机构造函数创建种子,无参数构造函数可以这样做:
Random random = new Random();
List<string> characters = new List<string>() { };
此时您无需在列表中放置任何项目,就不需要初始化括号:
List<string> characters = new List<string>();
+=
连接字符串是不好的做法。字符串不可变,因此不会在字符串末尾附加。像x += y;
这样的代码实际上最终以x = String.Concat(x, y)
结尾。您应该使用StringBuilder
来构建字符串。result += characters[random.Next(0, characters.Count)];
为什么要睡在循环中间?
而不是创建列表在字符串中,只需使用字符串文字从以下字符中选择字符:
Thread.Sleep(1);
考虑是否应包含所有这些字符,否则应忽略类似外观的字符,例如
o
,O
和0
。将字符包含在字符串文字中可以轻松实现此操作。编辑:
如果要多次调用该方法,则应将随机数生成器作为参数发送:
public static string GenerateCoupon(int length) {
Random random = new Random();
string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
StringBuilder result = new StringBuilder(length);
for (int i = 0; i < length; i++) {
result.Append(characters[random.Next(characters.Length)]);
}
return result.ToString();
}
用法:
public static string GenerateCoupon(int length, Random random) {
string characters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
StringBuilder result = new StringBuilder(length);
for (int i = 0; i < length; i++) {
result.Append(characters[random.Next(characters.Length)]);
}
return result.ToString();
}
示例输出:
Random rnd = new Random();
string[] coupon = new string[10];
for (int i = 0; i < coupon.Length; i++) {
coupon[i] = GenerateCoupon(10, rnd);
}
Console.WriteLine(String.Join(Environment.NewLine,coupon));
评论
\ $ \ begingroup \ $
我睡了1毫秒,因为生成的优惠券是相同的。试试看。微软表示,Random算法是基于时间的,当您在一个CPU时钟滴答中简单地创建许多随机数时,每一代随机产生的数字都是相同的。
\ $ \ endgroup \ $
– Saeed Neamati
2011年11月13日下午13:03
\ $ \ begingroup \ $
@SaeedNeamati:只是基于时钟的初始种子,以下随机数是基于前一个而不是时钟。因此,在每次使用Next之间进行睡眠根本没有任何效果。如果要多次调用GenerateCoupon,则应在方法外部创建一个随机生成器,并将其作为参数传递。
\ $ \ endgroup \ $
–古法
2011年11月13日13:17
\ $ \ begingroup \ $
@SaeedNeamati:那你做错了...我在上面加了一个例子。
\ $ \ endgroup \ $
–古法
2011年11月13日在16:47
\ $ \ begingroup \ $
@EdmundSchweppe:是的,这就是为什么我在上面写道,他应该在方法之外创建一个随机生成器,并将其作为参数传递。
\ $ \ endgroup \ $
–古法
2011-11-14 21:44
\ $ \ begingroup \ $
我意识到了这一点,但是由于这个问题要求使用唯一的字符串,所以我认为这是尝试的方法。 “优惠券应该是唯一的”。这就是为什么我要问的原因,因为那里有很多东西要学习,而且我没有办法认为这可以保证一个唯一的字符串,但是由于这是我所需要的,所以我认为这段代码实际上可以做到。
\ $ \ endgroup \ $
–詹姆斯·赫尔福德(James Hurford)
2011-11-17 23:59
#2 楼
您不应该使用Random
生成优惠券。您的优惠券在某种程度上是可以预测的:如果某人可以看到一些优惠券(尤其是几个连续的优惠券),他们将能够重建种子并生成所有优惠券。对于大多数数值模拟和某些游戏来说,Random
是可以的,当您需要生成不可预测的值时,这根本不好。您的优惠券就像密码一样;您需要加密质量的随机性。幸运的是,C#库中有一个加密质量的随机生成器:System.Security.Cryptography.RNGCryptoServiceProvider
。此RNG返回字节。一个字节中有256个可能的值。您的优惠券只能使用62个字符之一,因此您需要拒绝不映射到ASCII字母或数字的字节。
此外,在逐块构建字符串时,应使用
StringBuilder
。完成构建后,将其解析为字符串。var couponLength = 32;
StringBuilder coupon = new StringBuilder(couponLength);
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] rnd = new byte[1];
int n = 0;
while (n < couponLength) {
rng.GetBytes(rnd);
char c = (char)rnd[0];
if ((Char.IsDigit(c) || Char.IsLetter(c)) && rnd[0] < 127) {
++n;
coupon.Append(c);
}
}
return coupon.ToString();
通过拒绝较少的值,可以使生成速度提高约4倍。除仅接受映射到所需字符的62个值外,将其除以4得到64个等概率值之一,然后接受其中的62个(将它们映射到正确的字符)并拒绝2。 />
评论
\ $ \ begingroup \ $
为什么我们拒绝数字,而没有从62开始提醒呢?因为256不能被256整除,是否可以进行均匀分布?
\ $ \ endgroup \ $
– C0DEF52
20-05-10在11:40
\ $ \ begingroup \ $
@ C0DEF52是的。参见例如cs.stackexchange.com/questions/29204/…、cs.stackexchange.com/questions/2605/…
\ $ \ endgroup \ $
–吉尔斯'所以-不再是邪恶的'
20 May 10 '14:45
#3 楼
一些一般性的想法,我希望所有这些也可以在C#中工作。如果不是正确的C#语法,请随时编辑答案。1,将
characters
列表的类型更改为char
,并将循环变量也更改为char
。这样,您就不必强制转换,并且更容易阅读for
循环: 3,从列表中删除
Thread.Sleep(1);
,0
,O
和o
,l
。将它们混合起来很容易。4,我会掏出
1
方法:List<char> characters = new List<char>() { };
for (char c = '0'; i <= '9'; c++) {
characters.Add(c);
}
...
for (int i = 0; i < length; i++){
result += characters[random.Next(0, characters.Count)];
}
#4 楼
这是我将要实现的代码。它更快,更简单public static string GenerateCoupon(int length)
{
return Guid.NewGuid().ToString().Replace("-", string.Empty).Substring(0, 10);
}
使用公会gaurantees唯一性,因此您的优惠券代码不会重叠。
评论
\ $ \ begingroup \ $
+1,这是个好主意,OP会检查代码是否唯一(将它们与以前的优惠券代码进行比较),但是我不得不提到“新Guid的价值将是零或等于其他Guid的值非常低。” msdn.microsoft.com/zh-CN/library/system.guid.newguid.aspx,并且GUID的唯一性与GUID的第一个字符和唯一字符不同。
\ $ \ endgroup \ $
–palacsint
2011年11月16日21:00
\ $ \ begingroup \ $
是的,任何两个GUID都不唯一的可能性非常小;但是,如果仅使用其中一部分,则可能会更大。
\ $ \ endgroup \ $
–钟表缪斯
2011年11月16日23:50
\ $ \ begingroup \ $
这很容易解决。如果字符大小写很重要,那么他可以将随机字符设置为小写或大写。这将解决冲突的可能性。我高度怀疑他是否会发生碰撞,除非他在超级计算机上紧紧地接连抽出引导。
\ $ \ endgroup \ $
–Luke101
2011年11月17日在15:17
\ $ \ begingroup \ $
不需要'Replace(“-” ...)`调用,只需执行Guid.NewGuid()。ToString(“ N”),它将只用十六进制字符而不是破折号进行格式化。
\ $ \ endgroup \ $
– pstrjds
2011年11月17日在18:10
\ $ \ begingroup \ $
@ Luke101错误的解决方案。对我来说,将“随机”字符更改为小写/大写听起来并不好。每个字符只允许16个不同的选项,总共16 ^ 10; OP允许每个字符62个不同的选项。您只使用了允许的位置字符的四分之一,或总解决方案空间的(1/4)^ 10。与OP当前的解决方案相比,您将获得更多的重叠。
\ $ \ endgroup \ $
–柯克·布罗德赫斯特(Kirk Broadhurst)
2011年11月17日,23:51
#5 楼
如果可以接受稍长的字符串,则可以使用ShortGuid类。这需要一个Guid,使其比您以前使用的32字节格式({xxx-xxx-xxx ...})更具可读性。作者的示例是:
c9a646d3-9c61-4cb7-bfcd-ee2522c8f633}
缩写为:
00amyWGct0y_ze4lIsj2Mw
对于优惠券代码来说可能太长了。另一个建议是使用明显的密码生成器,该链接是我从Java源转换而来的。一些大写的例子:
#6 楼
Thread.Sleep(1)
是一个实际的问题,尤其是如果您要使用它一次生成数千张或数百万张优惠券时。您唯一需要它的原因是因为您正在为每个优惠券创建一个新的Random
实例,并使用当前时间对该实例进行播种。 Random
的默认构造函数已经可以处理基于时间的种子。如果您将实例设为静态,则只需构造一次即可,从而避免了重复问题。循环,尽管我会让列表成为惰性初始化的属性,而不是每次都重新创建它。我完全同意@Guffa关于使用List<char>
来创建优惠券而不是for
运算符的观点。public class CouponGenerator
{
private static List<char> _allowed = null;
private static List<char> AllowedChars
{
get
{
if (_allowed == null)
{
_allowed = new List<char>();
for (char c = 'A'; c < 'Z'; c++)
{
_allowed.Add(c);
}
for (char c = 'a'; c < 'z'; c++)
{
_allowed.Add(c);
}
for (char c = '0'; c < '9'; c++)
{
_allowed.Add(c);
}
}
return _allowed;
}
}
private static Random _rg = new Random();
public static string GenerateCoupon(int length)
{
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
{
sb.Append(AllowedChars[_rg.Next(0, AllowedChars.Count)]);
}
return sb.ToString();
}
}
#7 楼
我相信主要的问题不是生成方法,而是数据的唯一性。据我所知,有一个数据库,您必须用过去生成的数据追溯检查生成的数据的唯一性。 br />
假设有一个新的要求,即您的业务方要求您再提供100.000个唯一数据。过去您已经生成了10.000.000。我的建议是;
1-生成例如120.000唯一密钥(大于要求)
2 -将这些数据批量插入到临时表中。
3-然后执行存储过程以比较SQL Server端的两个表,
4-如果您的临时表包含与旧的10.000.000数据集不同的值100.000 ,那么您可以将那些100.000插入主表,将它们返回到业务端,
否则,例如120.000中的60.000与旧数据集不同,但60.000相同,在这种情况下,您的存储过程可以返回int = 40.000,这将使您理解您还需要另外40.000数据。
转到步骤1,根据需要执行另一生成器60.000、100.000,并执行相同的步骤。 >
可能不是最好的解决方案,但我相信它会很快。因为使用下面的代码生成1.000.000个随机字母数字字符串最多需要2秒钟;
过去,我使用下面的代码来生成唯一的随机数据
为了使运行时生成的数据具有唯一性,我使用了哈希表,如上所示。
public static string GetUniqueKey()
{
int size = 7;
char[] chars = new char[62];
string a = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
chars = a.ToCharArray();
RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider();
byte[] data = new byte[size];
crypto.GetNonZeroBytes(data);
StringBuilder result = new StringBuilder(size);
foreach (byte b in data)
result.Append(chars[b % (chars.Length - 1)]);
return Convert.ToString(result);
}
评论
\ $ \ begingroup \ $
如果要合并用户帐户,则应使用[联系方式]表格来请求帐户合并
\ $ \ endgroup \ $
–Vogel612♦
19年8月21日在11:58
#8 楼
另一种方法是: public static string CouponGenerator(int length)
{
var sb = new StringBuilder();
for (var i = 0; i < length; i++)
{
var ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * _random.NextDouble() + 65)));
sb.Append(ch);
}
return sb.ToString();
}
private static readonly Random _random = new Random();
#9 楼
对于我自己的未来:@Guffa实现的单例实例,以避免重新创建
StringBuilder
和Random
对象;更少的GC和更快的速度。另外,如果需要加密版本,则@Gilles在下面实现了GenerateUId。 > 用法:
public class UIdGenerator
{
private static readonly Lazy<UIdGenerator> _lazy = new Lazy<UIdGenerator>(
() => new UIdGenerator(), LazyThreadSafetyMode.ExecutionAndPublication);
public static UIdGenerator Instance
{
get { return UIdGenerator._lazy.Value; }
}
private readonly Random _random = new Random();
private readonly Dictionary<int, StringBuilder> _stringBuilders = new Dictionary<int, StringBuilder>();
private const string CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
private UIdGenerator()
{
}
public string GenerateUId(int length)
{
StringBuilder result;
if (!_stringBuilders.TryGetValue(length, out result))
{
result = new StringBuilder();
_stringBuilders[length] = result;
}
result.Clear();
for (int i = 0; i < length; i++)
{
result.Append(CHARACTERS[_random.Next(CHARACTERS.Length)]);
}
return result.ToString();
}
}
#10 楼
稍微修改Berrly的答案。给出带字母的数字并带有大小写,并确保没有创建重复项。我将开始使用它来获取访问令牌,这些访问令牌将通过电子邮件发送给用户,并以文本形式登录到我的系统。这将比今天的大多数这些系统更安全,因为它们通常具有6个字符(固定长度)和数字(有限集),因此更容易预测。即使它们都有时间限制,也要花上更多的时间,因为它并不难,为什么不花那么多的力气呢。 br /> Side Rant:我相信随着社交登录电子邮件/短信访问令牌的出现,用于授予对其他网站/应用程序的访问权限的方式将会成为现实。没有人想记住所有这些密码,因此它们使用相同的密码,我们将其归咎于他们,这很愚蠢,因为这只是人性。然后我们组成密码持有人,您需要一个仅包含1个其他所有密码的密码,我们认为这是一个很好的解决方案...您最安全的做法是经常更改密码并使它们变得复杂,但是因为有那么多没人做我们不想花一生的时间来更改密码。理想情况下,我们将使用1个密码来表示我们的电子邮件,然后所有内容都将通过步骤1进行身份验证,然后通过电话进行步骤2。人们会更愿意经常更改其ONE密码,因为知道它只有1个并且用于访问授予他们访问所有其他应用程序/网站的令牌。//Rextester.Program.Main is the entry point for your code. Don't change it.
//Compiler version 4.0.30319.17929 for Microsoft (R) .NET Framework 4.5
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Text;
namespace Rextester
{
public class Program
{
public static void Main(string[] args)
{
//Your code goes here
Console.WriteLine("Hello, world!");
var blackList = new List<char>{
'[', ']', '`', '{', '}', '|', '\', '/', '~', '^', '_', '-', ':', ';', '<', '>', '=', '?', '@', '0', 'o', 'l', 'I', '1'
};
var accessTokens = new List<string>();
// test by creating a bunch of tokens
for(int i = 0; i < 100; i++)
{
// this makes sure we don't duplicate active access tokens
var token = CouponGenerator(_random.Next(6, 12), blackList);
while(accessTokens.Contains(token)){
token = CouponGenerator(_random.Next(6, 12), blackList);
}
accessTokens.Add(token);
}
// print tokens out
accessTokens.ForEach(x => Console.WriteLine("Access Token: " + x));
}
public static string CouponGenerator(int length, List<char> exclusionList)
{
var sb = new StringBuilder();
for (var i = 0; i < length; i++)
{
var ch = Convert.ToChar(Convert.ToInt32(Math.Floor(62 * _random.NextDouble() + 48)));
while(exclusionList.Contains(ch)){
ch = Convert.ToChar(Convert.ToInt32(Math.Floor(62 * _random.NextDouble() + 65)));
}
sb.Append(ch);
}
return sb.ToString();
}
private static readonly Random _random = new Random();
}
}
评论
这是我关于这个的堆栈溢出的答案。我仍然认为我自己的解决方案比您的解决方案更具可读性,除此之外,我认为palacsint的解决方案是最具可读性的解决方案,它也实现了SRP(两种方法,一种甚至从外部配置源中获取允许的字符,以及用于从该列表中生成随机字符串的一种)。 :)
给每个人自己。
我认为通常您应该避免同时使用大写和小写字母。尤其是如果要包括数字...您将以什么字体输出它们-字符I(大写I),l(小写L)和1(数字1)如何出现?
同时使用上限和下限的@ X-Zero会极大地增加可用数据空间(组合数)。你为什么不使用这些?如果要由人类输入,请选择一种好字体,以使1与I和L之间的区别显而易见(即,不要让营销团队塞满它)