具有组属性的完整版本

具有组属性的完整版本

我正在尝试创建一个 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(即,在您的示例中:按照所需的比例随机选择BMWMWF、中的一个);BF

  • 从随机选择的集合中,随机选择一个元素(在您的情况下是图像的名称)并将其从集合中删除。

在下面的代码中,我使用了术语“group”。这既不是“group”的数学含义,也不是 TeX 含义。团体这里有一个expl3 seq变量;你可以把它看作一个列表,只不过定义组时使用的元素顺序无关紧要。允许重复,我用它来实现高级组的计数(或比例)要求——这就是我不把它们称为“集合”的充分理由。

我的代码一个组(即定义其内容),它会对其进行打乱——这就是为什么第二个参数中使用的顺序\setGroup无关紧要。通常,低级组BM、、、 (对应于抽象描述中的集合 E_1、E_2、E_3、E_4)将包含不同的元素,除非您希望WM同一张图像出现多次。WFBF

为了便于使用,组名通过\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-1BM-2等,并\chosenElement\includegraphics{\chosenElement}节点内容中的调用替换(不要忘记放入\usepackage{graphicx}序言中)。

相关内容