5

I want to produce a 10x10 grid of shapes. At each point on the grid, one shape should be displayed. The shape should be randomly drawn from {red circle, red diamond, white circle}, such that:

  • red circles are drawn uniformly but there are min 10 and max 50 of them;
  • red diamonds are drawn uniformly but there are min 10 and max 50 of them;
  • white circles take up the remaining grid elements.

For complete uniform random selection (i.e., no attempt to constrain the domain of red shapes between 10-50) of the items my MWE attempt is below, but it seems that my use of \pgfmathdeclarerandomlist is bad. The file will not compile. Do you know how I can fix this?

(In contrast, below this I include a MWE where only shape varies and each element is randomly drawn from {red circle, red diamond} and it works well)

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes}
\begin{document}
\begin{tikzpicture}
\pgfmathdeclarerandomlist{states}{{circle,fill=red}{diamond,fill=red}{circle,fill=white}};
\draw [thin, black] (0,0) rectangle (7.75,7.75);
\foreach \r in {1,...,10} %row
\foreach \c in {1,...,10} %col
{
    \pgfmathrandomitem{\state}{states}
    \node[\state,draw=black] at (\c*0.75-0.25,0.75*\r-0.25) {};
}
\end{tikzpicture}
\end{document}

Example where only shape varies and no limits on the number of each type of shape is imposed, which compiles, producing the image below:

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes}
\begin{document}
\begin{tikzpicture}
    \pgfmathdeclarerandomlist{states}{{circle}{diamond}};
    \draw [thin, black] (0,0) rectangle (7.75,7.75);
    \foreach \r in {1,...,10} %row
    \foreach \c in {1,...,10} %col
    {
        \pgfmathrandomitem{\state}{states}
        \node[\state,fill=red,draw=black] at (\c*0.75-0.25,0.75*\r-0.25) {};
    }
\end{tikzpicture}
\end{document}

enter image description here

2
  • You need style/.expand twice=\state or (if there's no "fancy" stuff in the list) style/.expanded=\state. Commented Feb 24, 2023 at 16:22
  • You need an urn with 100 balls where you draw the positions for each shape without returning the ball. This is not something provided by PGFMath. Commented Feb 24, 2023 at 16:32

3 Answers 3

6

I think this is pretty simple. Just guess that you need to place 45 red circles (RC), 30 red diamonds (RD) and 25 white circles (WC). We can choose a random number n between 1 and 100=45+30+25. If n in [1,45] we place a RC, if n in [45+1,45+30]=[46,75] we place a RD, and if n in [45+30+1,45+30+25]=[76,100] we place a WC. Let's suppose that the number is n=50 in [46,75]. We place the RD and now we have 45 RC, 29 RD and 25 WC to place. Just repeat...

To avoid global variables I'm using two counters.

Like this:

\documentclass[tikz,border=2mm]{standalone}
\usetikzlibrary{shapes}

\tikzset
{% styles
   shape1/.style={draw,circle,fill=red},
   shape2/.style={draw,diamond,fill=red},
   shape3/.style={draw,circle,fill=white},
}

%% counters
\newcounter{NumberOfRC} % Number of Red Circles
\newcounter{NumberOfRD} % Number of Red Diamonds

\begin{document}
\begin{tikzpicture}
\pgfmathparse{random(10,50)}\setcounter{NumberOfRC}{\pgfmathresult}
\pgfmathparse{random(10,50)}\setcounter{NumberOfRD}{\pgfmathresult}
% Just to check the numbers
%\node[right] at (0,-1) {Red circles: \theNumberOfRC};
%\node[right] at (0,-1.5) {Red diamonds: \theNumberOfRD};
\draw [thin, black] (0,0) rectangle (7.75,7.75);
\foreach\i in {1,...,100}
{
  \pgfmathsetmacro\x{0.775*(mod(\i-1,10)+0.5)}
  \pgfmathsetmacro\y{0.775*(int((\i-1)/10)+0.5)}
  \pgfmathtruncatemacro\RandomNumber{random(0,100-\i)}
  \pgfmathtruncatemacro\TotalNumberOfRF{\theNumberOfRC+\theNumberOfRD} % Total number of Red Figures
  \ifnum\RandomNumber<\theNumberOfRC
    \pgfmathsetmacro\shape{1}
    \addtocounter{NumberOfRC}{-1}
  \else\ifnum\RandomNumber<\TotalNumberOfRF
    \pgfmathsetmacro\shape{2}
    \addtocounter{NumberOfRD}{-1}
  \else
    \pgfmathsetmacro\shape{3}
  \fi\fi
  \node[shape\shape] at (\x,\y) {};
}
\end{tikzpicture}
\end{document}

enter image description here

4
  • 2
    It is much easier and does exactly this. I already wrote a PGFmath function choose(\RandomNumber,\theNumberOfRC,theNumberOfRD) that gives the shape number and scales easily for more than two kinds. But I didn't think of all the decrementing. 🤦 Commented Feb 25, 2023 at 9:46
  • 1
    This is indeed a nice solution and much easier than my approach! Commented Feb 25, 2023 at 10:34
  • 1
    I took the liberty to generalize your approach where \tikzplacerandom allows any number of different shapes with any number of occurences. Too convulated for just a specific use-case, though. Commented Feb 25, 2023 at 13:14
  • @Qrrbrbirlbel, very good!! I like it Commented Feb 25, 2023 at 13:20
5

You could first generate a list that contains the shapes in random order and then just use these shapes one after another in the foreach loop.

The following code allows you to first create a list which generates two random integers between 10 and 50 and an integer that represents the remainder to 100. Then, a list is filled with the relevant amount of the three different shapes. This list is finally shuffled. Inside the foreach loop, the shape at the relevant index of this list is then picked.

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{shapes}

\ExplSyntaxOn
% create the needed integer and sequence variables
\int_new:N \l_randomshapes_shapeAcount_int 
\int_new:N \l_randomshapes_shapeBcount_int 
\int_new:N \l_randomshapes_shapeCcount_int 
\seq_new:N \l_randomshapes_shapelist_seq
    
\NewDocumentCommand { \createshuffledshapelist } { m m m m m } {
    % set the first variable to a random number between #2 and #3
    \int_set:Nn \l_randomshapes_shapeAcount_int { 
        \int_rand:nn { #2 } { #3 } 
    }
    % set the second variable to a random number between #4 and #5
    \int_set:Nn \l_randomshapes_shapeBcount_int { 
        \int_rand:nn { #4 } { #5 } 
    }
    % set the third variable to #1 minus the sum of the previous two variables
    \int_set:Nn \l_randomshapes_shapeCcount_int { 
        #1 - \l_randomshapes_shapeAcount_int - \l_randomshapes_shapeBcount_int
    }

    % clear the sequence in case
    \seq_clear:N \l_randomshapes_shapelist_seq
    % add as many `shape a` to the sequence as are defined in the first variable
    \int_step_inline:nn \l_randomshapes_shapeAcount_int {
        \seq_put_right:Nn \l_randomshapes_shapelist_seq { shape~a }
    }
    % add as many `shape b` to the sequence as are defined in the second variable
    \int_step_inline:nn \l_randomshapes_shapeBcount_int {
        \seq_put_right:Nn \l_randomshapes_shapelist_seq { shape~b }
    }
    % add as many `shape c` to the sequence as are defined in the third variable
    \int_step_inline:nn \l_randomshapes_shapeCcount_int {
        \seq_put_right:Nn \l_randomshapes_shapelist_seq { shape~c }
    }
    
    % shuffle the sequence
    \seq_shuffle:N \l_randomshapes_shapelist_seq
}

\NewDocumentCommand { \getfromshuffledshapelist } { m } {
    % pick index #1 from the sequence  
    \seq_item:Nn \l_randomshapes_shapelist_seq { #1 }
}
\ExplSyntaxOff

\begin{document}
\begin{tikzpicture}

\createshuffledshapelist{100}{10}{50}{10}{50}

\tikzset{
    % define the shapes
    shape a/.style={
        circle, fill=red
    },
    shape b/.style={
        diamond, fill=red
    },
    shape c/.style={
        circle, fill=white
    },
}

\draw[thin, black] (0,0) rectangle (7.75,7.75);
\foreach \r in {1,...,10} { % rows
    % calculate the running index as product of row and column (as integer)
    \foreach \c [evaluate=\c as \i using int(10*(\r-1)+\c)] in {1,...,10} { % columns
        \node[\getfromshuffledshapelist{\i}, draw=black] 
            at ({\c*0.75-0.25},{0.75*\r-0.25}) {};
    }
}

\end{tikzpicture}
\end{document}

enter image description here

1
  • I have to admit that I am not fully sure whether this way the selection of the different shapes is as randomly as the OP intended. But this might at least serve as a starting point. Commented Feb 24, 2023 at 18:31
3

This is not an answer, but I need more space than is available for a comment. I want to address the random part of your question. You already have great answers about how to create the grids, in particular starting from the choices of how many shapes, but all the answers (so far) start with a uniform choice of those numbers. That's what I want to address.

Let me be clear as to my assumptions, as if these are wrong then my analysis will be a bit off. I envision you generating all possible 10 x10 grids with the three shapes where each location has equal probability of being a given shape. From these, you then discard all the grids that don't meet your criteria. Now you pick one of the remaining patterns uniformly.

The answers so far will generate the same set of grids (subject to pseudorandom limitations) but not with the same distribution. The number of each shape is not uniformly distributed but follows a trinomial distribution, which is like a binomial but with three outcomes. So to use the technique of the other answers the numbers of each shape need to be chosen to reflect a trinomial distribution.

Fortunately, your numbers are sufficiently large that the central limit theorem comes into play. So the algorithm is as follows:

  1. Generate two pseudorandom numbers uniformly on (0,1)
  2. Use the Box-Muller transform to convert these to two normally distributed random numbers.
  3. Transform the first to a normal with mean 100×⅓ and variance 100×⅓×⅔ and round to nearest whole number. Call this n.
  4. Discard if not between 10 & 50. There's probably some maths can be done here to figure out the right initial range to guarantee being in the right range here, but the probability of being less than 10 or greater than 50 is about 0.02% so the likelihood of discarding here is very small.
  5. Transform the second to a normal with mean (100-n)×½ and variance (100-n)×½×½ and round to the nearest whole number.
  6. Discard if not in the required range, again the likelihood of needing to discard is very small.

In this, we're simulating the trinominal by a pair of binomials where the first is, say, diamond or not diamond so has probability parameter ⅓ and number of trials 100. The second is then red or white circle and has probability parameter ½ with number of trials the remaining number of shapes after the first binomial.

Here's some code. I've integrated it with both of Juan and Jasper's codes for drawing the grid (so, in particular, if anyone votes for this answer then they really ought to vote for the others as well and this shouldn't be the accepted answer). Both needed only a small modification to adapt to using this algorithm for generating the random numbers.

\documentclass{article}
%\url{https://tex.stackexchange.com/q/676390/86}
\usepackage{tikz}
\usetikzlibrary{shapes}

\ExplSyntaxOn

\fp_new:N \l__bm_ua_fp
\fp_new:N \l__bm_ub_fp
\fp_new:N \l__bm_r_fp
\fp_new:N \l__bm_th_fp
\fp_new:N \l__bm_xa_fp
\fp_new:N \l__bm_xb_fp
\fp_new:N \g__bm_outputa_fp
\fp_new:N \g__bm_outputb_fp

% Create two normally distributed variables; this uses the trigonometric version of the Box-Muller algorithm.

% This function generates the numbers inside a group and sets two global output registers
\cs_new_protected_nopar:Npn \__bm_normals:
{
  \group_begin:
  % Uniformly chosen on (0,1)
  \fp_set:Nn \l__bm_ua_fp {rand()}
  \fp_set:Nn \l__bm_ub_fp {rand()}
  % Polar coordinates
  \fp_set:Nn \l__bm_r_fp {sqrt(-2*ln(\l__bm_ua_fp))}
  \fp_set:Nn \l__bm_th_fp {2*\c_pi_fp*\l__bm_ub_fp}
  % Cartesian coordinates
  \fp_set:Nn \l__bm_xa_fp {\l__bm_r_fp * cos( \l__bm_th_fp )}
  \fp_set:Nn \l__bm_xb_fp {\l__bm_r_fp * sin( \l__bm_th_fp )}
  % Export
  \fp_gset_eq:NN \g__bm_outputa_fp \l__bm_xa_fp
  \fp_gset_eq:NN \g__bm_outputb_fp \l__bm_xb_fp
  \group_end:
}

% The next two functions are wrappers around the above to enable the target variables to be set either locally or globally
\cs_new_protected_nopar:Npn \bm_normals:NN #1#2
{
  \__bm_normals:
  \fp_set_eq:NN #1 \g__bm_outputa_fp
  \fp_set_eq:NN #2 \g__bm_outputb_fp
  \fp_gzero:N \g__bm_outputa_fp
  \fp_gzero:N \g__bm_outputb_fp
}

\cs_new_protected_nopar:Npn \bm_gnormals:NN #1#2
{
  \__bm_normals:
  \fp_gset_eq:NN #1 \g__bm_outputa_fp
  \fp_gset_eq:NN #2 \g__bm_outputb_fp
  \fp_gzero:N \g__bm_outputa_fp
  \fp_gzero:N \g__bm_outputb_fp
}

% The next functions calculate a trinomial, the structure is the same as above with an auxiliary function to do the work and two wrappers for saving the results into local or global variables
\fp_new:N \l__bm_tmpa_fp
\fp_new:N \l__bm_tmpb_fp
\fp_new:N \l__bm_mu_fp
\fp_new:N \l__bm_sigma_fp

% #1: number of trials
% #2: probability of first outcome
% #3: probability of second outcome
\cs_new_protected_nopar:Npn \__bm_trinomial:nnn #1#2#3
{
  \group_begin:
  \bm_normals:NN \l__bm_tmpa_fp \l__bm_tmpb_fp
  \fp_set:Nn \l__bm_mu_fp { (#1) * (#2) }
  \fp_set:Nn \l__bm_sigma_fp { sqrt( (#1) * (#2) * (1 - #2) ) }
  \fp_set:Nn \l__bm_tmpa_fp {round(\l__bm_tmpa_fp * \l__bm_sigma_fp + \l__bm_mu_fp)}
  \fp_set:Nn \l__bm_mu_fp { (#1 - \l__bm_tmpa_fp) * (#3) / (1 - #2) }
  \fp_set:Nn \l__bm_sigma_fp {
    sqrt(
    (#1 - \l__bm_tmpa_fp)
    * (#3) / (1 - #2)
    * (1 - #2 - #3) / (1 - #2)
    )
  }
  \fp_set:Nn \l__bm_tmpb_fp {round(\l__bm_tmpb_fp * \l__bm_sigma_fp + \l__bm_mu_fp)}
  \fp_gset_eq:NN \g__bm_outputa_fp \l__bm_tmpa_fp
  \fp_gset_eq:NN \g__bm_outputb_fp \l__bm_tmpb_fp
  \group_end:
}

\cs_new_protected_nopar:Npn \bm_trinomial:NNnnn #1#2#3#4#5
{
  \__bm_trinomial:nnn {#3}{#4}{#5}
  \fp_set_eq:NN #1 \g__bm_outputa_fp
  \fp_set_eq:NN #2 \g__bm_outputb_fp
  \fp_gzero:N \g__bm_outputa_fp
  \fp_gzero:N \g__bm_outputb_fp
}

\cs_new_protected_nopar:Npn \bm_gtrinomial:NNnnn #1#2#3#4#5
{
  \__bm_trinomial:nnn {#3}{#4}{#5}
  \fp_gset_eq:NN #1 \g__bm_outputa_fp
  \fp_gset_eq:NN #2 \g__bm_outputb_fp
  \fp_gzero:N \g__bm_outputa_fp
  \fp_gzero:N \g__bm_outputb_fp
}

% This is a user-level function which puts the resulting numbers as "decimals" (though they will be integers) into two macros (the above interal functions set fp registers)
\DeclareDocumentCommand \SetTrinomial {m m m m m}
{
  \__bm_trinomial:nnn {#3}{#4}{#5}
  \tl_set:Nx #1 {\fp_to_decimal:N \g__bm_outputa_fp}
  \tl_set:Nx #2 {\fp_to_decimal:N \g__bm_outputb_fp}
  \fp_gzero:N \g__bm_outputa_fp
  \fp_gzero:N \g__bm_outputb_fp
}

% This imposes a restriction on the numbers using a rejection method
\cs_new_protected_nopar:Npn \__bm_trinomial_between:nnnnnnn #1#2#3#4#5#6#7
{
  \group_begin:
  \bool_do_while:nn
  {
    \fp_compare_p:n {\g__bm_outputa_fp < #4}
    ||
    \fp_compare_p:n {\g__bm_outputa_fp > #5}
    ||
    \fp_compare_p:n {\g__bm_outputb_fp < #6}
    ||
    \fp_compare_p:n {\g__bm_outputb_fp > #7}
  }
  {
    \__bm_trinomial:nnn {#1}{#2}{#3}
  }
  \group_end:
}

\DeclareDocumentCommand \SetTrinomialBetween {m m m m m m m}
{
  \__bm_trinomial_between:nnnnnnn {#3}{#4}{#5}{#6}{#7}{#6}{#7}
  \tl_set:Nx #1 {\fp_to_decimal:N \g__bm_outputa_fp}
  \tl_set:Nx #2 {\fp_to_decimal:N \g__bm_outputb_fp}
  \fp_gzero:N \g__bm_outputa_fp
  \fp_gzero:N \g__bm_outputb_fp
}


% Jasper Habicht's code
\int_new:N \l_randomshapes_shapeAcount_int 
\int_new:N \l_randomshapes_shapeBcount_int 
\int_new:N \l_randomshapes_shapeCcount_int 
\seq_new:N \l_randomshapes_shapelist_seq

\NewDocumentCommand { \createshuffledshapelist } { m m m m m } {
  %
  %%% Start of modification
  %
  % Generate trinomials between the limits
  \__bm_trinomial_between:nnnnnnn {#1}{1/3}{1/3}{#2}{#3}{#4}{#5}

  \int_set:Nn \l_randomshapes_shapeAcount_int {\fp_to_int:N \g__bm_outputa_fp}
  \int_set:Nn \l_randomshapes_shapeBcount_int {\fp_to_int:N \g__bm_outputb_fp}
  %
  %%% End of modification
  %

    % set the third variable to #1 minus the sum of the previous two variables
    \int_set:Nn \l_randomshapes_shapeCcount_int { 
        #1 - \l_randomshapes_shapeAcount_int - \l_randomshapes_shapeBcount_int
    }

    % clear the sequence in case
    \seq_clear:N \l_randomshapes_shapelist_seq
    % add as many `shape a` to the sequence as are defined in the first variable
    \int_step_inline:nn \l_randomshapes_shapeAcount_int {
        \seq_put_right:Nn \l_randomshapes_shapelist_seq { shape~a }
    }
    % add as many `shape b` to the sequence as are defined in the second variable
    \int_step_inline:nn \l_randomshapes_shapeBcount_int {
        \seq_put_right:Nn \l_randomshapes_shapelist_seq { shape~b }
    }
    % add as many `shape c` to the sequence as are defined in the third variable
    \int_step_inline:nn \l_randomshapes_shapeCcount_int {
        \seq_put_right:Nn \l_randomshapes_shapelist_seq { shape~c }
    }
    
    % shuffle the sequence
    \seq_shuffle:N \l_randomshapes_shapelist_seq
}

\NewDocumentCommand { \getfromshuffledshapelist } { m } {
    % pick index #1 from the sequence  
    \seq_item:Nn \l_randomshapes_shapelist_seq { #1 }
}

\ExplSyntaxOff

\begin{document}


% Juan Castaño's code
\tikzset
{% styles
   shape1/.style={draw,circle,fill=red},
   shape2/.style={draw,diamond,fill=red},
   shape3/.style={draw,circle,fill=white},
}

%% counters
\newcounter{NumberOfRC} % Number of Red Circles
\newcounter{NumberOfRD} % Number of Red Diamonds

\begin{tikzpicture}
  %
  %%% Start of modification
  %
\SetTrinomialBetween{\NumberOfRC}{\NumberOfRD}{100}{1/3}{1/3}{10}{50}
\setcounter{NumberOfRC}{\NumberOfRC}
\setcounter{NumberOfRD}{\NumberOfRD}
  %
  %%% End of modification
  %
% Just to check the numbers
%\node[right] at (0,-1) {Red circles: \theNumberOfRC};
%\node[right] at (0,-1.5) {Red diamonds: \theNumberOfRD};
\draw [thin, black] (0,0) rectangle (7.75,7.75);
\foreach\i in {1,...,100}
{
  \pgfmathsetmacro\x{0.775*(mod(\i-1,10)+0.5)}
  \pgfmathsetmacro\y{0.775*(int((\i-1)/10)+0.5)}
  \pgfmathtruncatemacro\RandomNumber{random(0,100-\i)}
  \pgfmathtruncatemacro\TotalNumberOfRF{\theNumberOfRC+\theNumberOfRD} % Total number of Red Figures
  \ifnum\RandomNumber<\theNumberOfRC
    \pgfmathsetmacro\shape{1}
    \addtocounter{NumberOfRC}{-1}
  \else\ifnum\RandomNumber<\TotalNumberOfRF
    \pgfmathsetmacro\shape{2}
    \addtocounter{NumberOfRD}{-1}
  \else
    \pgfmathsetmacro\shape{3}
  \fi\fi
  \node[shape\shape] at (\x,\y) {};
}
\end{tikzpicture}


% Jasper Habicht's code, continued
\begin{tikzpicture}

\createshuffledshapelist{100}{10}{50}{10}{50}

\tikzset{
    % define the shapes
    shape a/.style={
        circle, fill=red
    },
    shape b/.style={
        diamond, fill=red
    },
    shape c/.style={
        circle, fill=white
    },
}

\draw[thin, black] (0,0) rectangle (7.75,7.75);
\foreach \r in {1,...,10} { % rows
    % calculate the running index as product of row and column (as integer)
    \foreach \c [evaluate=\c as \i using int(10*(\r-1)+\c)] in {1,...,10} { % columns
        \node[\getfromshuffledshapelist{\i}, draw=black] 
            at ({\c*0.75-0.25},{0.75*\r-0.25}) {};
    }
}

\end{tikzpicture}

\end{document}

Random grids

2
  • I didn't think about that possibility. I interpreted "drawn uniformly" in the OP to mean that all shapes should be 'equally' distributed around the grid. My English probably didn't help either. But if your interpretation is correct, you're right: it's something for LaTeX3 Commented Feb 25, 2023 at 11:03
  • 1
    @JuanCastaño It all depends on which bits of the original problem are meant to be done uniformly as then that affects the distributions of the rest. I read "drawn uniformly but there are ..." as meaning that it should be as if each location had equal probability of being each shape, but then the constraint is imposed at the end. Commented Feb 25, 2023 at 20:03

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .