我需要对 TikZ 坐标列表进行排序,首先按增加 X 坐标,然后按增加 Y 坐标。目前,我通过调用外部 Python shell 来执行此操作,但如果能够完全在 LaTeX 中执行此操作会更好。
下面是一个最小的工作示例来说明:
\documentclass{article}
\def\coordList{(5,5) (0,0) (2,5) (2,4) (-1, 3)}
\def\sortList#1{
% TODO: ?
}
\begin{document}
Input: \coordList
After sorting: \sortList{\coordList}
Expected result: (-1, 3) (0,0) (2,4) (2,5) (5,5)
\end{document}
列表的格式不一定需要与 MWE 中的格式相同;唯一的要求是它们最后应该进行排序。
答案1
更新添加了处理维度坐标的变体。
这是“代码4”的改编https://tex.stackexchange.com/a/273084/4686
我最初打算使用归并排序(上面链接中的“代码 6”),但、 和空格也(
特别处理\pdfescapestring
,因此选择了“代码 4”。这是可扩展的代码。即使由 TikZ 解析也应该可以工作。警告:我有一天注意到 TikZ 的坐标限制在大约 100 个扩展步骤,这对于任何严肃的可扩展宏来说都非常低。可以使用技巧。包中xintexpr
有一个宏\xintthecoords
,它将偶数个值的逗号分隔列表转换为 TikZ 的坐标对coordinates
。也许我应该在这里编写代码,使其一直使用逗号分隔的值。这将很方便\xintthecoords
在最后一分钟将其输入以生成坐标对,从而以欺骗 TikZ 的扩展计数器。我想得太晚了,如果需要,我会编辑。
\documentclass{article}
\makeatletter
% This is based ond the "code 4" of
% https://tex.stackexchange.com/a/273084/4686,
%
% modified to handle pairs (a, b) (c,d)(e, f) (g,h) etc...
%
% acts expandably. Lexicographic order.
\makeatletter
% Here we define the comparison macro for pairs (a,b)
% We assume decimal numbers acceptable to \ifdim tests
\long\def\xintdothis #1#2\xintorthat #3{\fi #1}%
\let\xintorthat \@firstofone
\long\def\@thirdoffour #1#2#3#4{#3}%
\long\def\@fourthoffour #1#2#3#4{#4}%
\def\IfFirstPairIsGreaterTF #1#2{\@IfFirstPairIsGreaterTF #1,#2,}%
\def\@IfFirstPairIsGreaterTF #1,#2,#3,#4,{%
\ifdim #1\p@=#3\p@
\xintdothis{%
\ifdim #2\p@>#4\p@\expandafter\@firstoftwo
\else\expandafter\@secondoftwo\fi}\fi
\ifdim #1\p@>#3\p@\expandafter\@thirdoffour
\else\expandafter\@fourthoffour\fi
\xintorthat{}%
}%
% not needed for numerical inputs
% \catcode`! 3
% \catcode`? 3
% Here there is a very strange \romannumeral0\romannumeral0, this is
% due to some convoluted scheme to avoid double spaces or no spaces
% in between coordinate pairs. Trust me.
\def\QSpairs {\romannumeral0\romannumeral0\qspairs }%
% first we check if empty list
\def\qspairs #1{\expandafter\qspairs@a\romannumeral-`0#1(!)(?)}%
\def\qspairs@a #1(#2{\ifx!#2\expandafter\qspairs@abort\else
\expandafter\qspairs@b\fi (#2}%
\edef\qspairs@abort #1(?){\space\space}%
%
% we check if empty of single and if not pick up the first as Pivot:
\def\qspairs@b #1(#2)#3(#4){\ifx?#4\xintdothis\qspairs@empty\fi
\ifx!#4\xintdothis\qspairs@single\fi
\xintorthat \qspairs@separate {}{}{#2}(#4)}%
\def\qspairs@empty #1(?){ }%
\edef\qspairs@single #1#2#3#4(?){\space\space(#3)}%
\def\qspairs@separate #1#2#3#4(#5)%
{%
\ifx!#5\expandafter\qspairs@separate@done\fi
\IfFirstPairIsGreaterTF {#5}{#3}%
\qspairs@separate@appendtogreater
\qspairs@separate@appendtosmaller {#5}{#1}{#2}{#3}%
}%
%
\def\qspairs@separate@appendtogreater #1#2{\qspairs@separate {#2 (#1)}}%
\def\qspairs@separate@appendtosmaller #1#2#3{\qspairs@separate {#2}{#3 (#1)}}%
%
\def\qspairs@separate@done\IfFirstPairIsGreaterTF #1#2%
\qspairs@separate@appendtogreater
\qspairs@separate@appendtosmaller #3#4#5#6(?)%
{%
\expandafter\qspairs@f\expandafter
{\romannumeral0\qspairs@b #4(!)(?)}{\qspairs@b #5(!)(?)}{ (#2)}%
}%
%
\def\qspairs@f #1#2#3{#2#3#1}%
%
% \catcode`! 12
% \catcode`? 12
\makeatother
\usepackage{geometry}
\begin{document}
\Large
\def\coordList{(5,5) (0,0) (2,5) (2,4) (-1, 3)}
Input: \coordList
After sorting: \QSpairs{\coordList}
Expected result: (-1, 3) (0,0) (2,4) (2,5) (5,5)
\bigskip
\def\coordList {
(735.578, -0.5)
(408.866, 7.2)
(513.653, 1)
(465.136, 17)
(323.362, 17)
(408.866, 3)
(408.866, 4)
(735.578, -0.1)
(408.866, 7.1)
(408.866, 5)
(886.204, 1.4)
(408.866, 2)
(649.711, 17)
(735.578, -1)
(886.204, 1.3)
(886.204, 1.2)
(254.715, 17)
(408.866, 6)
(886.204, 1)
(504.730, 17)
(504.730, -100)
(186.578, -2)
(735.578, -0.4)
(608.552, 0)
(408.866, 7.21)
(412.004, -1)
(886.204, 1.1)
(195.793, 17)
(408.866, 1)
(388.683, 17)
(140.974, -10)
(408.866, 7.201)
}
Input: \coordList
Output: \QSpairs{\coordList}
\end{document}
更新处理维度坐标。
\documentclass{article}
\makeatletter
% This is based ond the "code 4" of
% https://tex.stackexchange.com/a/273084/4686,
%
% modified to handle pairs (a, b) (c,d)(e, f) (g,h) etc...
%
% acts expandably. Lexicographic order.
\makeatletter
% This variant assumes that all a's and b's are explicit dimension coordinates
% like 5pt or 20ex
\long\def\xintdothis #1#2\xintorthat #3{\fi #1}%
\let\xintorthat \@firstofone
\long\def\@thirdoffour #1#2#3#4{#3}%
\long\def\@fourthoffour #1#2#3#4{#4}%
\def\IfFirstPairIsGreaterTF #1#2{\@IfFirstPairIsGreaterTF #1,#2,}%
% Th code handles also \dimen's like \ht\box0
% (but the output then can not be directly printed, it can
% only be used in contexts accepting \dimen's)
% Variant handling also things like \ht\box0
\def\@IfFirstPairIsGreaterTF #1,#2,#3,#4,{%
\ifdim \dimexpr(#1)-(#3)=\z@
\xintdothis{%
\ifdim \dimexpr (#2)-(#4)>\z@\expandafter\@firstoftwo
\else\expandafter\@secondoftwo\fi}\fi
\ifdim \dimexpr(#1)-(#3)>\z@\expandafter\@thirdoffour
\else\expandafter\@fourthoffour\fi
\xintorthat{}%
}%
% not needed for numerical inputs
% \catcode`! 3
% \catcode`? 3
% Here there is a very strange \romannumeral0\romannumeral0, this is
% due to some convoluted scheme to avoid double spaces or no spaces
% in between coordinate pairs. Trust me.
\def\QSpairs {\romannumeral0\romannumeral0\qspairs }%
% first we check if empty list (else \qsfull@finish will not find a comma)
\def\qspairs #1{\expandafter\qspairs@a\romannumeral-`0#1(!)(?)}%
\def\qspairs@a #1(#2{\ifx!#2\expandafter\qspairs@abort\else
\expandafter\qspairs@b\fi (#2}%
\edef\qspairs@abort #1(?){\space\space}%
%
% we check if empty of single and if not pick up the first as Pivot:
\def\qspairs@b #1(#2)#3(#4){\ifx?#4\xintdothis\qspairs@empty\fi
\ifx!#4\xintdothis\qspairs@single\fi
\xintorthat \qspairs@separate {}{}{#2}(#4)}%
\def\qspairs@empty #1(?){ }%
\edef\qspairs@single #1#2#3#4(?){\space\space(#3)}%
\def\qspairs@separate #1#2#3#4(#5)%
{%
\ifx!#5\expandafter\qspairs@separate@done\fi
\IfFirstPairIsGreaterTF {#5}{#3}%
\qspairs@separate@appendtogreater
\qspairs@separate@appendtosmaller {#5}{#1}{#2}{#3}%
}%
%
\def\qspairs@separate@appendtogreater #1#2{\qspairs@separate {#2 (#1)}}%
\def\qspairs@separate@appendtosmaller #1#2#3{\qspairs@separate {#2}{#3 (#1)}}%
%
\def\qspairs@separate@done\IfFirstPairIsGreaterTF #1#2%
\qspairs@separate@appendtogreater
\qspairs@separate@appendtosmaller #3#4#5#6(?)%
{%
\expandafter\qspairs@f\expandafter
{\romannumeral0\qspairs@b #4(!)(?)}{\qspairs@b #5(!)(?)}{ (#2)}%
}%
%
\def\qspairs@f #1#2#3{#2#3#1}%
%
% \catcode`! 12
% \catcode`? 12
\makeatother
\usepackage{geometry}
\begin{document}
\Large
\def\coordList{(5pt, 5pt) (0pt, 0pt) (2pt, 5pt) (2pt, 5bp) (2pt, 4pt) (2bp, 4pt) (-1pt, 3pt)}
Input: \coordList
After sorting: \QSpairs{\coordList}
\end{document}
答案2
此包forest
实现了您可以使用的快速排序算法。该算法在手册的第 8.2 节(实现部分)中有描述。
要使用\forest@sort
宏,必须定义两个宏:
对两个项目进行排序的宏:下面,
\sortcoordinates
。TeX
将一个项目复制(在s的意义上)到另一个项目上的宏\let
:如下所示\letcoordinates
。
由于数组排序forest
实际上是包内部的事情,因此没有很好的宏来设置和读取与之配套的数组,但这并不是一个主要障碍arrayjobx
。(包multido
用于遍历数组。)
下面的代码使用了 的实现细节arrayjobx
,即42
数组的项coordinates
将存储在控制序列中coordinates42~
。
\documentclass{article}
\usepackage{arrayjobx}
\usepackage{multido}
\usepackage{forest}
\makeatletter
\def\letcoordinates#1#2{\csletcs{coordinates#1\string~}{coordinates#2\string~}}
\def\parsecoordinate(#1,#2)#3#4{%
\def#3{#1 pt}%
\def#4{#2 pt}%
}
\def\sortcoordinates#1#2{%
\expandafter\expandafter\expandafter\parsecoordinate\csname coordinates#1\string~\endcsname\firstx\firsty
\expandafter\expandafter\expandafter\parsecoordinate\csname coordinates#2\string~\endcsname\secondx\secondy
\forest@sort@cmptwodimcs{firstx}{firsty}{secondx}{secondy}%
}
\makeatother
\begin{document}
\newarray\coordinates
\readarray{coordinates}{(5,5)&(0,0)&(2,5)&(2,4)&(-1, 3)}
\def\ncoordinates{5}
\newcounter{i}
Unsorted: \multido{\i=1+1}{\ncoordinates}{\coordinates(\i),}
\makeatletter
\forest@sort\sortcoordinates\letcoordinates\forest@sort@ascending{1}{\ncoordinates}%
\makeatother
Sorted: \multido{\i=1+1}{\ncoordinates}{\coordinates(\i),}
\end{document}
输出:
Unsorted: (5,5),(0,0),(2,5),(2,4),(-1, 3),
Sorted: (-1, 3),(0,0),(2,4),(2,5),(5,5),