This howto shows explains how to add social voting algorithm to your Drupal site using Votingapi. It implements the Reddit algorithm circa Dec 8, 2005, and is done via hook_votingapi_results_alter.

It is based off of this: http://uggedal.com/reddit.cf.algorithm.png

hook_votingapi_results_alter() allows the module to insert its own voting function into the votingapi_cache table.

/**
 * Implementation of hook_votingapi_results_alter().
 */
function my_module_votingapi_results_alter(&$cache, $content_type, $content_id) {
  if ($content_type == 'node') {
    // Reddit algorithm: uggedal.com/reddit.cf.algorithm.png.
    $created = db_result(db_query("SELECT created FROM {node} WHERE nid = %d", $content_id));

    // Go through each of the votingapi results and calculate
    // hotness for each point voting system.
    foreach ($cache as $tag => $types) {
      if (isset($types['points'])) {
        $sum = $types['points']['sum'];
        $coefficent = 1;

        // Determine if we should really bury the node.
        if ($sum < 0) {
          $coefficent = -1;
          // Only can do logs of natural numbers.
          $sum = 1;
        }
        elseif ($sum == 0) {
          $coefficent = 0;
          // Only can do logs of natural numbers.
          $sum = 1;
        }

        // We raise the sum by an odd exponent. Higher the exponent sharper the curve.
        $cache[$tag]['points']['reddit'] = log(pow($sum, variable_get('reddit_exponent', 39)), 10) +  ($coefficent * $created) / variable_get('reddit_age_factor', 45000);
      }
    }
  }
}

The two variable_gets() are important in shaping the curve of the ranking.

  • reddit_exponent - Must be an odd number, and the higher it is the more powerful votes are in the calculation
  • reddit_age_factor - Increase this number to add more power to newer nodes.

The defaults for these variable gets are roughly optimized for a site with about the max number of votes ~ 20. This might seem small at first (I mean you can count that using your hands and feet!) but I have found this to be a good number for a vast majority of sites.

reddit_exponent must be an odd number because for example, raising -3 by 2 = 9 and raising 3 by 2 = 9. Therefore, negative voting sums will have no meaning.

Now it becomes as simple as adding the Node: Vote Results relationship to a View and specifying the Aggregation function as -Other- and typing in 'reddit' into the text field. Like so: http://skitch.com/supermanscott/nxgn4/reddit

While this algorithm might not be exactly the one used on Reddit, it is true in spirit.

Comments

1800collect’s picture

double post

1800collect’s picture

Does this get added in the Voting_API? Somewhere in particular?

We would adjust the variable_gets as the site grows? So the max number of votes isn't ~20? So if the average number of votes is getting in the hundreds we would increase those numbers. I don't have a scientific calculator on hand so I can't see what the numbers would be so I wouldn't know how to alter those in the future. But I'm guessing on a calculator I could figure that out.

From your explanation it seems I would decrease reddit_exponent from 39 down if more votes are happening. Decrease 45000 if too many items are getting to the top to easily.

$cache[$tag]['points']['reddit'] = log(pow($sum, variable_get('reddit_exponent', 39)), 10) + ($coefficent * $created) / variable_get('reddit_age_factor', 45000);

I think I figured out everything else in the code. Brushing up on my math a bit. :)

Thanks for your help, I can't wait to try this out.

So I figured out this needs to be a module. So I dumped this code into a module called "reddit" and did my thing, but when I put "reddit" in the "Aggregation" function nothing shows up.

Here is the exact code in the module

<?php
// $Id$


function reddit_perm() {
  return array('access reddit');
} // function reddit_perm()


/**
* Implementation of hook_votingapi_results_alter().
*/
function reddit_votingapi_results_alter($cache, $content_type, $content_id) {
  if ($content_type == 'node') {
    // Reddit algorithm: uggedal.com/reddit.cf.algorithm.png.
    $created = db_result(db_query("SELECT created FROM {node} WHERE nid = %d", $content_id));

    // Go through each of the votingapi results and calculate
    // hotness for each point voting system.
    foreach ($cache as $tag => $types) {
      if (isset($types['points'])) {
        $sum = $types['points']['sum'];
        $coefficent = 1;

        // Determine if we should really bury the node.
        if ($sum < 0) {
          $coefficent = -1;
          // Only can do logs of natural numbers.
          $sum = 1;
        }
        elseif ($sum == 0) {
          $coefficent = 0;
          // Only can do logs of natural numbers.
          $sum = 1;
        }

        // We raise the sum by an odd exponent. Higher the exponent sharper the curve.
        $cache[$tag]['points']['reddit'] = log(pow($sum, variable_get('reddit_exponent', 39)), 10) +  ($coefficent * $created) / variable_get('reddit_age_factor', 45000);
      }
    }
  }
}

The module is turned on and everything, but no results show.

Edit: Nevermind. I had it working the whole time. Just I hadn't tested a vote since over 24 hours so nothing was showing up. :)

For anyone who stumbles upon this page the above code works as a module. You will need to create a .info file as well.

Also I've heard radioactivity is good, haven't tried it out.

farez’s picture

Shouldn't that be &$cache (passed by ref)?

Farez

Scott Reynolds’s picture

Updated as such thank you.