如何创建随机 GUID?

如何创建随机 GUID?

GUID(或G全球独特ID实体) 通常存储为 128 位值,并且通常显示为 32 个十六进制数字,各组之间用连字符分隔,例如:

21EC2020-3AEA-4069-A2DD-08002B30309D

在线资源可用于创建伪随机 GUID,但我想在编译时创建它们。理想情况下,我对一个\GUID可以生成 GUID(我可以将其存储在宏中)的宏感兴趣,也许使用计算机时间来初始化伪随机数生成器。如果可能的话,也许一个可选参数可以允许提供一个应该返回固定 GUID 的种子。

我特别感兴趣的是版本 4 GUID其格式如下:

Hex digits  Description
8           Data1
4           Data2
4           Data3
4           Initial two bytes from Data4
12          Remaining six bytes from Data4

“版本 4 GUID 只是使用伪随机数来填充除六位之外的所有位。它们4在 4 位版本位置上有一个,并且的前两位data410(因此的第一个十六进制数字data48、、9或) AB例如38A52BE4-9352-453E-AF97-5C3B448652F0。更具体地说,data3位模式将是 [...] 0100xxxxxxxxxxxx [...]。”

答案1

random由 D. Arsenau合作。

\documentclass{article}
\usepackage{xparse}
\input{random}

\ExplSyntaxOn
\cs_set_eq:NN \guid_set_random_number:Nnn \setrannum
\cs_set_eq:NN \g_guid_seed_int \randomi

\tl_new:N \l__guid_four_bytes_tl
\int_new:N \l__guid_random_int

\cs_new_protected:Nn \__guid_generate:n
 {
  \guid_set_random_number:Nnn \l__guid_random_int { 0 } { #1 }
  \int_case:nn { #1 }
   {
    { 4095 }{ \int_add:Nn \l__guid_random_int { "4000 } }
    { 16383 }{ \int_add:Nn \l__guid_random_int { 32768 } }
   }
  \tl_set:Nx \l__guid_four_bytes_tl { \int_to_Hex:n { \l__guid_random_int } }
  \tl_set:Nx \l__guid_four_bytes_tl
   {
    \prg_replicate:nn { 4 - \tl_count:N \l__guid_four_bytes_tl } { 0 }
    \tl_use:N \l__guid_four_bytes_tl
   }
 }

\NewDocumentCommand{\GUIDgenerate}{om}
 {
  \tl_new:c { g_guid_user_#2_tl }
  \group_begin:
  \IfValueT { #1 } { \int_gset:Nn \g_guid_seed_int { #1 } } % the seed
  % Data 1 (eight bytes)
  \__guid_generate:n { 65535 }
  \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  \__guid_generate:n { 65535 }
  \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % hyphen
  \tl_gput_right:cx { g_guid_user_#2_tl } { - }
  % Data 2 (four bytes)
  \__guid_generate:n { 65535 }
  \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % hyphen
  \tl_gput_right:cx { g_guid_user_#2_tl } { - }
  % Data 3 (four bytes)
  \__guid_generate:n { 4095 }
  \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % hyphen
  \tl_gput_right:cx { g_guid_user_#2_tl } { - }
  % Data 4a (three bytes)
  \__guid_generate:n { 16383 }
  \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % hyphen
  \tl_gput_right:cx { g_guid_user_#2_tl } { - }
  % Data 4b (twelve bytes)
  \__guid_generate:n { 65535 }
  \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  \__guid_generate:n { 65535 }
  \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  \__guid_generate:n { 65535 }
  \tl_gput_right:cx { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  \group_end:
 }

\DeclareExpandableDocumentCommand{\GUID}{m}
 {
  \tl_use:c { g_guid_user_#1_tl }
 }
\ExplSyntaxOff

\GUIDgenerate{fooA}
\GUIDgenerate{fooB}
\GUIDgenerate{fooC}

\begin{document}

\ttfamily
\GUID{fooA}\par
\GUID{fooB}\par
\GUID{fooC}\par

\GUIDgenerate[2]{fooD}
\GUIDgenerate[3]{fooE}
\GUIDgenerate[42]{fooF}

\GUID{fooD}\par
\GUID{fooE}\par
\GUID{fooF}\par

\end{document}

如果您多次编译它,您将看到前三个 GUID 会改变,而后三个 GUID 不会改变,因为它们是用固定种子定义的。

在此处输入图片描述

2024 年更新

现在所有引擎都有一个伪随机数生成器。

\documentclass{article}

\ExplSyntaxOn
% keep the initial (random) seed
\int_const:Nn \c_guid_seed_int { \sys_rand_seed: }

\tl_new:N \l__guid_four_bytes_tl
\int_new:N \l__guid_random_int

\cs_new_protected:Nn \__guid_generate:n
 {
  \int_set:Nn \l__guid_random_int { \int_rand:nn { 0 } { #1 } }
  \int_case:nn { #1 }
   {
    { 4095 }{ \int_add:Nn \l__guid_random_int { "4000 } }
    { 16383 }{ \int_add:Nn \l__guid_random_int { 32768 } }
   }
  \tl_set:Ne \l__guid_four_bytes_tl { \int_to_Hex:n { \l__guid_random_int } }
  \tl_put_left:Ne \l__guid_four_bytes_tl
   {
    \prg_replicate:nn { 4 - \tl_count:N \l__guid_four_bytes_tl } { 0 }
   }
 }

\NewDocumentCommand{\GUIDgenerate}{om}
 {
  \tl_new:c { g_guid_user_#2_tl }
  % if with optional argument, use it a seed
  \IfValueT { #1 } { \sys_gset_rand_seed:n { #1 } } % the seed
  % Data 1 (eight bytes)
  \__guid_generate:n { 65535 }
  \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  \__guid_generate:n { 65535 }
  \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % hyphen
  \tl_gput_right:ce { g_guid_user_#2_tl } { - }
  % Data 2 (four bytes)
  \__guid_generate:n { 65535 }
  \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % hyphen
  \tl_gput_right:ce { g_guid_user_#2_tl } { - }
  % Data 3 (four bytes)
  \__guid_generate:n { 4095 }
  \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % hyphen
  \tl_gput_right:ce { g_guid_user_#2_tl } { - }
  % Data 4a (three bytes)
  \__guid_generate:n { 16383 }
  \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % hyphen
  \tl_gput_right:ce { g_guid_user_#2_tl } { - }
  % Data 4b (twelve bytes)
  \__guid_generate:n { 65535 }
  \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  \__guid_generate:n { 65535 }
  \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  \__guid_generate:n { 65535 }
  \tl_gput_right:ce { g_guid_user_#2_tl } { \l__guid_four_bytes_tl }
  % reset the seed to the initial (random) value
  \IfValueT { #1 } { \sys_gset_rand_seed:n { \c_guid_seed_int } } % the seed
 }

\NewExpandableDocumentCommand{\GUID}{m}
 {
  \tl_use:c { g_guid_user_#1_tl }
 }
\ExplSyntaxOff

\GUIDgenerate{fooA}
\GUIDgenerate{fooB}
\GUIDgenerate{fooC}

\begin{document}

\ttfamily
\GUID{fooA}\par
\GUID{fooB}\par
\GUID{fooC}\par

\GUIDgenerate[2]{fooD}
\GUIDgenerate[3]{fooE}
\GUIDgenerate[42]{fooF}

\GUID{fooD}\par
\GUID{fooE}\par
\GUID{fooF}\par

\end{document}

答案2

使用 PDFTeX 和包中的随机生成器xintbinhex进行十六进制转换。

编辑:我添加了一个更简单的无包装版本。仍在使用pdfuniformdeviate

我忘了说大写字母是用 catcode 字母生成的,它们是常见的字母。(如果有关系的话……)

编辑2:不知为何我误读了说明,结果只产生了 28 位十六进制数字。已修复。

\documentclass{article}

\usepackage{xintbinhex}

\makeatletter
% there is a complication as we need to avoid stripping leading zeros...
% ... but I am going to use \xintDecToHex which does that.
%
\begingroup\catcode0 12
\gdef\@expand@and@gob {\expandafter\@gobble\romannumeral`^^@}
\endgroup

% this one is expandable
\newcommand*{\GUID}
{%
% Data 1: (8 Hex Digits)
% \pdfuniformdeviate will refuse higher than "7FFFFFFF
% \xintDecToHex wants tokens, hence \the\numexpr needed.
% generate five hex digits and gobble the first one (which will be 1)
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "10000+\pdfuniformdeviate "FFFF\relax}% 
% again for a total of 8 Hex Digits
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "10000+\pdfuniformdeviate "FFFF\relax}-% 
% Data 2: (4 Hex Digits)
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "10000+\pdfuniformdeviate "FFFF\relax}-%
% Data 3: (4 Hex Digits, the first one a 4)
 \xintDecToHex {\the\numexpr "4000+\pdfuniformdeviate "FFF\relax}-%
% Data 4: (16=4+12 Hex Digits, the first one 8, 9 , A or B)
 \xintDecToHex {\the\numexpr  "8000+\pdfuniformdeviate "4000\relax}-%
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "1000000+\pdfuniformdeviate "FFFFFF\relax}%
 \@expand@and@gob
 \xintDecToHex {\the\numexpr "1000000+\pdfuniformdeviate "FFFFFF\relax}%
 }   
 % side-note: if we used directly \pdfuniformdeviate in \xintDecToHex,
 % we could do it this way 
 % \xintDecToHex {\pdfuniformdeviate "FFFF }% <-- space needed
 % we don't do this above as \xintDecToHex trims leading zeros
\makeatother

% this one is not expandable
\newcommand*{\GUIDnx}[1][.]{\begingroup\ifx.#1\else
                          \pdfsetrandomseed #1\relax\fi \GUID\endgroup}

% this one is not expandable
\newcommand*{\GUIDset}[2][.]{\begingroup\ifx.#1\else
                          \pdfsetrandomseed #1\relax\fi \xdef#2{\GUID}\endgroup}

\begin{document}

\ttfamily

\GUID\par
\GUID\par
\GUID\par
\edef\foo{\GUID}\meaning\foo<-- end of macro\par
\edef\foo{\GUID}\meaning\foo<-- end of macro\par


\GUIDnx [0]\par

\GUIDset[0]\foo

\meaning\foo<-- end of macro\par

\GUIDnx [123456789]\par

\GUIDset[123456789]\foo

\meaning\foo<-- end of macro\par

\end{document}

%No package version

\documentclass{article}


\def\GUIDonedigit {\ifcase\pdfuniformdeviate 16\space\space
                    0\or 1\or 2\or 3%
                \or 4\or 5\or 6\or 7%
                \or 8\or 9\or A\or B%
                \or C\or D\or E\else F\fi}

\def\GUIDonespecialdigit {\ifcase\pdfuniformdeviate 4\space\space
                8\or 9\or A\else B\fi }

% this one is expandable


\newcommand*{\GUID}
{%
% Data 1: (8 Hex Digits)
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit-%
% Data 2: (4 Hex Digits)
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit-%
% Data 3 : (4 Hex Digits, the first one a 4)
4\GUIDonedigit\GUIDonedigit\GUIDonedigit-%
% Data 4: (16=4+12 Hex Digits, the first one 8, 9 , A or B)
\GUIDonespecialdigit\GUIDonedigit\GUIDonedigit\GUIDonedigit-%
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit
\GUIDonedigit\GUIDonedigit\GUIDonedigit\GUIDonedigit
 }


\makeatother

% this one is not expandable
\newcommand*{\GUIDnx}[1][.]{\begingroup\ifx.#1\else
                          \pdfsetrandomseed #1\relax\fi \GUID\endgroup}

% this one is not expandable
\newcommand*{\GUIDset}[2][.]{\begingroup\ifx.#1\else
                          \pdfsetrandomseed #1\relax\fi \xdef#2{\GUID}\endgroup}

\begin{document}

\ttfamily

\GUID\par
\GUID\par
\GUID\par
\edef\foo{\GUID}\meaning\foo<-- end of macro\par
\edef\foo{\GUID}\meaning\foo<-- end of macro\par


\GUIDnx [0]\par

\GUIDset[0]\foo

\meaning\foo<-- end of macro\par

\GUIDnx [123456789]\par

\GUIDset[123456789]\foo

\meaning\foo<-- end of macro\par

\end{document}

在此处输入图片描述

答案3

这可能无法在所有 tex 引擎中发挥作用。

在此处输入图片描述

{\catcode`\%=12
\gdef\GUID{\directlua{
tex.print(string.format("%08x-%04x-%04x-%04x-%012x",
math.random(0xffffffff),
math.random(0xffff),
0x4000+math.random(0xfff),
0x8000+math.random(0x3fff),
math.random(0xffffffffffff)
))
}}}

\tt

\edef\foo{\GUID}\foo

\GUID


\bye

答案4

还有更多!这个使用了 md5sum,所以从技术上讲,它在 uuid v4 意义上并不是“随机的”,但没有人会注意到。

此代码提供了一个特定的 documentID,除非您执行某些操作来更改它,否则该 documentID 不会改变。instanceID 基于时间,因此始终会改变。

警告:如果您不愿意根据需要更改 documentID 生成器中的“某些内容”,那么请不要使用它。

\documentclass{article}
% My own usage already has these packages loaded for other reasons,
% so I use them here. No doubt the code can be re-written so that
% the mdfivesum and string manipulations can be done directly.
\RequirePackage{pdftexcmds}
\RequirePackage{xstring}
% Farther down, \tempstr will be defined. It begins as an mdfivesum with
% uppercase hex, no hyphens, and no particular hexcodes in two locations
% where uuid version 4 expects particular hexcodes. This tweak puts the
% letters in lowercase (via egreg code elsewhere), then adds the hyphens and
% specific hexcodes:
\def\tweaktempstr{
  \lowercase\expandafter{% Per 'egreg' tex.stackexchange.com q.351065.
    \expandafter\def\expandafter\tempstr\expandafter{\tempstr}%
  }
  \StrLeft{\tempstr}{8}[\tempn]
  \StrRight{\tempstr}{24}[\tempd]
  \edef\tempstr{\tempn-\tempd}
  \StrLeft{\tempstr}{13}[\tempn]
  \StrRight{\tempstr}{19}[\tempd] % Omit character, becomes 4.
  \edef\tempstr{\tempn-4\tempd}
  \StrLeft{\tempstr}{18}[\tempn]
  \StrRight{\tempstr}{15}[\tempd] % Omit character, becomes 8.
  \edef\tempstr{\tempn-8\tempd}
  \StrLeft{\tempstr}{23}[\tempn]
  \StrRight{\tempstr}{12}[\tempd]
  \edef\tempstr{\tempn-\tempd}
}
% Change "somthing" so that documents with same jobname are distinguished:
\makeatletter
\edef\tempstr{\pdf@mdfivesum{\jobname something}}
\tweaktempstr
\edef\documentID{uuid:\tempstr}
\ifdefined\pdffeedback % lualatex
  \edef\tempstr{\pdf@mdfivesum{\pdffeedback creationdate}}
\else % pdflatex
  \edef\tempstr{\pdf@mdfivesum{\pdfcreationdate}}
\fi
\tweaktempstr
\edef\instanceID{uuid:\tempstr}
\makeatother
%%
\begin{document}
\ttfamily % Only so that strings are easier to see:
documentID = \documentID\par
instanceID = \instanceID\par
\end{document}

结果截图

相关内容