2

Hello , today I was developing a short url system and I created this class to help me encode / decode integer (id) to small string. That way for example “Ac” means 3674 . Because I believe that it may be helpful to others too I share it here. Notice that in the constructing string each char can be present only once.

Example of usage:

    $numCode = new Model_Utils_Numcode("4fFiV8kRTvm5MPNDcyO1dg7lr20Qtn3X6pKLZUqaEsxCwubGYIzhSWJojHeA9B");      
    $num = 3674;
    $encoded = $numCode->encode($num);

    echo "<br/> $num encoded = $encoded";
    // 3674 encoded = Ac

    echo "<br/> $encoded decoded = ".$numCode->decode($encoded);
    // Ac decoded = 3674

Class:

class Model_Utils_Numcode
{
  private $nums;
  private $chars;
  private $numeral;

  /**
   * @param $string , String containing the chars that will be used for
   * the convension. !important each char can be present only once.
   */
  public function __construct($string)
  {
    $this->nums = str_split($string);
    $this->chars = array_flip($this->nums);
    if(count($this->nums) != count($this->chars))
    {
      throw new Exception("$string !important each char can be present only once.", 371);
    }
    $this->numeral = count($this->nums);
  }

  /**
   * Encodes a number to a string
   * @param int $int
   * @return string
   */
  public function encode($int)
  {
    if(!is_int($int))
    {
      throw new Exception("$int is not integer.", 372);
    }
    return $this->convension($int, $this->numeral);
  }

  /**
   * Decodes a string to a number
   * @param string $string
   * @return number
   */
  public function decode($string)
  {
    $num = 0;
    $m = 1;
    $parts = str_split($string);
    $parts = array_reverse($parts);
    foreach($parts as $part)
    {
      if(!isset($this->chars[$part]))
      {
        throw new Exception("$part is not defined.", 373);
      }
      $num =  $num + $this->chars[$part] * $m;
      $m = $m * $this->numeral;
    }
    return $num;
  }

  /**
   * @see http://www.cut-the-knot.org/recurrence/conversion.shtml
   */
  private function convension($M,$N)
  {
    if($M  < $N)
    {
      return $this->nums[$M];
    }
    else
    {
      return $this->convension($M / $N, $N) . $this->nums[bcmod($M , $N)];
    }
  }

}

Edited by jkon: Code Snippet

Votes + Comments
Thanks for sharing
great!
// Examle of usage:
		$numCode = new Model_Utils_Numcode("4fFiV8kRTvm5MPNDcyO1dg7lr20Qtn3X6pKLZUqaEsxCwubGYIzhSWJojHeA9B");      
        $num = 3674;
        $encoded = $numCode->encode($num);
        
        echo "<br/> $num encoded = $encoded";
        // 3674 encoded = Ac
        
        echo "<br/> $encoded decoded = ".$numCode->decode($encoded);
        // Ac decoded = 3674
		
// Class:
   class Model_Utils_Numcode
    {
      private $nums;
      private $chars;
      private $numeral;
    
      /**
       * @param $string , String containing the chars that will be used for
       * the convension. !important each char can be present only once.
       */
      public function __construct($string)
      {
        $this->nums = str_split($string);
        $this->chars = array_flip($this->nums);
        if(count($this->nums) != count($this->chars))
        {
          throw new Exception("$string !important each char can be present only once.", 371);
        }
        $this->numeral = count($this->nums);
      }
    
      /**
       * Encodes a number to a string
       * @param int $int
       * @return string
       */
      public function encode($int)
      {
        if(!is_int($int))
        {
          throw new Exception("$int is not integer.", 372);
        }
        return $this->convension($int, $this->numeral);
      }
    
      /**
       * Decodes a string to a number
       * @param string $string
       * @return number
       */
      public function decode($string)
      {
        $num = 0;
        $m = 1;
        $parts = str_split($string);
        $parts = array_reverse($parts);
        foreach($parts as $part)
        {
          if(!isset($this->chars[$part]))
          {
            throw new Exception("$part is not defined.", 373);
          }
          $num =  $num + $this->chars[$part] * $m;
          $m = $m * $this->numeral;
        }
        return $num;
      }
    
      /**
       * @see http://www.cut-the-knot.org/recurrence/conversion.shtml
       */
      private function convension($M,$N)
      {
        if($M  < $N)
        {
          return $this->nums[$M];
        }
        else
        {
          return $this->convension($M / $N, $N) . $this->nums[bcmod($M , $N)];
        }
      }
    
    }
3
Contributors
5
Replies
52
Views
2 Years
Discussion Span
Last Post by diafol
1

Thanks for sharing! I tested your class and I like it, consider to add a passphrase to allow different results, it could be helpful.

You may want to check Hashids, it's very similar to your concept: http://hashids.org/

0

Thank you cereal , the demand was to create a url shortening system with no consideration on exposed url security (it is just redirecting) . In that matter I could pass the reference id as it was. But of course if this goes large the url's would look ugly and not 'short'. Creating this helped me in that sense and it has a bit of complexity that the chars used for the numeral system , and the numeral system each-self are defined in the construction argument.

Of course this is not a security system since id's , to more complex things than just redirecting , has no meaning to be shared in a url (internal system entities should not be exposed in any sense). Even in this simple redirection I believe that “Numcode” hides the internal system id and would require a lot of effort for someone to understand it, gaining from that just to redirect to the next page.

1

The project I was working went live this week , making me to realize that is_int don't work with strings that are integers , in the encode method. I am posting once again the class Numcode alternating is_int($int) with preg_match('/^\d+$/', $int)

<?php
class Model_Utils_Numcode
{
  private $nums;
  private $chars;
  private $numeral;

  /**
   * @param $string , String containing the chars that will be used for
   * the conversion. !important each char can be present only once.
   */
  public function __construct($string)
  {
    $this->nums = str_split($string);
    $this->chars = array_flip($this->nums);
    if(count($this->nums) != count($this->chars))
    {
      throw new Exception("$string !important each char can be present only once.", 371);
    }
    $this->numeral = count($this->nums);
  }

  /**
   * Encodes a number to a string
   * @param int $int
   * @return string
   */
  public function encode($int)
  {
    if(!preg_match('/^\d+$/', $int))
    {
      throw new Exception("$int is not integer.", 372);
    }
    return $this->convension($int, $this->numeral);
  }

  /**
   * Decodes a string to a number
   * @param string $string
   * @return number
   */
  public function decode($string)
  {
    $num = 0;
    $m = 1;
    $parts = str_split($string);
    $parts = array_reverse($parts);
    foreach($parts as $part)
    {
      if(!isset($this->chars[$part]))
      {
        throw new Exception("$part is not defined.", 373);
      }
      $num =  $num + $this->chars[$part] * $m;
      $m = $m * $this->numeral;
    }
    return $num;
  }

  /**
   * @see http://www.cut-the-knot.org/recurrence/conversion.shtml
   */
  private function convension($M,$N)
  {
    if($M  < $N)
    {
      return $this->nums[$M];
    }
    else
    {
      return $this->convension($M / $N, $N) . $this->nums[bcmod($M , $N)];
    }
  }

}
?>

Edited by jkon: better preg_match

2

is_int is a problem with strings. I use the validate functions for data from forms.

if($var = filter_var($intString, FILTER_VALIDATE_INT))...

Edited by diafol

Votes + Comments
me too!
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.