2

I do love the simplicity of the readarray package but is there any way to set/change values in a data structure produced by readarray?

\documentclass{standalone}
\usepackage{readarray}

\def\data{%
1 15 14 4
10 11 8 5
7 6 9 12
16 2 3 13
}
\readarray\data\dataA[4,4]

\begin{document}
value at (2,1) = \dataA[2,1]

%what i'm searching for: 
%\set\dataA[2,1]{42} 

\end{document}
Bernard
  • 271,350

1 Answers1

2
\documentclass{standalone}
\usepackage{readarray}

\def\data{%
1 15 14 4
10 11 8 5
7 6 9 12
16 2 3 13
}
\readarray\data\dataA[4,4]

\begin{document}
value at (2,1) = \dataA[2,1]

\expandafter\def\csname dataA[2,1]\endcsname{42}

value at (2,1) = \dataA[2,1]

\end{document}

enter image description here

While this may seem like its "cheating", this is in fact the way the package defines array element names:

\def\arg@name{\csname#3[\the@row,\the@col]\endcsname}%

If you wanted a pretty looking macro, I define here \setvalue. Not done here, but one could do bounds-checking as part of the macro.

\documentclass{standalone}
\usepackage{readarray}
\makeatletter
\def\setvalue#1[#2]#3{%
  \expandafter\def\csname
    \expandafter\@gobble\string#1[#2]\endcsname{#3}%
}
\makeatother

\def\data{% 1 15 14 4 10 11 8 5 7 6 9 12 16 2 3 13 } \readarray\data\dataA[4,4]

\begin{document} value at (2,1) = \dataA[2,1]

\setvalue\dataA[2,1]{42}

value at (2,1) = \dataA[2,1]

\end{document}

LONG OVERDUE SUPPLEMENT

Two years ago, I promised the OP to eventually make revisions to the readarray package to initialize and extend arrays. I was again reminded recently that I had not done it.

I can't say that what follows will be the final version that gets into a package revision, but it presents the flavor of what I have in mind (certainly, some of the internal macros will be named less generically).

First, I have a file, readarrayhotmod.tex, that one can \input into their document:

\RequirePackage{pgffor}
\newcount\readarrayendlinechar
% INITIALIATIONS
% ON \readdef, SEP CHAR INSERTED AFTER EACH RECORD; 
% ON \readarray, SEP CHAR SERVES AS DATA-FIELD SEPARATOR
    \readarraysepchar{ }
% ON \readdef, IGNORE END-LINE CHARS BY DEFAULT (NORMAL LaTeX MODE = 5)
    \readarrayendlinechar=9
% DEFAULT CELL DATA FOR \initarray
    \def\readarrayinitvalue{-}
% DEFAULT FIELD SEPARATORS FOR \typesetarray
    \def\readarrayplanesep{\\---\\}
    \def\readarrayrowsep{\\}
    \def\readarraycolsep{,}
% DEFAULT CELL FORMATTING ON \typesetarray
    \def\readarraycellset#1{#1}
%
\makeatletter
% FIXES ON EXISTING MACROS
\def\define@rootmacro#1{%
  \expandafter\def\csname#1\endcsname[##1]{\rootmacro@aux{#1}{##1}}%
}
%
\def\nocheckbounds{\def\rootmacro@aux##1##2{\csname##1[##2]\endcsname%
 }\typeout{readarray: bounds checking OFF}%
}
%
\def\checkbounds{\def\rootmacro@aux##1##2{%
  \ifcsname##1[##2]\endcsname\csname##1[##2]\endcsname\else%
  \readarrayboundfailmsg%
  \typeout{readarray Warning: \RAbackslash##1[##2] undefined.}%
  \fi%
 }\typeout{readarray: bounds checking ON}%
}
%
\def\hypercheckbounds{\def\rootmacro@aux##1##2{%
  \ifcsname##1[##2]\endcsname\csname##1[##2]\endcsname\else
    \readarrayboundfailmsg%
    \typeout{readarray Warning: \RAbackslash##1[##2] undefined:}%
    \setcounter{index@count}{0}%
    \parse@index##2,\relax%
    \foreach\i in{1,...,\theindex@count}{%
      \ifnum\parsed@index[\i]<1%
        \relax\typeout{\nonposmessage{##1}{##2}}\fi%
    }%
    \ifnum \value{index@count}=1\relax%
      \ifnum\parsed@index[1]>\csname##1CELLS\endcsname\relax
        \typeout{\recordmessage{##1}{##2}}\fi%
    \fi
    \ifnum \value{index@count}=2\relax%
      \ifnum\parsed@index[1]>\csname##1ROWS\endcsname\relax
        \typeout{\rowmessage{##1}{\parsed@index[1]}}\fi%
      \ifnum\parsed@index[2]>\csname##1COLS\endcsname\relax
        \typeout{\colmessage{##1}{\parsed@index[2]}}\fi%
    \fi
    \ifnum \value{index@count}=3\relax%
      \ifnum\parsed@index[1]>\csname##1PLANES\endcsname\relax
        \typeout{\planemessage{##1}{\parsed@index[1]}}\fi%
      \ifnum\parsed@index[2]>\csname##1ROWS\endcsname\relax
        \typeout{\rowmessage{##1}{\parsed@index[2]}}\fi%
      \ifnum\parsed@index[3]>\csname##1COLS\endcsname\relax
        \typeout{\colmessage{##1}{\parsed@index[3]}}\fi%
    \fi%
  \fi%
 }\typeout{readarray: bounds hyperchecking ON}%
}
%
\def\@readdef#1#2#3{%
  \clear@array{#3}%
  \edef\former@recordcount{\csname #3CELLS\endcsname}%
  \def\first@row{T}%
  \def\first@plane{T}%
  \catcode\endlinechar=\readarrayendlinechar\relax %
  \def#2{}%
  \setcounter{@record}{0}%
  \openin\rdar@file=#1%
  \loop\unless\ifeof\rdar@file%
    \read\rdar@file to\rdar@fileline % Reads a line of the file into \rdar@fileline%
    \addtocounter{@record}{1}%
    \expandafter\g@addto@macro\expandafter#2\expandafter{\rdar@fileline}%
    \ifthenelse{\equal{\rdar@fileline}{}}{}{\expandafter\g@addto@macro%
      \expandafter#2\expandafter{\read@array@sepchar}}%
    \if T\first@row\read@array{#2}\setcounter{@col}{\numexpr(\Arg@listlen-1)}%
      \edef\ncols{\arabic{@col}}\def\first@row{F}\setcounter{@row}{1}%
    \else%
      \if T\first@plane%
        \ifthenelse{\equal{\rdar@fileline}{}}{%
          \edef\nrows{\arabic{@row}}\def\first@plane{F}%
        }{%
          \addtocounter{@row}{1}%
        }%
      \fi%
    \fi%
    \def\record@name{\csname #3[\the@record]\endcsname}%
    \expandafter\expandafter\expandafter\expandafter\expandafter\expandafter%
      \expandafter\def\expandafter\record@name\expandafter{\rdar@fileline}%
  \repeat%
  \edef\nrecords{\arabic{@record}}%
  \expandafter\edef\csname #3PLANES\endcsname{0}%
  \expandafter\edef\csname #3ROWS\endcsname{\nrecords}%
  \expandafter\edef\csname #3COLS\endcsname{0}%
  \expandafter\edef\csname #3CELLS\endcsname{\nrecords}%
  \closein\rdar@file%
  \catcode\endlinechar=5 %
  \define@rootmacro{#3}%
}
%
\def\clear@array#1{%
  \ifcsname #1\endcsname%
    \setcounter{@row}{0}%
    \whiledo{\value{@row}<\csname #1ROWS\endcsname}{%
      \stepcounter{@row}%
      \ifnum\csname #1COLS\endcsname=0\relax%
        \expandafter\let\csname #1[\the@row]\endcsname\undefined%
      \else
        \setcounter{@col}{0}%
        \whiledo{\value{@col}<\csname #1COLS\endcsname}{%
          \stepcounter{@col}%
          \ifnum\csname #1PLANES\endcsname=0\relax%
            \expandafter\let\csname #1[\the@row,\the@col]\endcsname
              \undefined%
          \else
            \setcounter{@plane}{0}%
            \whiledo{\value{@plane}<\csname #1PLANES\endcsname}{%
              \stepcounter{@plane}%
              \expandafter%
                \let\csname #1[\the@plane,\the@row,\the@col]\endcsname
                  \undefined%
            }%
          \fi%
        }%
      \fi%
    }%
    \expandafter\let\csname #1PLANES\endcsname\undefined
    \expandafter\let\csname #1ROWS\endcsname\undefined
    \expandafter\let\csname #1PLANES\endcsname\undefined
    \expandafter\let\csname #1\endcsname\undefined
  \fi%
}
%
\def\nonposmessage#1#2{Positive indices [#2] required for #1.}
%%% END FIXES
%
\def\parsed@index[#1]{\csname parsed@index[#1]\endcsname}
%
\edef\RAbackslash{\expandafter\@firstoftwo\string\\}
%
\def\setvalue#1[#2]#3{%
  \ifcsname\rdar@macroname#1[#2]\endcsname
    \expandafter\gdef\csname\rdar@macroname#1[#2]\endcsname{#3}%
  \else
    \typeout{readarray Warning (setvalue = #3): 
      \RAbackslash\rdar@macroname#1[#2] undefined.}%
  \fi%
}
\def\RAinitializedata#1[#2]#3{%
  \expandafter\gdef\csname
    \rdar@macroname#1[#2]\expandafter\endcsname\expandafter{#3}%
}
%
\def\initarray#1[#2]{%
  \clear@array{\rdar@macroname#1}%
  \expandafter\define@rootmacro\expandafter{\rdar@macroname#1}%
  \setcounter{index@count}{0}%
  \parse@index#2,\relax
  \ifnum\value{index@count}=2\relax
    \setcounter{use@args}{\numexpr\parsed@index[1]*\parsed@index[2]}
    \expandafter\def\csname\rdar@macroname
                      #1PLANES\endcsname{0}
    \expandafter\edef\csname\rdar@macroname
                      #1ROWS\endcsname{\parsed@index[1]}
    \expandafter\edef\csname\rdar@macroname
                      #1COLS\endcsname{\parsed@index[2]}
    \expandafter\edef\csname\rdar@macroname
                      #1CELLS\endcsname{\theuse@args}
    \foreach\Z in{1,...,\parsed@index[1]}{%
      \foreach\ZZ in{1,...,\parsed@index[2]}{%
        \RAinitializedata#1[\Z,\ZZ]{\readarrayinitvalue}}}
  \else
    \ifnum\value{index@count}=3\relax
      \setcounter{use@args}{\numexpr\parsed@index[1]*
        \parsed@index[2]*\parsed@index[3]}
      \expandafter\edef\csname\rdar@macroname
                      #1PLANES\endcsname{\parsed@index[1]}
      \expandafter\edef\csname\rdar@macroname
                      #1ROWS\endcsname{\parsed@index[2]}
      \expandafter\edef\csname\rdar@macroname
                      #1COLS\endcsname{\parsed@index[3]}
      \expandafter\edef\csname\rdar@macroname
                      #1CELLS\endcsname{\theuse@args}
      \foreach\Z in{1,...,\parsed@index[1]}{%
        \foreach\ZZ in{1,...,\parsed@index[2]}{%
          \foreach\ZZZ in{1,...,\parsed@index[3]}{%
            \RAinitializedata#1[\Z,\ZZ,\ZZZ]{\readarrayinitvalue}}}}
    \else
      [initarray ERROR: 2-D or 3-D arrays only]
    \fi
  \fi
}
%
\def\mergearray#1#2[#3]{%
  \setcounter{index@count}{0}%
  \parse@index#3,\relax
  \ifnum\value{index@count}=2\relax 
    \foreach\Z in{1,...,\csname\rdar@macroname#1ROWS\endcsname}{%
      \foreach\ZZ in{1,...,\csname\rdar@macroname#1COLS\endcsname}{%
        \edef\tmpA{\the\numexpr\Z+\parsed@index[1]-1,%
                  \the\numexpr\ZZ+\parsed@index[2]-1 }%
        \edef\tmpB{\csname\rdar@macroname#1[\Z,\ZZ]\endcsname}%
        \def\tmpC{\setvalue#2[\tmpA]}%
        \expandafter\tmpC\expandafter{\tmpB}}}%
  \else
    \ifnum\value{index@count}=3\relax 
      \foreach\Z in{1,...,\csname\rdar@macroname#1PLANES\endcsname}{%
        \foreach\ZZ in{1,...,\csname\rdar@macroname#1ROWS\endcsname}{%
          \foreach\ZZZ in{1,...,\csname\rdar@macroname#1COLS\endcsname}{%
            \edef\tmpA{\the\numexpr\Z+\parsed@index[1]-1,%
                      \the\numexpr\ZZ+\parsed@index[2]-1,%
                      \the\numexpr\ZZZ+\parsed@index[3]-1 }%
            \edef\tmpB{\csname\rdar@macroname#1[\Z,\ZZ,\ZZZ]\endcsname}%
            \def\tmpC{\setvalue#2[\tmpA]}%
            \expandafter\tmpC\expandafter{\tmpB}}}}%
    \else
      [mergearray ERROR: 2-D or 3-D arrays only]
    \fi
  \fi
}
%
\def\addtoArg@toks#1{\global\Arg@toks\expandafter{\the\Arg@toks#1}}
\def\xaddtoArg@toks#1{\expandafter\addtoArg@toks\expandafter{#1}}
\def\xxaddtoArg@toks#1{\expandafter\xaddtoArg@toks\expandafter{#1}}
%
\def\typesetarray#1{\noindent\Arg@toks{}%
  \ifnum\csname\rdar@macroname#1PLANES\endcsname>0\relax
    \foreach\Z in{1,...,\csname\rdar@macroname#1PLANES\endcsname}{%
      \ifnum\Z=1 \else\xaddtoArg@toks{\readarrayplanesep}\fi%
      \foreach\ZZ in{1,...,\csname\rdar@macroname#1ROWS\endcsname}{%
        \ifnum\ZZ=1 \else\xaddtoArg@toks{\readarrayrowsep}\fi%
        \foreach\ZZZ in{1,...,\csname\rdar@macroname#1COLS\endcsname}{%
          \ifnum\ZZZ=1 \else\xaddtoArg@toks{\readarraycolsep}\fi%
          \xxaddtoArg@toks{\expandafter\readarraycellset\expandafter
            {\csname\rdar@macroname#1[\Z,\ZZ,\ZZZ]\endcsname}}}%
      }%
    }%
  \else
    \foreach\Z in{1,...,\csname\rdar@macroname#1ROWS\endcsname}{%
      \ifnum\Z=1 \else\xaddtoArg@toks{\readarrayrowsep}\fi%
      \foreach\ZZ in{1,...,\csname\rdar@macroname#1COLS\endcsname}{%
        \ifnum\ZZ=1 \else\xaddtoArg@toks{\readarraycolsep}\fi%
        \xxaddtoArg@toks{\expandafter\readarraycellset\expandafter
          {\csname\rdar@macroname#1[\Z,\ZZ]\endcsname}}%
      }%
    }%
  \fi
  \the\Arg@toks
}

\makeatother

In addition to providing error checking on \setvalue which was provided in the answer above, it introduces 3 new commands:

\initarray\<arrayname>[<2-D or 3-D size>]
\mergearray\<from-array>\<to-array>[<insert point]
\typeset\<arrayname>

Examples would include \initarray\AB[6,6] to initialize a 6x6 array, each cell containing the replacement text of \arrayinitvalue. A merge example would be \mergearray\dataA\AB[3,2] which would merge the dataA array into the \AB array, beginning at cell [3,2] in \AB. So if \dataA is 4x4, it would span from \AB[3,2]--\AB[6,5]. If the merge extends past the \AB array boundary, warnings are printed in the log file (and at present, the document itself). The command \typesetarray\AB would output the \AB array, employing the follow default, but changeable settings:

\def\arrayplnsep{\\---\\}
\def\arrayrowsep{\\}
\def\arraycolsep{,}
\def\arrayset#1{#1}

These settings tell what gets typeset between planes, rows, columns, and how to typeset the cell itself. Changing \arraycolsep to & will make the typeset suitable for a tabular environment, for example. If you wish to typeset directly, then \def\arrayset#1{\makebox[5ex][r]{#1}} will allow a fixed space to be allocated for the typesetting of each cell.

\documentclass{article}
\usepackage{readarray}

\input readarrayhotmod.tex % DEFAULTS %% INITIALIATIONS %% ON \readdef, SEP CHAR INSERTED AFTER EACH RECORD; %% ON \readarray, SEP CHAR SERVES AS DATA-FIELD SEPARATOR % \readarraysepchar{ } %% ON \readdef, IGNORE END-LINE CHARS BY DEFAULT (NORMAL LaTeX MODE = 5) % \readarrayendlinechar=9 %% DEFAULT CELL DATA FOR \initarray % \def\readarrayinitvalue{-} %% DEFAULT FIELD SEPARATORS FOR \typesetarray % \def\readarrayplanesep{\---\} % \def\readarrayrowsep{\} % \def\readarraycolsep{,} %% DEFAULT CELL FORMATTING ON \typesetarray % \def\readarraycellset#1{#1} %%

\begin{filecontents}[overwrite]{mydata.txt} 1,15,14,4 10,11,8,5 7,6,9,12 16,2,3,13 \end{filecontents}

\begin{document} \sbox0{\begin{tabular}{|c|}\hline x\end{tabular}}% LOADS cmex FONTS \hypercheckbounds

\readarraysepchar{,} \readdef{mydata.txt}\data

  1. Set value in bounds and out of bounds\hrulefill

\readarray\data\dataA[-,\ncols]

value at (2,1) = \dataA[2,1]

\setvalue\dataA[2,1]{42}

value at (2,1) = \dataA[2,1]

\setvalue\dataA[2,7]{42}

  1. Initialize 6x6 array, specify in and out of bound cells\hrulefill

\initarray\AB[6,6]

AB[2,1] = \AB[2,1]

AB[2,2] = \AB[2,2]

AB[3,7] = \AB[3,7]

  1. Merge 4x4 array into 6x6, typeset as text and in tabular\hrulefill

\mergearray\dataA\AB[3,2]

\def\readarrayrowsep{\} \def\readarraycolsep{,} \def\readarraycellset#1{\makebox[5ex][r]{#1}}

\typesetarray\AB

\def\readarrayrowsep{\} \def\readarraycolsep{&} \def\readarraycellset#1{#1}

tabular: \begin{tabular}{cc|cccc} \hline \typesetarray\AB \\hline \end{tabular}

  1. Initialize 3x5x4 (3-D) array, set value\hrulefill

\renewcommand\readarrayinitvalue{Q}

\initarray\Q[3,5,4]

\Q[3,4,2]

\setvalue\Q[3,4,2]{SV}

\Q[3,4,2]

  1. Merge initialized 2x2x2 AND read-in 2x2x4 arrays into 3x5x4 array\hrulefill

\renewcommand\readarrayinitvalue{R}

\initarray\R[2,2,2]

\readarray\data\dataB[-,2,4]

\mergearray\dataB\Q[1,4,1]

\mergearray\R\Q[2,2,1]

\def\readarrayplanesep{\\hline} \def\readarrayrowsep{\} \def\readarraycolsep{&} \def\readarraycellset#1{#1}

\begin{tabular}{rrrrr} \hline \typesetarray\Q \\hline \end{tabular}

  1. Error checking when existing array is reinitialized\hrulefill

\initarray\Q[2,2] \Q[0,2,2] \Q[1,2,2] \QPLANES \end{document}

This code produces the following warnings in the log file

readarray: bounds hyperchecking ON
readarray Warning (setvalue = 42): \dataA[2,7] undefined.
readarray Warning: \AB[3,7] undefined:
COL=7 exceeds bounds(=6) for AB.
readarray Warning: \Q[0,2,2] undefined:
Positive indices [0,2,2] required for Q.
readarray Warning: \Q[1,2,2] undefined:
PLANE=1 exceeds bounds(=0) for Q.

and this output

enter image description here

  • i just tried \expandafter\def\csname dataA[5,1]\endcsname{3} what is actually index out of bounds, but it works. i even can get the values of \dataA[5,1]. is there a regular way to add new rows/columns? – susis strolch Jun 30 '19 at 01:15
  • @susisstrolch Using that \def indeed provides no bounds checking. Allowing rows/columns to be added would make the bookkeeping more difficult. p.s. I provide a \setvalue macro. – Steven B. Segletes Jun 30 '19 at 01:27
  • @susisstrolch If you wanted to take the task on yourself, it would entail redefining \<arrayidentifier>CELLS,\<arrayidentifier>ROWS, and \<arrayidentifier>COLS (and \<arrayidentifier>PLANES in 3-D), and also initializing the newly created cells as empty. – Steven B. Segletes Jun 30 '19 at 01:38
  • @steven-b-segletes unfortunately that exceeds my abilities in TeX programming. btw. initializing an empty array like \emptyarray\dataA[10,100] would be an nice extension. same for an easy \writedef{filename}\dataA ... if you allow me to mention some feature request here. – susis strolch Jun 30 '19 at 03:22
  • @susisstrolch Yes, I will add it to my readarray to-do list so the next time I tackle it, I'll remember what is asked for. – Steven B. Segletes Jun 30 '19 at 15:46
  • @steven-b-segletes ... and do you still remember? – susis strolch May 23 '21 at 23:39
  • @susisstrolch Thank you for the reminder. – Steven B. Segletes May 24 '21 at 00:45
  • @susisstrolch Please see the SUPPLEMENT to my answer. – Steven B. Segletes May 29 '21 at 01:17