15

I try to find a way to make a

\newcommand

so that it has different outputs, that it chooses randomly. In the best case the macro should also avoid word repetitions (something like, only choose this word if it hasnt been used for 50 words).

In this macro I want to group similar words, I use a lot in the document. For example if I often write the same name, it should sometimes just print the family name and other times the full name.

Edit another example: the macro should print "the group" or "the group of people" or any different synonym, so that I just have one macro for this pool of words.

12
  • Interesting question ;-)
    – user31729
    Aug 21 '14 at 10:16
  • What do you want to achieve with it? What is the context? If you just need some text lipsum can help. Some "rubbish" can be generated for example here.
    – Hackbard_C
    Aug 21 '14 at 10:17
  • @Hackbard_C: Did you read the question???
    – user31729
    Aug 21 '14 at 10:17
  • 1
    varioref does something similar (but just alternates between two styles) Aug 21 '14 at 10:19
  • 2
    @Benutzer193 Thanks for the explanation. Can this random quote selector help?
    – Hackbard_C
    Aug 21 '14 at 10:25
15

You can define the macro by specifying a relative frequency:

\documentclass{article}
\usepackage{xparse}
\input{random}

\ExplSyntaxOn
% alias \setrannum to an expl3 function
\cs_set_eq:NN \benutzer_set_rannum:Nnn \setrannum

\NewDocumentCommand{\newrandomcommand}{mm}
 {% #1 is the macro name, #2 is the data
  \cs_if_exist:cTF { #1 }
   {
    ERROR!
   }
   {
    \benutzer_new_random_command:nn { #1 } { #2 }
   }
 }

\seq_new:N \l__benutzer_data_seq
\int_new:N \l__benutzer_random_int

\cs_new_protected:Npn \benutzer_new_random_command:nn #1 #2
 {
  \seq_set_split:Nnn \l__benutzer_data_seq { , } { #2 }
  \seq_new:c { g_benutzer_#1_data_seq }
  \benutzer_process_data:n { #1 }
  \cs_new_protected:cpn { #1 }
   {
    \benutzer_set_rannum:Nnn \l__benutzer_random_int { 1 }
     { \seq_count:c { g_benutzer_#1_data_seq } }
    \seq_item:cn { g_benutzer_#1_data_seq } { \l__benutzer_random_int }
   }
 }

\cs_new_protected:Npn \benutzer_process_data:n #1
 {
  \seq_map_inline:Nn \l__benutzer_data_seq
   {
    \__benutzer_process_item:nw { #1 } ##1 // \q_stop
   }
 }

\cs_new:Npn \__benutzer_process_item:nw #1 #2 / #3 / #4 \q_stop
 {
  \tl_if_blank:nTF { #3 }
   {
    \__benutzer_add_item:nnn { #1 } { #2 } { 1 }
   }
   {
    \__benutzer_add_item:nnn { #1 } { #2 } { #3 }
   }
 }

\cs_new_protected:Npn \__benutzer_add_item:nnn #1 #2 #3
 {
  \prg_replicate:nn { #3 }
   {
    \seq_put_right:cn { g_benutzer_#1_data_seq } { #2 }
   }
 }

% this is just for testing
\NewDocumentCommand{\test}{mm}
 {
  \prg_replicate:nn { #2 } { #1\space }
 }

\ExplSyntaxOff

% Fyodor will appear 10/61 times on average
% Michailovich will appear 1/61 times on average
% Dostoyevsky will appear 50/61 times on average
\newrandomcommand{fyodor}{Fyodor/10, Mikhailovich, Dostoyevsky/50}

\begin{document}

\raggedright
\test{\fyodor}{100}

\end{document}

enter image description here

1
  • This solution is really good. I specially like the option to set the average appearance of an word. Thanks for the answer. Aug 21 '14 at 12:18
12

This is a preliminary version. The accounting of how much a word has been called so far is not done so far. I will update the solution...

Just define a word pool list with \NewWordPool{poolname}{WordA,WordB,{SentenceA}}

and say \pickuprandomword{\poolname}

The pool name is just like a macro name, but without \ character

\documentclass{article}
\usepackage{datetime}%
\usepackage{tikz}%
\usepackage{ifthen}%
\usepackage{etoolbox}%

\listgadd{\GroupWordsPools}{}%

\listgadd{\GroupWordsPoolsCount}{}%

\usepackage{forloop}%

\newcommand{\NewWordPool}[2]{%
\csxdef{#1}{}%
\forcsvlist{\listcsxadd{#1}}{#2}%
\csxdef{#1values}{}%
\forcsvlist{\listcsxadd{#1values}}{0}%
}%


\newcounter{loopcounter}%

\newcounter{randompos}%

\def\lastrandom{0}%

\newcommand{\pickuprandomword}[2][50]{%
\def\currentrandom{}%
%First counter the number of elements in the list
\renewcommand*{\do}[1]{%
\stepcounter{randompos}%
}{}%
\dolistloop{#2}% 
\pgfmathrandominteger{\currentrandom}{1}{\number\value{randompos}}%
\whiledo{\lastrandom =\currentrandom}{%
\pgfmathrandominteger{\currentrandom}{1}{\number\value{randompos}}%
}%
\setcounter{randompos}{0}%
\renewcommand*{\do}[1]{%
\stepcounter{randompos}%
\ifnumequal{\number\value{randompos}}{\currentrandom}{%
% Output the name
##1%
\listbreak%
}{}%
}%
\dolistloop{#2}%
\edef\lastrandom{\currentrandom}%
}%



\begin{document}

\NewWordPool{GroupWordsPool}{Hamlet,{Hamlet -- Prince of Denmark}, Shakespeare,{William Shakespeare}, Einstein,{Albert Einstein},Frodo, Bilbo, Gandalf,Aragorn,Galadriel,{Thorin Oakshield},Pippin,Elrond,Heisenberg,StackExchange}%


\forloop{loopcounter}{1}{\value{loopcounter} < 30}{%
\pickuprandomword{\GroupWordsPool}\par

}%

\end{document}

Edit I am still working on a better solution, but it needs time.

5
  • Also a really good answer, both ways do what I wanted to achieve! Thank you very much! Aug 21 '14 at 12:18
  • To the unknown downvoter: Leave a comment why you downvoted this answer
    – user31729
    Sep 4 '14 at 16:43
  • I hope it wasn't me. Some time ago, I accidentely voted one answer down, because I was on my mobile, but I immediately changed the vote to the desired upvote. I can't remember which answer it was and on which question. Sep 8 '14 at 7:39
  • @Benutzer193: Nope, it was a serial downvoter... he has left TeX.SX already or was kicked ;-)
    – user31729
    Sep 8 '14 at 7:41
  • That's good! :) Sep 8 '14 at 7:42
3

Update

Better Version for the lua file, with different probabilies

n = math.random(1,6) 
if n == 6 then tex.sprint("foo1")
elseif n >= 4 then tex.sprint("foo2") 
else tex.sprint("foo3")
end

(example probabilites:
1/6 => foo1
1/3 => foo2
1/2 => foo3
)

Another easier solution with the compromise of fewer options (and only if using Lualatex):

set a new command:

\newcommand{\test}{\directlua{  dofile("test.lua")  }\xspace}

test.lua:

local test = { 'a','b'}
tex.sprint(test[math.random( #test )])

a and b need to be changed the desired text output (more words can be added of course)
and test can be changed to the desired name of the command.


I don't know if or how I can restrict the word repetition like in the other solutions or how to set the average appearance of a word.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.