计算校验和

计算校验和

我想在 LaTeX 中计算校验和。我的问题是我不知道如何获取字符的数值。

以下是该算法的伪代码:

var input = "123456789";
car output = "";
var checksum = 0;
var weight = 10;

foreach(Char c in input) {
    checksum += Char.GetNumericValue(c) * weight;
    weight--;
}

checksum = 11-(checksum mod 11);
if(checksum == 10)
    output += "X";
else if (checksum == 11)
    output += "0";
else
    output += checksum;

print output;

实际上,我尝试使用以下软件包进行此forloop操作xstring

\newcommand{\inputstr}{123456789}
\newcounter{i}
\newcounter{c}
\forloop{i}{1}{\value{i} < 10}{%
    %\StrChar{\inputstr}{\value{i}} % returns one char
    \setcounter{c}{\value{\StrChar{\inputstr}{\value{i}}}} % here is no convertion :(
}

乘法和加法应该也没有问题\multiply\addcounter但是如何进行模运算呢?

答案1

如果您将参数作为字符,即#1= A,则将`#1 为您提供此字符的 ASCII 数字。如果您希望字符“0”为您提供数字值 0,依此类推,则只需从`0每个值中减去该值即可。幸运的是,数字的字符按 ASCII 中的数字顺序编码,即`0-`1 = 1等。

我会自己循环输入文本,将其放在结束标记前面,然后以递归方式一次读取一个字符。

\documentclass{article}
\usepackage{calc}

\newcounter{checksum}
\newcounter{weight}
\makeatletter
\newcommand\checksum[1]{%
    \setcounter{checksum}{0}%
    \setcounter{weight}{10}%
    \expandafter\@checksum#1\@nnil
    \loop\ifnum\value{checksum}>10
        \addtocounter{checksum}{-11}%
    \repeat
    \setcounter{checksum}{11-\value{checksum}}%
    \ifnum\value{checksum}=10
        \def\checksumdigit{X}%
    \else
    \ifnum\value{checksum}=11
        \def\checksumdigit{0}%
    \else
        \edef\checksumdigit{\arabic{checksum}}%
    \fi\fi
    \checksumdigit
}
% Reads the input one token a time, should only contains normal characters!
\def\@checksum#1{%
    \ifx\@nnil#1\relax\else % stop looping when endmarker is read
        \addtocounter{checksum}{\value{weight}*(`#1-`0)}%
        \addtocounter{weight}{-1}%
        \expandafter\@checksum % Recursive call => loop
    \fi
}
\makeatother




\begin{document}

\checksum{383480757}%5

\checksum{055215295}%1

\checksum{020113448}%9

\end{document}

这会将校验和数字存储到\checksumdigit文本中并将其打印出来。我在上面三本书上成功测试了它。

答案2

\documentclass{article}
\usepackage{xstring}
\def\GOODISBN#1{ISBN #1 is valid}
\def\BADISBN#1{ISBN #1 is invalid}

\makeatletter
\def\checkISBN#1{%
  \def\ISBN@arg{#1}%
  \StrDel{#1}{-}[\ISBN@temp]%
  \expandafter\StrLen\expandafter{\ISBN@temp}[\ISBN@length]%
  \ifnum\ISBN@length=10 
    \expandafter\checkISBNold\expandafter{\ISBN@temp}%
  \else
    \ifnum\ISBN@length=13
      \expandafter\checkISBNnew\expandafter{\ISBN@temp}%
    \else
      \BADISBN{\ISBN@arg}
    \fi
  \fi}

\def\checkISBNold#1{%
  \StrGobbleRight{#1}{1}[\ISBN@temp]%
  \StrRight{#1}{1}[\ISBN@check]%
  \@tempcnta=11 \@tempcntb=\z@
  \expandafter\@tfor\expandafter\next 
  \expandafter:\expandafter=\ISBN@temp\do
    {\advance\@tempcnta\m@ne
     \@tempcntb=\numexpr\@tempcntb+\next*\@tempcnta\relax
    }
  \@tempcnta=\@tempcntb
  \divide\@tempcnta by 11
  \multiply\@tempcnta by 11
  \advance\@tempcntb-\@tempcnta
  \@tempcntb=\numexpr11-\@tempcntb\relax
  \ifnum\@tempcntb=11
    \def\ISBN@final{0}%
  \else
    \ifnum\@tempcntb=10
      \def\ISBN@final{X}%
    \else
      \edef\ISBN@final{\number\@tempcntb}%
    \fi
  \fi
  \ifx\ISBN@final\ISBN@check
    \GOODISBN{\ISBN@arg}
  \else
    \BADISBN{\ISBN@arg}
  \fi  
}
\def\checkISBNnew#1{%
  \StrGobbleRight{#1}{1}[\ISBN@temp]%
  \StrRight{#1}{1}[\ISBN@check]%
  \@tempcnta=\z@ \@tempcntb=\z@
  \expandafter\@tfor\expandafter\next
  \expandafter:\expandafter=\ISBN@temp\do
    {\advance\@tempcnta\@ne
     \@tempcntb=\numexpr\@tempcntb+\next*\ifodd\@tempcnta 1\else 3\fi\relax
    }
  \@tempcnta=\@tempcntb
  \divide\@tempcnta by 10
  \multiply\@tempcnta by 10
  \advance\@tempcntb-\@tempcnta
  \@tempcntb=\numexpr10-\@tempcntb\relax
    \ifnum\@tempcntb=10
      \def\ISBN@final{0}%
    \else
      \edef\ISBN@final{\number\@tempcntb}%
   \fi
  \ifx\ISBN@final\ISBN@check
    \GOODISBN{\ISBN@arg}
  \else
    \BADISBN{\ISBN@arg}
  \fi
}

\begin{document}
\checkISBN{1000000011}

\checkISBN{1-00-000001-X}

\checkISBN{0-306-40615-2}

\checkISBN{978-0-306-40615-7}
\end{document}

结果是

ISBN 1000000011 无效
ISBN 1-00-000001-X 有效
ISBN 0-306-40615-2 有效
ISBN 978-0-306-40615-7 有效

旧 ISBN 号码的计算可以简化:

\def\checkISBNold#1{%
  \@tempcnta=11 \@tempcntb=\z@
  \@tfor\next:=#1\do   
    {\advance\@tempcnta\m@ne
     \@tempcntb=\numexpr\@tempcntb+\if\next X10\else\next\fi*\@tempcnta\relax
    }
  \@tempcnta=\@tempcntb
  \divide\@tempcnta by 11
  \multiply\@tempcnta by 11 
  \advance\@tempcntb-\@tempcnta
  \ifnum\@tempcntb=\z@
    \GOODISBN{\ISBN@arg}
  \else
    \BADISBN{\ISBN@arg}
  \fi
}

答案3

luatex根据@egreg 提供的内容,还有另一种解决方案。

\begin{filecontents*}{isbn.lua}
function checksum(str)
   local temp = 0
   local weight = 10
   for i = 1, string.len(str) do
      local c = str:sub(i,i)
      temp = temp + tonumber(c) * weight
      weight = weight - 1
   end
   temp = 11 - (temp % 11)
   if temp == 10 then
      return "X"
   else 
      if temp == 11 then
         return "0"
      else
         return tostring(temp)
      end
   end
end

function checkISBN(str)
   local ISBN
   local ISBN_test_str = "ISBN " .. str .. " is "
   ISBN = str:gsub("-","")
   if string.len(ISBN) == 10 then
      return tex.sprint(ISBN_test_str .. checkISBNold(ISBN))
   else
      if string.len(ISBN) == 13 then
         return tex.sprint(ISBN_test_str .. checkISBNnew(ISBN))
      else
         return tex.sprint(ISBN_test_str .. "invalid")
      end
   end
end

function checkISBNold(str)
   local check = str:sub(-1)
   local computedcheck = tostring(checksum(str:sub(1,string.len(str)-1)))
   if check == computedcheck then
      return "valid"
   else
      return "invalid"
   end
end

function checkISBNnew(str)
   local check = str:sub(-1)
   local ISBN = str:sub(1,string.len(str)-1)
   local temp = 0
   local weight,computedcheck
   for i = 1, string.len(ISBN) do
      local c = str:sub(i,i)
      if i%2 == 1 then
         weight = 1
      else
         weight = 3
      end
      temp = temp + tonumber(c) * weight
   end
   temp = temp % 10
   if temp == 0 then
      computedcheck = "0"
   else 
      computedcheck = tostring(10 - temp)
   end
   if check == computedcheck then
      return "valid"
   else
      return "invalid"
   end
end
\end{filecontents*}

\documentclass{minimal}
\directlua{dofile("isbn.lua")}
\def\checksum#1{%
  \directlua{tex.sprint(checksum("#1"))}}
\def\checkISBN#1{%
  \directlua{checkISBN("#1")}}
\begin{document}
\checksum{123456789}

\checkISBN{1000000011}

\checkISBN{1-00-000001-X}

\checkISBN{0-306-40615-2}

\checkISBN{978-0-306-40615-7}

\checkISBN{978-0-306-40415-7}
\end{document}

答案4

有点好玩,这里有一个可扩展的宏\checksum来回答初始查询。它的定义不使用辅助宏。我提供了两个版本,第二个版本可能更有效。无论如何,这不是一个计算密集型的任务。

\documentclass{article}

\usepackage{xintexpr}

% w index like in Python language
% [L] like a list in Python
% This syntax computes L only once, but each [L][w] to fetch item with
% index w "sees" the whole L
%
% there is annoying problem when the modulo 11 gives 0, then we
% we want final result to be 0 not 11 ...
% I solve it via 10 - (10+x) mod 11 trick to avoid an iterated mod 11 !
\newcommand\checksum[1]{%
\xinttheiiexpr 
  10 - (10 + subs(add([L][w]*(10-w), w = 0..8), L=\xintListWithSep{,}{#1})) 'mod' 11
\relax
}

% more efficient (probably), as here "i" is an individual digit of the input
% and @ at each step is <partial sum>, <decreasing weight>
\renewcommand\checksum[1]{%
\xinttheiiexpr 
   10 - 
% @ is previous value. I.e. a two element "list".
  (10 + [iter(0,10; ([@][0]+[@][1]*i,[@][1]-1), i=\xintListWithSep{,}{#1})][0]) 'mod' 11 
\relax
}% we used 10 - (10 + x) mod 11 trick.

\begin{document}

% expandable macro!

\checksum{123456789}%10

\checksum{383480757}%5

\checksum{055215295}%1

\checksum{020113448}%9

\ifnum\checksum{123456789}=10 OK\else\ERROR\fi

\end{document}

enter image description here

并且无包装方法(因为其他答案中没有给出)

\documentclass{article}

\makeatletter
\newcommand\checksum[1]{\check@sum #1}
\def\check@sum #1#2#3#4#5#6#7#8#9{%
    \expandafter\check@@sum\the\numexpr 10*#1 + 9*#2 + 8*#3
    + 7*#4 + 6*#5 + 5*#6 + 4*#7 + 3*#8 + 2*#9.%
}
\def\check@@sum #1.{\the\numexpr 10 - 
                    % this is 10 + #1 mod 11
                    (10 + #1 - 11*((#1+16)/11-1))}
\makeatother

\begin{document}
% expandable macro!

\checksum{123456789}%10

\checksum{383480757}%5

\checksum{055215295}%1

\checksum{020113448}%9

\ifnum\checksum{123456789}=10 OK\else\ERROR\fi

\end{document}

这样效率自然就高多了!

相关内容