我想在 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}
并且无包装方法(因为其他答案中没有给出)
\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}
这样效率自然就高多了!