我正在尝试创建一个 10x10 的外部图像矩阵,其中给定图像的位置是随机的,并且图像的选择是从一组更大的图像中随机抽取的。
具体来说,我有 4 个外部图像集 E_1、E_2、E_3、E_4,其中每个 E_i 有 n_i 个元素。我有记为 k_1、k_2、k_3、k_4 的整数,并且对于 {1,..., 4} 中的每个 i,k_i ≤ n_i。
我想要获得一个包含 N = k_1 + k_2 + k_3 + k_4 个元素的集合 F。更准确地说,F 应该包含:
- k_1 从 E_1 中随机选择不同的元素;
- k_2 随机选择,与 E_2 不同的元素;
- k_3 随机选择,与 E_3 不同的元素;
- k_4 随机选择,与 E_4 不同的元素。
换句话说,我想从每个图像集中随机抽取(不替换)一组外部图像,并将这些图像随机放置在 10x10 矩阵上。同时,我希望能够指定从 4 个集合中显示的图像的确切比例/数量。
下面是一些代码(根据之前的帖子修改) 允许我指定矩阵中显示的每个集合 (BM、WM、BF、WF) 的精确比例,同时随机打乱每个图像的精确位置。 (在此示例中,BM 填充了 40% 的图像,WM 填充了 10% 的图像,BF 填充了 40% 的图像,WF 填充了 10% 的图像)。
但是,此代码只针对每组一张图像执行此操作。我想要做的是列出每组大约 50 张图像的列表,然后随机抽取这些图像的子集(不重复图像)。
\documentclass[tikz,border=2mm]{standalone}
\ExplSyntaxOn
% We need global assignments for this sequence because \foreach executes each
% iteration in its own group.
\seq_new:N \g_dave_Images_seq
\cs_new_protected:Npn \dave_init_Images:
{
\seq_gclear:N \g_dave_Images_seq
\int_step_inline:nn { 40 }
{ \seq_gput_right:Nn \g_dave_Images_seq { BM-01.jpg } }
\int_step_inline:nn { 10 }
{ \seq_gput_right:Nn \g_dave_Images_seq { WM-01.jpg } }
\int_step_inline:nn { 10 }
{ \seq_gput_right:Nn \g_dave_Images_seq { WF-01.jpg } }
\int_step_inline:nn { 40 }
{ \seq_gput_right:Nn \g_dave_Images_seq { BF-01.jpg } }
\seq_gshuffle:N \g_dave_Images_seq
}
\msg_new:nnn { dave } { no-more-items }
{ No~ more~ items~ in~ the~ \token_to_str:N #1 sequence. }
\cs_new_protected:Npn \dave_pop_one_Image:N #1
{
\seq_gpop_left:NN \g_dave_Images_seq #1
\quark_if_no_value:NT #1
{ \msg_error:nnn { dave } { no-more-items } { \g_dave_Images_seq } }
}
\cs_new_eq:NN \initImages \dave_init_Images:
\cs_new_eq:NN \popOneImage \dave_pop_one_Image:N
\ExplSyntaxOff
\begin{document}
\begin{tikzpicture}
\initImages
\foreach \x in {1,...,10} {
\foreach \y in {1,...,10} {
\popOneImage{\myImage}
\node[inner sep=2pt] at (2*\x,2*\y){\includegraphics[height=.15\textwidth]{\myImage}};
}
}
\end{tikzpicture}
\end{document}
答案1
具有组属性的完整版本
以下是下一节中“基本版本”的概括。它几乎相同,但使示例更加直观(小网格、几何形状、颜色),并增加了对任意数量的每组属性的支持(示例仅设置一个命名的属性,node options
因为这最方便满足其特定需求,但您可以根据需要使用任意数量的属性 - 例如,参见注释掉的对 的调用\setGroupAttributesFromKeyval
)。
此示例生成了两张图片(因此您可以看到重新定义组内容很容易并且可以随时完成)。在第一张图片中:
- 每种颜色对应集合 E_1、E_2、E_3、E_4 之一;
- 同样,我们对每个集合使用不同的形状;
- 在每个集合中,所有物品都有唯一的编号——因此您可以轻松检查没有物品被重复使用。
16 个项目放置在 4×4 的网格中,并遵循以下约束:
- 5 个独特的蓝色物品;
- 6 个独特的橙色物品;
- 2 个独特的“黑暗兰花”物品;
- 3 个独特的绿色物品。
示例输出:
mwe
第二张图片非常相似,展示了如何进行图像包含(每个人安装的包中没有很多图像,因此采用 3×3 网格):
生成两张图片的代码:
\PassOptionsToPackage{svgnames}{xcolor}
\documentclass[tikz,border=2mm]{standalone}
\usepackage{graphicx}
\usetikzlibrary{shapes.geometric}
\ExplSyntaxOn
% We use global assignments for these seq variables in order to make it easy
% to use the code given here with \foreach, which executes each iteration in
% its own _TeX group_.
\cs_new_protected:Npn \dave_group_new:n #1
{
\seq_new:c { g_dave_group_#1_seq }
\prop_new:c { g_dave_group_#1_attributes_prop }
}
% Set a metadata field (an “attribute”) for a group.
%
% #1: group name
% #2: attribute name (key)
% #3: value
\cs_new_protected:Npn \dave_group_set_attribute:nnn #1#2#3
{
\prop_gput:cnn { g_dave_group_#1_attributes_prop } {#2} {#3}
}
% Set all metadata fields (“attributes”) for a group at once.
%
% #1: group name
% #2: ⟨keyval list⟩, i.e. a comma list of the form
% ⟨key1⟩ = ⟨value1⟩, ⟨key2⟩ = ⟨value2⟩, ...
%
% This *replaces all attributes* for the specified group. For details on the
% stripping of outer braces and trimming of outer spaces, see interface3.pdf.
\cs_new_protected:Npn \dave_group_set_attributes_from_keyval:nn #1#2
{
\prop_gset_from_keyval:cn { g_dave_group_#1_attributes_prop } {#2}
}
% Retrieve a group attribute and store it in the specified macro or tl var.
%
% #1: macro or tl var where the result will be stored
% #2: group name
% #3: attribute name
\cs_new_protected:Npn \dave_group_set_to_attribute:Nnn #1#2#3
{
\prop_get:cnN { g_dave_group_#2_attributes_prop } {#3} #1
}
% Expand to the value of the specified group attribute. This is slower than
% \dave_group_set_to_attribute:Nnn. The result is returned within the
% \unexpanded primitive (\exp_not:n).
%
% #1: group name
% #2: attribute name
\cs_new:Npn \dave_group_attribute:nn #1#2
{
\prop_item:cn { g_dave_group_#1_attributes_prop } {#2}
}
\msg_new:nnn { dave } { attempt-to-set-undefined-group }
{ Trying~ to~ set~ group~ '#1',~ however~ it~ hasn't~ been~ defined~ yet. }
% Define the contents (i.e. all elements) of a group. Can be used several
% times for the same group (the last call overrides any previous calls).
%
% #1: name of the group
% #2: comma list of elements which determines the new contents of the group
\cs_new_protected:Npn \dave_group_set:nn #1#2
{
\seq_if_exist:cTF { g_dave_group_#1_seq }
{
\seq_gset_from_clist:cn { g_dave_group_#1_seq } {#2}
\seq_gshuffle:c { g_dave_group_#1_seq }
}
{ \msg_error:nnx { dave } { attempt-to-set-undefined-group } {#1} }
}
\msg_new:nnn { dave } { no-more-items }
{ Not~ enough~ items~ in~ the~ '#1'~ group. }
% #1: macro or tl var where to store the popped element
% #2: name of the group to pop from
\cs_new_protected:Npn \dave_group_pop_one:Nn #1#2
{
\seq_gpop_left:cN { g_dave_group_#2_seq } #1
\quark_if_no_value:NT #1 { \msg_error:nnx { dave } { no-more-items } {#2} }
}
\msg_new:nnn { dave } { invalid-syntax-for-set-with-repeat-counts }
{
Invalid~ syntax~ for~ the~ following~ element~ of~ the~ first~ argument~
of~ \exp_not:N \dave_group_set_with_repeat_counts:nn
(\token_to_str:N \setGroupWithRepeatCounts):~ '#1'.
}
\regex_const:Nn \c__dave_group_set_with_repeat_counts_regex
{ \A (.+) \: \h* (\d+) \Z }
% Define the contents of a group using repeat counts for each element.
%
% #1: group name
% #2: comma list of specifications of the form 'item: count' (see how
% \setGroupWithRepeatCounts is used below)
\cs_new_protected:Npn \dave_group_set_with_repeat_counts:nn #1#2
{
\seq_if_exist:cTF { g_dave_group_#1_seq }
{ \__dave_group_set_with_repeat_counts:nn {#1} {#2} }
{ \msg_error:nnx { dave } { attempt-to-set-undefined-group } {#1} }
}
\cs_new_protected:Npn \__dave_group_set_with_repeat_counts:nn #1#2
{
\seq_gclear:c { g_dave_group_#1_seq }
\clist_map_inline:nn {#2}
{
\regex_extract_once:NnNTF \c__dave_group_set_with_repeat_counts_regex
{##1} \l_tmpa_seq
{
\int_step_inline:nn { \seq_item:Nn \l_tmpa_seq { 3 } }
{
\seq_gput_right:cx { g_dave_group_#1_seq }
{ \seq_item:Nn \l_tmpa_seq { 2 } }
}
}
{
\msg_error:nnn { dave }
{ invalid-syntax-for-set-with-repeat-counts } {##1}
}
}
\seq_gshuffle:c { g_dave_group_#1_seq }
}
\cs_new_eq:NN \newGroup \dave_group_new:n
\cs_new_eq:NN \setGroup \dave_group_set:nn
\cs_new_eq:NN \setGroupAttribute \dave_group_set_attribute:nnn
\cs_new_eq:NN \setGroupAttributesFromKeyval
\dave_group_set_attributes_from_keyval:nn
\cs_new_eq:NN \setToGroupAttribute \dave_group_set_to_attribute:Nnn
\cs_new_eq:NN \groupAttribute \dave_group_attribute:nn
\cs_new_eq:NN \popOneFromGroup \dave_group_pop_one:Nn
\cs_new_eq:NN \setGroupWithRepeatCounts \dave_group_set_with_repeat_counts:nn
\ExplSyntaxOff
% Declare a few groups and assign them attributes. Alternatively, this could be
% done in the document body if you prefer.
\foreach \group in {high level, group 1, group 2, group 3, group 4}{%
\newGroup{\group}%
}
\setGroupAttribute{group 1}{node options}{shape=circle, fill=DeepSkyBlue}
\setGroupAttribute{group 2}{node options}{shape=rectangle, fill=DarkOrange}
\setGroupAttribute{group 3}{node options}{shape=diamond, fill=DarkOrchid}
% The space tokens will be in the 'node options' attribute, however this
% doesn't matter because the value will be fed to \pgfkeys via \node. You can
% see why I used only one attribute to store all the node options pertaining
% to a group.
\setGroupAttribute{group 4}{node options}{
shape=regular polygon, regular polygon sides=5, fill=MediumAquamarine,
}
% One can also set all attributes at once. Note that this *replaces all
% attributes* for the specified group.
% \setGroupAttributesFromKeyval{group 1}{attr1=value1, attr2=value2, ...}
\tikzset{style/.style={#1}}% trick from Qrrbrbirlbel (I don't remember where!)
\begin{document}
% ****************************************************************************
% * First picture *
% ****************************************************************************
% Contents of the 'high level' group
\setGroupWithRepeatCounts{high level}{%
group 1: 5, % 'group 1' is repeated five times
group 2: 6, % 'group 2' is repeated six times
group 3: 2, % 'group 3' is repeated twice
group 4: 3 % 'group 4' is repeated three times
}
% Contents of the groups 'group 1', 'group 2', 'group 3' and 'group 4'
\setGroup{group 1}{1, 2, 3, 4, 5, 6, 7, 8, 9}% these groups aren't
\setGroup{group 2}{1, 2, 3, 4, 5, 6, 7, 8, 9}% required to have
\setGroup{group 3}{1, 2, 3, 4, 5, 6, 7, 8, 9}% the same number
\setGroup{group 4}{1, 2, 3, 4, 5, 6, 7, 8, 9}% of items
\begin{tikzpicture}
\foreach \x in {1,...,4} {
\foreach \y in {1,...,4} {
\popOneFromGroup{\lowLevelGroup}{high level}
\popOneFromGroup{\chosenElement}{\lowLevelGroup}
% Retrieve the value of the 'node options' attribute and store it in
% \nodeOptions.
\setToGroupAttribute{\nodeOptions}{\lowLevelGroup}{node options}
\node[style/.expand once={\nodeOptions}, minimum size=1.25cm,
text=white, font=\Large] at (1.5*\x, 1.5*\y) {\chosenElement};
}
}
\end{tikzpicture}
% ****************************************************************************
% * Second picture *
% ****************************************************************************
% Redefine the contents of the 'high level' group
\setGroupWithRepeatCounts{high level}{%
group 1: 2, % 'group 1' is repeated twice
group 2: 3, % 'group 2' is repeated three times
group 3: 2, % 'group 3' is repeated twice
group 4: 2 % 'group 4' is repeated twice
}
% Redefine the contents of groups 'group 1', 'group 2', 'group 3' and 'group 4'
\setGroup{group 1}{example-image, example-image-plain}
\setGroup{group 2}{example-image-a, example-image-b, example-image-c}
\setGroup{group 3}{example-image-duck, example-grid-100x100bp}
\setGroup{group 4}{example-image-4x3, example-image-golden}
\begin{tikzpicture}[scale=4]
\foreach \x in {1,...,3} {
\foreach \y in {1,...,3} {
\popOneFromGroup{\lowLevelGroup}{high level}
\popOneFromGroup{\chosenElement}{\lowLevelGroup}
% Retrieve the value of the 'node options' attribute and store it in
% \nodeOptions.
\setToGroupAttribute{\nodeOptions}{\lowLevelGroup}{node options}
\node[style/.expand once={\nodeOptions}] at (\x, \y)
{\includegraphics[height=1.25cm]{\chosenElement}};
}
}
\end{tikzpicture}
\end{document}
基本版本(无每个组的属性)并附带一些解释
您所要求的可以使用与前,依次分为两个层次:
从一个
high_level
结构中,选择一个项目,该项目标识了您将在下一步中从中提取的集合 E_i(即,在您的示例中:按照所需的比例随机选择BM
、WM
、WF
、中的一个);BF
从随机选择的集合中,随机选择一个元素(在您的情况下是图像的名称)并将其从集合中删除。
在下面的代码中,我使用了术语“group”。这既不是“group”的数学含义,也不是 TeX 含义。团体这里有一个expl3
seq
变量;你可以把它看作一个列表,只不过定义组时使用的元素顺序无关紧要。允许重复,我用它来实现高级组的计数(或比例)要求——这就是我不把它们称为“集合”的充分理由。
我的代码套一个组(即定义其内容),它会对其进行打乱——这就是为什么第二个参数中使用的顺序\setGroup
无关紧要。通常,低级组BM
、、、 (对应于抽象描述中的集合 E_1、E_2、E_3、E_4)将包含不同的元素,除非您希望WM
同一张图像出现多次。WF
BF
为了便于使用,组名通过\newGroup
、\setGroup
和递归扩展\popOneFromGroup
。不要在这些名称中放置任何格式化命令(如\textbf
或\color
);它们只是编程语言中的标识符,就像变量名一样。
\documentclass[tikz,border=2mm]{standalone}
\ExplSyntaxOn
% We use global assignments for these seq variables in order to make it easy
% to use the code given here with \foreach, which executes each iteration in
% its own _TeX group_.
\cs_new_protected:Npn \dave_group_new:n #1
{
\seq_new:c { g_dave_group_#1_seq }
}
\msg_new:nnn { dave } { attempt-to-set-undefined-group }
{ Trying~ to~ set~ group~ '#1',~ however~ it~ hasn't~ been~ defined~ yet. }
% Define the contents (i.e. all elements) of a group. Can be used several
% times for the same group (the last call overrides any previous calls).
%
% #1: name of the group
% #2: comma list of elements which determines the new contents of the group
\cs_new_protected:Npn \dave_group_set:nn #1#2
{
\seq_if_exist:cTF { g_dave_group_#1_seq }
{
\seq_gset_from_clist:cn { g_dave_group_#1_seq } {#2}
\seq_gshuffle:c { g_dave_group_#1_seq }
}
{ \msg_error:nnx { dave } { attempt-to-set-undefined-group } {#1} }
}
\msg_new:nnn { dave } { no-more-items }
{ Not~ enough~ items~ in~ the~ '#1'~ group. }
% #1: macro or tl var where to store the popped element
% #2: name of the group to pop from
\cs_new_protected:Npn \dave_group_pop_one:Nn #1#2
{
\seq_gpop_left:cN { g_dave_group_#2_seq } #1
\quark_if_no_value:NT #1 { \msg_error:nnx { dave } { no-more-items } {#2} }
}
\msg_new:nnn { dave } { invalid-syntax-for-set-with-repeat-counts }
{
Invalid~ syntax~ for~ the~ following~ element~ of~ the~ first~ argument~
of~ \exp_not:N \dave_group_set_with_repeat_counts:nn
(\token_to_str:N \setGroupWithRepeatCounts):~ '#1'.
}
\regex_const:Nn \c__dave_group_set_with_repeat_counts_regex
{ \A (.+) \: \h* (\d+) \Z }
% Define the contents of a group using repeat counts for each element.
%
% #1: group name
% #2: comma list of specifications of the form 'item: count' (see how
% \setGroupWithRepeatCounts is used below)
\cs_new_protected:Npn \dave_group_set_with_repeat_counts:nn #1#2
{
\seq_if_exist:cTF { g_dave_group_#1_seq }
{ \__dave_group_set_with_repeat_counts:nn {#1} {#2} }
{ \msg_error:nnx { dave } { attempt-to-set-undefined-group } {#1} }
}
\cs_new_protected:Npn \__dave_group_set_with_repeat_counts:nn #1#2
{
\seq_gclear:c { g_dave_group_#1_seq }
\clist_map_inline:nn {#2}
{
\regex_extract_once:NnNTF \c__dave_group_set_with_repeat_counts_regex
{##1} \l_tmpa_seq
{
\int_step_inline:nn { \seq_item:Nn \l_tmpa_seq { 3 } }
{
\seq_gput_right:cx { g_dave_group_#1_seq }
{ \seq_item:Nn \l_tmpa_seq { 2 } }
}
}
{
\msg_error:nnn { dave }
{ invalid-syntax-for-set-with-repeat-counts } {##1}
}
}
\seq_gshuffle:c { g_dave_group_#1_seq }
}
\cs_new_eq:NN \newGroup \dave_group_new:n
\cs_new_eq:NN \setGroup \dave_group_set:nn
\cs_new_eq:NN \popOneFromGroup \dave_group_pop_one:Nn
\cs_new_eq:NN \setGroupWithRepeatCounts \dave_group_set_with_repeat_counts:nn
\ExplSyntaxOff
% Declare and populate the groups. Alternatively, this could be done in the
% document body if you prefer.
\foreach \group in {high level, BM, WM, WF, BF}{%
\newGroup{\group}%
}
% Contents of the 'high level' group
\setGroupWithRepeatCounts{high level}{%
BM: 40, % 'BM' is repeated 40 times
WM: 10, % 'WM' is repeated 10 times
WF: 10, % 'WF' is repeated 10 times
BF: 40 % 'BF' is repeated 40 times
}
% Contents of the four groups BM, WM, WF and BF. This could of course be
% automated; I'm doing it manually here, because this is where you'll want to
% put the names of your images.
\setGroup{BM}{BM-1, BM-2, BM-3, BM-4, BM-5, BM-6, BM-7, BM-8, BM-9, BM-10,
BM-11, BM-12, BM-13, BM-14, BM-15, BM-16, BM-17, BM-18, BM-19, BM-20, BM-21,
BM-22, BM-23, BM-24, BM-25, BM-26, BM-27, BM-28, BM-29, BM-30, BM-31, BM-32,
BM-33, BM-34, BM-35, BM-36, BM-37, BM-38, BM-39, BM-40, BM-41, BM-42, BM-43,
BM-44, BM-45, BM-46, BM-47, BM-48, BM-49, BM-50, BM-51}
\setGroup{WM}{WM-1, WM-2, WM-3, WM-4, WM-5, WM-6, WM-7, WM-8, WM-9, WM-10,
WM-11, WM-12, WM-13, WM-14, WM-15, WM-16, WM-17, WM-18, WM-19, WM-20, WM-21,
WM-22, WM-23, WM-24, WM-25, WM-26, WM-27, WM-28, WM-29, WM-30, WM-31, WM-32,
WM-33, WM-34, WM-35, WM-36, WM-37, WM-38, WM-39, WM-40, WM-41, WM-42, WM-43,
WM-44, WM-45, WM-46, WM-47, WM-48, WM-49, WM-50}
\setGroup{WF}{WF-1, WF-2, WF-3, WF-4, WF-5, WF-6, WF-7, WF-8, WF-9, WF-10,
WF-11, WF-12, WF-13, WF-14, WF-15, WF-16, WF-17, WF-18, WF-19, WF-20, WF-21,
WF-22, WF-23, WF-24, WF-25, WF-26, WF-27, WF-28, WF-29, WF-30, WF-31, WF-32,
WF-33, WF-34, WF-35, WF-36, WF-37, WF-38, WF-39, WF-40, WF-41, WF-42, WF-43,
WF-44, WF-45, WF-46, WF-47, WF-48}
\setGroup{BF}{BF-1, BF-2, BF-3, BF-4, BF-5, BF-6, BF-7, BF-8, BF-9, BF-10,
BF-11, BF-12, BF-13, BF-14, BF-15, BF-16, BF-17, BF-18, BF-19, BF-20, BF-21,
BF-22, BF-23, BF-24, BF-25, BF-26, BF-27, BF-28, BF-29, BF-30, BF-31, BF-32,
BF-33, BF-34, BF-35, BF-36, BF-37, BF-38, BF-39, BF-40, BF-41, BF-42, BF-43,
BF-44, BF-45, BF-46, BF-47, BF-48, BF-49, BF-50, BF-51, BF-52}
\begin{document}
\begin{tikzpicture}
\foreach \x in {1,...,10} {
\foreach \y in {1,...,10} {
\popOneFromGroup{\lowLevelGroup}{high level}
\popOneFromGroup{\chosenElement}{\lowLevelGroup}
\node[minimum size=1.25cm] at (1.5*\x, 1.5*\y) {\chosenElement};
}
}
\end{tikzpicture}
\end{document}
对于您的图像:如我的第一个示例所示,您可以简单地将它们的名称替换为BM-1
、BM-2
等,并\chosenElement
用\includegraphics{\chosenElement}
节点内容中的调用替换(不要忘记放入\usepackage{graphicx}
序言中)。