1
\$\begingroup\$

I wrote a function to get a 2-digit random number for each day, but it should be retrievable.

For example, today's random number is 72, but later I can provide "day and month" and get this 72 again.

function ranDay($day,$month){

    $pow1 = round(pow($day,$day))*$month;
    $pow2 = round(pow($day,$day))+$month;
        
    $dig1 = intval(substr($pow1,0,1));
    $dig2 = intval(substr($pow2,-1));
        
    echo $dig1.$dig2;    
}

Now if I provide 27 and 1, I get 48 and I can get this number again later.

Am I doing this right?

The only issue I see in my code is that I can't get 0 for the first digit, and it would be nice to cover that too.

Please review this and tell me if I should consider doing something else.

EDIT:

Based on the accepted answer in ended up using this code instead:

function hashDay($day, $month) {
    echo substr(hexdec(substr(hash('sha256',($month.$day)),0,15)),-2);
}

reason i used 2 substr is amazingly when i used -2 instead of 0,15 in first substr, i got 1-3 digits instead of always 2 digits...

\$\endgroup\$

1 Answer 1

3
\$\begingroup\$

I wrote a function to get a 2-digit random number for each day, but it should be retrievable.

For example, today's random number is 72, but later I can provide "day and month" and get this 72 again.

This isn't random. This is a hashing function. For a given input, it always returns the same output. The normal name for something like this is a hashing function.

You don't explain why you want to do this. If you ask again, try to include that context. We may suggest alternatives.

function ranDay($day,$month){

If this were a random function, I'd write out the name: randomDay. If you really must abbreviate, randDay would be better. However, as I noted earlier, this isn't random. You are just mapping one input to another, so a name like hashDay would be better. There may be an even better name.

function hashDay($day, $month) {
    if ( ! is_numeric($day) || ! is_numeric($month) || 0 > $day || 0 > $month || 12 < $month || 31 < $day ) {
        return;
    }

I also added additional whitespace for readability.

Since this is only supposed to work for valid day and month values, I added some basic checks. Even with these, it will still allow dates like the 31st of February, but this restricts the range somewhat. I just return an undefined value implicitly, but you could explicitly return null (which would do the same thing) or do something like throwing an exception. Note that this also handles if the month or day is given as a string (e.g. January) rather than a number.

Handling zero values with different returns than a value of one is possible:

    $day++;

With that, a zero and a one will return different values.

$dig1 = intval(substr($pow1,0,1));
$dig2 = intval(substr($pow2,-1));

I'd write out $digit1 and $digit2.

Also, you don't need the intval. You immediately use string concatenation on it, which will throw it back into strings. So the only thing that the intval can do is hide bad input from you.

echo $dig1.$dig2;

Rather than echoing the result, it would probably be better to return it. That way you can display it or check for invalid values or whatever. This can also be seen as a violation of the Single Responsibility Principle: you are calculating the hash value; don't display it as well. If you really want to display it, consider changing the name to something like printDayHash.

return $digit1 . $digit2;

Your function will have collisions. There are only 100 possible results, but there are at least 366 valid day/month combinations. Running it on all 416 allowed day/month combinations (including zero values and weird dates like the 31st of February) shows that some results are more likely than others. For example, 91 and 92 (and eight others) are never returned while 16 is returned a whopping twenty times. For most uses, it would be better for this to be more evenly distributed.

My test code:

$count_of = array();
for ( $i = 0; $i < 10; $i++ ) {
    for ( $j = 0; $j < 10; $j++ ) {
        $count_of[$i.$j] = 0;
    }
}

for ( $m = 0; $m <= 12; $m++ ) {
    for ( $d = 0; $d <= 31; $d++ ) {
         $count_of[hashDay($d, $m)]++;
    }
}

asort($count_of);
print_r($count_of);

Note how returning the value rather than displaying it makes this test much easier.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you very much, your explanation guide me to this code instead: function hashDay($day, $month) {$hash=hexdec(substr(sha1($month.$day),0,15));} \$\endgroup\$
    – Ara
    Commented Jan 28, 2015 at 18:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.