*! version 1 -- developed by RT 10/16/2014

capture program drop randomid
program define randomid
  version 13
  syntax name(id="newvar name"), [LENgth(integer 8)]


qui {
preserve //<--preserve in case the algorithm fails, we automatically restore the original dataset

gen ___origsort = _n

if 62^`length'<_N {
  if _N>9999 noi di as error "error: impossible to create unique ID of length `length' with " _N " observations; please select a length of at least " `=length(string(_N))+2'
  else       noi di as error "error: impossible to create unique ID of length `length' with " _N " observations; please select a length of at least " `=ceil((log(_N)/log(62)))+2'
  exit 121
}

/* NOTE: 26 lower +26 upper +10 numbers =62.
         the next greater integer of log base 62 of _N is the minimum length needed to identify every observation with 62 characters.
         Mathematically: log(_N)/log(62)
         However, by choosing the minimum integer needed, you run the highly probable risk of randomly selecting duplicate ids,
         making the command enter a very lengthy --or even infinite!-- loop.
         I, thus, recommend choosing a length of length(string(_N)) +2.
*/

if `length'<`=length(string(_N))+2' & _N>9999 {
  noi di as error "error: high risk of entering a very lengthy loop; please select a length of at least " `=length(string(_N))+2'
  exit 121
}

if `length'<`=ceil((log(_N)/log(62)))+2' & _N<10000 {
  noi di as error "error: high risk of entering a very lengthy loop; please select a length of at least " `=ceil((log(_N)/log(62)))+2'
  exit 121
}

*** sort randomly
gen __u1 =runiform()
gen __u2 =runiform()
local n=2
capt isid __u?
while _rc!=0 {
  local n = `n' +1
  gen __u`n' =runiform()
  capt isid __u?
}
sort __u?

*** make the `name' string
gen `namelist'=""

*** create random id
local distinct=1
local total=2

while `distinct'!=`total' {

  while length(`namelist')!=`length' {

    *** generate random integers from [65,90] which maps to uppercase [See help function char]
    gen __upper= char(65+int((90-65+1)*runiform()))

    *** generate random integers from [97,122] which maps to lowercase [See help function char]
    gen __lower= char(97+int((122-97+1)*runiform()))

    *** generate random integers [0,9]
    gen __integer09= string(0+int((9-0+1)*runiform()))

    *** concatenate random characters
    replace `namelist' = `namelist' + `:word `=1+int((3-1+1)*runiform())' of __upper __lower __integer09'
    drop __upper __lower __integer09
  }

    *** make sure the id is unique
  qui distinct `namelist'
  local distinct =r(ndistinct)
  local total =r(N)
  if `distinct'!=`total' {
       local length =`length'+1
       noi di "character length is now `length' to ensure `namelist' is unique"
       }
}

*** finalizing
compress `namelist'
drop __u?
restore, not
sort ___origsort
drop ___origsort


} // end qui
end