I need to generate a sequence (or function to get a \"next id\") with an alphanumeric incrementor.
The length of the string must be defineable, and the Characters mu
Would something like base_convert work? Maybe along these lines (untested)
function getNextAlphaNumeric($code) {
$base_ten = base_convert($code,36,10);
return base_convert($base_ten+1,10,36);
}
The idea is that your codes are all really just a base 36 number, so you convert that base 36 number to base 10, add 1 to it, then convert it back to base 36 and return it.
EDIT: Just realized that there may be an arbitrary string length of the code, but this approach might still be doable -- if you capture all the leading zeroes first, then strip them off, do the base 36 -> base 10 conversion, add one, and add back any needed leading zeroes ...
<?php
define('ALPHA_ID_LENGTH', 3);
class AlphaNumericIdIncrementor {
// current id
protected $_id;
/**
* check if id is valid
*
* @param string $id
* @return bool
**/
protected static function _isValidId($id) {
if(strlen($id) > ALPHA_ID_LENGTH) {
return false;
}
if(!is_numeric(base_convert($id, 36, 10))) {
return false;
}
return true;
}
/**
* format $id
* fill with leading zeros and transform to uppercase
*
* @param string $id
* @return string
**/
protected static function _formatId($id) {
// fill with leading zeros
if(strlen($id) < ALPHA_ID_LENGTH) {
$zeros = '';
for($i = 0; $i < ALPHA_ID_LENGTH - strlen($id); $i++) {
$zeros .= '0';
}
$id = strtoupper($zeros . $id);
} else {
$id = strtoupper($id);
}
return $id;
}
/**
* construct
* set start id or null, if start with zero
*
* @param string $startId
* @return void
* @throws Exception
**/
public function __construct($startId = null) {
if(!is_null($startId)) {
if(self::_isValidId($startId)) {
$this->_id = $startId;
} else {
throw new Exception('invalid id');
}
} else {
$this->_generateId();
}
}
/**
* generate start id if start id is empty
*
* @return void
**/
protected function _generateId() {
$this->_id = self::_formatId(base_convert(0, 10, 36));
}
/**
* return the current id
*
* @return string
**/
public function getId() {
return $this->_id;
}
/**
* get next free id and increment $this->_id
*
* @return string
**/
public function getNextId() {
$this->_id = self::_formatId(base_convert(base_convert($this->_id, 36, 10) + 1, 10, 36));
return $this->_id;
}
}
$testId = new AlphaNumericIdIncrementor();
echo($testId->getId() . '<br />'); // 000
echo($testId->getNextId() . '<br />'); // 001
$testId2 = new AlphaNumericIdIncrementor('A03');
echo($testId2->getId() . '<br />'); // A03
echo($testId2->getNextId() . '<br />'); // A04
$testId3 = new AlphaNumericIdIncrementor('ABZ');
echo($testId3->getId() . '<br />'); // ABZ
echo($testId3->getNextId() . '<br />'); // AC0
?>
I was interested in the more general solution to this problem - i.e. dealing with arbitrary character sets in arbitrary orders. I found it easiest to first translate to alphabet indexes and back again.
function getNextAlphaNumeric($code, $alphabet) {
// convert to indexes
$n = strlen($code);
$trans = array();
for ($i = 0; $i < $n; $i++) {
$trans[$i] = array_search($code[$i], $alphabet);
}
// add 1 to rightmost pos
$trans[$n - 1]++;
// carry from right to left
$alphasize = count($alphabet);
for ($i = $n - 1; $i >= 0; $i--) {
if ($trans[$i] >= $alphasize) {
$trans[$i] = 0;
if ($i > 0) {
$trans[$i -1]++;
} else {
// overflow
}
}
}
// convert back
$out = str_repeat(' ', $n);
for ($i = 0; $i < $n; $i++) {
$out[$i] = $alphabet[$trans[$i]];
}
return $out;
}
$alphabet = array();
for ($i = ord('0'); $i <= ord('9'); $i++) {
$alphabet[] = chr($i);
}
for ($i = ord('A'); $i <= ord('Z'); $i++) {
$alphabet[] = chr($i);
}
echo getNextAlphaNumeric('009', $alphabet) . "\n";
echo getNextAlphaNumeric('00Z', $alphabet) . "\n";
echo getNextAlphaNumeric('0ZZ', $alphabet) . "\n";
function formatPackageNumber($input)
{
//$input = $_GET['number'];
$alpha_array = array("A", "B" , "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z");
$number_array = array("0", "1" , "2", "3", "4", "5", "6", "7", "8", "9");
$output = "";
for($i=0; $i<=5; $i++){
if($i>=4) {
$divisor = pow(26,$i-3)*pow(10,3);
} else {
$divisor = pow(10,$i);
}
$pos = floor($input/$divisor);
if($i>=3) {
$digit = $pos%26;
$output .= $alpha_array[$digit];
} else {
$digit = $pos%10 ;
$output .= $number_array[$digit];
}
}
return strrev($output);
}
I would do something like this:
getNextChar($character) {
if ($character == '9') {
return 'A';
}
else if ($character == 'Z') {
return '0';
}
else {
return chr( ord($character) + 1);
}
}
getNextCode($code) {
// reverse, make into array
$codeRevArr = str_split(strrev($code));
foreach($codeRevArr as &$character) {
$character = getNextChar($character);
// keep going down the line if we're moving from 'Z' to '0'
if ($character != '0') {
break;
}
}
// array to string, then reverse again
$newCode = strrev(implode('', $codeRevArr));
return $newCode;
}