I have a function that generates a key of 4 characters that has to be unique for each time. In order to do that, the function first generates a key, and then checks a database table to see if it's in use by someone else.
我有一个函数生成一个4个字符的键,每次都必须是唯一的。为此,该函数首先生成一个密钥,然后检查数据库表以查看其他人是否正在使用它。
If it's not in use, it returns the key, else, it calls itself again, but this causes the function to do an infinite loop, which is a no-no. Here's the whole function:
如果它没有被使用,它返回键,否则,它再次调用自身,但这会导致函数执行无限循环,这是禁止的。这是整个功能:
function key_generator($length = 4)
{
// I've subsequently left out the generating code,
// which is not necesarry in this case
$key = 'xxxx';
if ($this->user_model->valid_key($key) == true)
{
return $key;
}
else
{
$this->key_generator(4);
}
}
What is the correct way to call the function again?
重新调用该函数的正确方法是什么?
By the way, I'm using CodeIgniter, hence $this
.
顺便说一句,我正在使用CodeIgniter,因此$ this。
7 个解决方案
#1
25
I would not use recursive functions for retry-scenarios (since you don't reuse the result of the function, it's pointless to use recursion)... It adds a lot of unnecessary overhead. Do something like this:
我不会将recursive函数用于重试场景(因为你没有重用函数的结果,使用递归是没有意义的)......它增加了很多不必要的开销。做这样的事情:
do {
$key = ...; // Generate your key here...
} while (!$this->user_model->valid_key($key));
return $key;
If you're near the maximum number of keys, this will result in very long loop times, so you might want to put some kind of max limit.
如果您接近最大键数,这将导致非常长的循环时间,因此您可能需要设置某种最大限制。
Oh, and if this is occurring on multiple threads simultaneously and you're checking a database, you should implement table write locking so that the same key can't be inserted twice. Preferably the function that checks whether a key is available should lock, check, and if available write in the same transaction to avoid any collisions.
哦,如果同时在多个线程上发生并且您正在检查数据库,则应该实现表写锁定,以便不能插入相同的键两次。优选地,检查密钥是否可用的功能应该锁定,检查并且如果可用则写入相同的事务以避免任何冲突。
#2
5
You need to return the result of the self-call, otherwise the valid key won't get returned once it recurses.
您需要返回自我调用的结果,否则一旦递归,将无法返回有效密钥。
return $this->key_generator($length);
#3
2
but this causes the function to do an infinite loop,
但这导致函数执行无限循环,
If you absolutely want to keep your recursive strategy you have to define an end case. For example you may define a counter, like this:
如果您绝对想要保持递归策略,则必须定义最终案例。例如,您可以定义一个计数器,如下所示:
function key_generator($length = 4, $limit=5)
{
if($limit === 0) {
throw new YourException();
}
// I've subsequently left out the generating code,
// which is not necesarry in this case
$key = 'xxxx';
if ($this->user_model->valid_key($key) == true)
{
return $key;
}
else
{
return $this->key_generator(4, ($limit-1));
}
}
It is however also possible to do your code iteratively...
然而,迭代地执行代码也是可能的......
#4
2
If you include enough uniqueness in your key generation routine, you might be able to avoid this situation in the first place. E.g. have the routine take into account the current timestamp and the local hostname and/or PID.
如果您在密钥生成例程中包含足够的唯一性,那么您可能首先可以避免这种情况。例如。让例程考虑当前时间戳和本地主机名和/或PID。
Looping in such a non-deterministic fashion is generally proof of some part being too naive. That's not good. :-)
以这种不确定的方式循环通常证明某些部分过于幼稚。这不好。 :-)
Anyhow, it would at least be good practice to catch it and log some sort of error as opposed to hanging the request and finally timing out:
无论如何,捕获它并记录某种错误至少是一种好的做法,而不是挂起请求并最终超时:
function key_generator($length = 4)
{
/* The $attempts_left clearly depends on how much trust
you give your key generation code combined with the key space size. */
$attempts_left = pow(16, $length) * 2;
/* ... just guessing, in case your key base is 16, i.e. [0-9a-z] for example */
do {
// ... key generation goes here ...
$key = 'xxxx';
} while ( $this->user_model->valid_key($key) == false && $attempts_left-- > 0 );
if( $attempts_left < 1 )
return false;
else
return $key;
}
#5
1
Why don't you just scan the key value space for the first unused key? Needs the key to fulfill additional constraints on top of being four characters long and unique?
为什么不直接扫描第一个未使用密钥的密钥值空间?需要密钥才能在四个字符长且独特的基础上实现额外的约束?
You could remember the last returned key to resume scanning from there on subsequent calls.
您可以记住最后返回的密钥,以便在后续呼叫中从那里恢复扫描。
If you want subsequent calls not to return similar keys, you could shuffle your key database first. This would mean that you need to hold a 456976, 1679616, 7311616, or 14776336 element array somewhere (depending on whether the alphabet used are single- or double-cased characters, with or without digits).
如果您希望后续调用不返回类似的键,则可以先将密钥数据库洗牌。这意味着您需要在某处保留456976,167916,7311616或14776336元素数组(取决于使用的字母是单字符还是双字符字符,有或没有数字)。
#6
1
You could put your code into a loop and determine the key iteratively instead of recursively.
您可以将代码放入循环中并迭代地而不是递归地确定密钥。
Example:
例:
function key_generator($length = 4)
{
do {
$key = 'xxxx'; //TODO
if (timeOutReached()) return InvalidKey;
} while (!$this->user_model->valid_key($key))
return $key;
}
The loop itself does not prevent an infinte loop, but unlike a function call, this doesn't eat up stack space, so you don't risk a stack overflow.
循环本身不会阻止infinte循环,但与函数调用不同,这不会占用堆栈空间,因此您不会冒着堆栈溢出的风险。
Also it simplifies things a little bit. Depending on the type of the key you can also adapt the key generation method, for example with numbered keys you can increase exponentially with each iteration.
它也简化了一些事情。根据键的类型,您还可以调整键生成方法,例如使用数字键,您可以在每次迭代时以指数方式增加。
Remarks: If it is possible, use a database's auto-increment feature instead of rolling your own key generation feature.
备注:如果可能,请使用数据库的自动增量功能,而不是滚动自己的密钥生成功能。
Also make sure you protect your code against concurrent access. What if two instances of this function try to generate a key and they both determine the same? Use critical sections or transactions to make sure nothing bad happens.
还要确保保护代码免受并发访问。如果此函数的两个实例尝试生成密钥并且它们都确定相同,该怎么办?使用关键部分或事务来确保不会发生任何不良事件。
#7
0
Using a function inside itself
在自身内部使用一个函数
function test($val) {
/*initialize return value by using the conditions*/
if($val>=5){
/*do something with return statement*/
return $val+10;
} else {
/*set the return default value for avoid the error throwing*/
return "default value";
}
/*return the function used for check the condition*/
return test($val);
}
echo test(4); // output "default value";
echo test(6); //output 16
#1
25
I would not use recursive functions for retry-scenarios (since you don't reuse the result of the function, it's pointless to use recursion)... It adds a lot of unnecessary overhead. Do something like this:
我不会将recursive函数用于重试场景(因为你没有重用函数的结果,使用递归是没有意义的)......它增加了很多不必要的开销。做这样的事情:
do {
$key = ...; // Generate your key here...
} while (!$this->user_model->valid_key($key));
return $key;
If you're near the maximum number of keys, this will result in very long loop times, so you might want to put some kind of max limit.
如果您接近最大键数,这将导致非常长的循环时间,因此您可能需要设置某种最大限制。
Oh, and if this is occurring on multiple threads simultaneously and you're checking a database, you should implement table write locking so that the same key can't be inserted twice. Preferably the function that checks whether a key is available should lock, check, and if available write in the same transaction to avoid any collisions.
哦,如果同时在多个线程上发生并且您正在检查数据库,则应该实现表写锁定,以便不能插入相同的键两次。优选地,检查密钥是否可用的功能应该锁定,检查并且如果可用则写入相同的事务以避免任何冲突。
#2
5
You need to return the result of the self-call, otherwise the valid key won't get returned once it recurses.
您需要返回自我调用的结果,否则一旦递归,将无法返回有效密钥。
return $this->key_generator($length);
#3
2
but this causes the function to do an infinite loop,
但这导致函数执行无限循环,
If you absolutely want to keep your recursive strategy you have to define an end case. For example you may define a counter, like this:
如果您绝对想要保持递归策略,则必须定义最终案例。例如,您可以定义一个计数器,如下所示:
function key_generator($length = 4, $limit=5)
{
if($limit === 0) {
throw new YourException();
}
// I've subsequently left out the generating code,
// which is not necesarry in this case
$key = 'xxxx';
if ($this->user_model->valid_key($key) == true)
{
return $key;
}
else
{
return $this->key_generator(4, ($limit-1));
}
}
It is however also possible to do your code iteratively...
然而,迭代地执行代码也是可能的......
#4
2
If you include enough uniqueness in your key generation routine, you might be able to avoid this situation in the first place. E.g. have the routine take into account the current timestamp and the local hostname and/or PID.
如果您在密钥生成例程中包含足够的唯一性,那么您可能首先可以避免这种情况。例如。让例程考虑当前时间戳和本地主机名和/或PID。
Looping in such a non-deterministic fashion is generally proof of some part being too naive. That's not good. :-)
以这种不确定的方式循环通常证明某些部分过于幼稚。这不好。 :-)
Anyhow, it would at least be good practice to catch it and log some sort of error as opposed to hanging the request and finally timing out:
无论如何,捕获它并记录某种错误至少是一种好的做法,而不是挂起请求并最终超时:
function key_generator($length = 4)
{
/* The $attempts_left clearly depends on how much trust
you give your key generation code combined with the key space size. */
$attempts_left = pow(16, $length) * 2;
/* ... just guessing, in case your key base is 16, i.e. [0-9a-z] for example */
do {
// ... key generation goes here ...
$key = 'xxxx';
} while ( $this->user_model->valid_key($key) == false && $attempts_left-- > 0 );
if( $attempts_left < 1 )
return false;
else
return $key;
}
#5
1
Why don't you just scan the key value space for the first unused key? Needs the key to fulfill additional constraints on top of being four characters long and unique?
为什么不直接扫描第一个未使用密钥的密钥值空间?需要密钥才能在四个字符长且独特的基础上实现额外的约束?
You could remember the last returned key to resume scanning from there on subsequent calls.
您可以记住最后返回的密钥,以便在后续呼叫中从那里恢复扫描。
If you want subsequent calls not to return similar keys, you could shuffle your key database first. This would mean that you need to hold a 456976, 1679616, 7311616, or 14776336 element array somewhere (depending on whether the alphabet used are single- or double-cased characters, with or without digits).
如果您希望后续调用不返回类似的键,则可以先将密钥数据库洗牌。这意味着您需要在某处保留456976,167916,7311616或14776336元素数组(取决于使用的字母是单字符还是双字符字符,有或没有数字)。
#6
1
You could put your code into a loop and determine the key iteratively instead of recursively.
您可以将代码放入循环中并迭代地而不是递归地确定密钥。
Example:
例:
function key_generator($length = 4)
{
do {
$key = 'xxxx'; //TODO
if (timeOutReached()) return InvalidKey;
} while (!$this->user_model->valid_key($key))
return $key;
}
The loop itself does not prevent an infinte loop, but unlike a function call, this doesn't eat up stack space, so you don't risk a stack overflow.
循环本身不会阻止infinte循环,但与函数调用不同,这不会占用堆栈空间,因此您不会冒着堆栈溢出的风险。
Also it simplifies things a little bit. Depending on the type of the key you can also adapt the key generation method, for example with numbered keys you can increase exponentially with each iteration.
它也简化了一些事情。根据键的类型,您还可以调整键生成方法,例如使用数字键,您可以在每次迭代时以指数方式增加。
Remarks: If it is possible, use a database's auto-increment feature instead of rolling your own key generation feature.
备注:如果可能,请使用数据库的自动增量功能,而不是滚动自己的密钥生成功能。
Also make sure you protect your code against concurrent access. What if two instances of this function try to generate a key and they both determine the same? Use critical sections or transactions to make sure nothing bad happens.
还要确保保护代码免受并发访问。如果此函数的两个实例尝试生成密钥并且它们都确定相同,该怎么办?使用关键部分或事务来确保不会发生任何不良事件。
#7
0
Using a function inside itself
在自身内部使用一个函数
function test($val) {
/*initialize return value by using the conditions*/
if($val>=5){
/*do something with return statement*/
return $val+10;
} else {
/*set the return default value for avoid the error throwing*/
return "default value";
}
/*return the function used for check the condition*/
return test($val);
}
echo test(4); // output "default value";
echo test(6); //output 16