\documentclass{beamer}
% \documentclass[handout]{beamer}

\usetheme{dcc}

\usepackage[utf8]{inputenc}
\usepackage[american]{babel}
\usepackage{url}

\newcommand{\ie}{i.\,e.}
\newcommand{\Ie}{I.\,e.}
\newcommand{\eg}{e.\,g.}
\newcommand{\Eg}{E.\,g.}

\title{Mineração de Dados Aplicada}
\subtitle{\texttt{awk}}

\author{Loïc Cerf}

\date{April 14th 2025}

\institute
{
  \scriptsize
  DCC -- ICEx -- UFMG
  \bigskip\\
  \includegraphics[width=.334113288\linewidth]{images/dcc}\hfill\includegraphics[width=.5\linewidth]{images/ufmg}
}

\subject{awk}

\keywords{awk}

\hypersetup{pdfpagemode=FullScreen}

\AtBeginSection[]
{
  \begin{frame}<beamer>
    \frametitle{Outline}
    \tableofcontents[currentsection]
  \end{frame}
}

\begin{document}

\begin{frame}[plain]
  \titlepage
\end{frame}

% \begin{frame}
%   \frametitle{Outline for the next sessions}
%   \begin{description}
%   \item[Today] \texttt{awk}'s basics;
%   \item[23/09] \alert<2>{Oral presentation of your data} and \texttt{awk}'s array;
%   \item[25/09] A few words about efficiency.
%   \end{description}

%   \vfill

%   \uncover<3>{Date for the exam on the POSIX text-processing
%     commands?}
% \end{frame}

\begin{frame}
  % \frametitle{Outline for this session}
  \frametitle{Outline}
  \tableofcontents
\end{frame}

\section{Correction of last week exercise}

\begin{frame}
  \frametitle{\only<-2>{Testing the presence}\only<3>{Counting
      occurrences} of keywords}
  \begin{alertblock}{Exercise}
    Given a list of keywords and a text, use the commands presented so
    far to \only<-2>{list every keyword occurring}\only<3>{count the
      occurrences of each keyword} in the text.
  \end{alertblock}

  \vfill
  \pause

  \begin{exampleblock}{Without grep}
    \texttt{sort keywords > keywords.sorted}\\
    \texttt{tr -s [:space:][:punct:] \textbackslash \textbackslash n < text}\\
    \texttt{| tr [:upper:] [:lower:]}\\
    \texttt{| sort \only<-2>{-u}\only<3>{| uniq -c}}\\
    \texttt{| \only<-2>{comm -12}\only<3>{join -2 2} keywords.sorted
      -}
  \end{exampleblock}
\end{frame}

\begin{frame}
  \frametitle{\only<-2>{Counting occurrences}\only<3>{Testing the
      presence} of keywords}
  \begin{exampleblock}{With grep and a shell loop}
    \texttt{tr -s [:space:][:punct:] \textbackslash \textbackslash n < text > words}\\
    \texttt{while read keyword}\\
    \texttt{do grep -ixc \$keyword words}\\
    \texttt{done < keywords | paste -d ' ' keywords -}
  \end{exampleblock}

  \vfill
  \pause

  \begin{exampleblock}{With grep but no shell loop}
    \texttt{grep -iwof keywords text | sort -f | uniq -ci}
  \end{exampleblock}

  \vfill
  \pause

  \begin{exampleblock}{To only list the keywords occuring in the text}
    \texttt{grep -iwof keywords text | sort -uf}
  \end{exampleblock}
\end{frame}

\section{Computational capabilities in the text-processing flow}

\begin{frame}
  \frametitle{Adding computational capabilities}
  \texttt{sed} is suited to select lines and transform them. To
  actually consider these lines as \emph{records} with \emph{fields}
  and possibly do some \emph{numerical computation} on them,
  \texttt{awk} is much more appropriate.

  \vfill
  \pause

  \texttt{awk} is Turing complete and closer than \texttt{sed} to
  general-purpose programming languages (variables, associative
  arrays, Boolean operators, functions, conditional statements, loops,
  etc.).

  \vfill
  \pause

  \texttt{awk} has inspired \texttt{perl}, which is general-purpose
  and very expressive... but also messy, slow and harder to learn.
\end{frame}

\begin{frame}
  \frametitle{Learning \texttt{awk}}
  \begin{enumerate}
  \item The following slides;
  \item Tutorials on the Web;
  \item '\texttt{info awk}';
  \item Books.
  \end{enumerate}

  \vfill
  \pause

  And cheat sheets on the Web...
\end{frame}

\begin{frame}
  \frametitle{\texttt{awk}}
  \texttt{awk}, which is specifically designed to process
  \emph{structured} text, is a mix of \texttt{sed}, the shell and the
  \texttt{C} programming language.
  \begin{description}
  \item[\texttt{sed}] Condition (to select records) followed by an
    action (on these records) and \texttt{sed}-like substitutions.
  \item[Shell] The fields of a record are \texttt{\$1}, \texttt{\$2},
    etc. and actions can use pipes and redirections.
  \item[\texttt{C}] Boolean operators, conditional statements, loops,
    numerical and string-processing functions à la \texttt{C}.
  \end{description}
\end{frame}

\begin{frame}
  \frametitle{A few options}
  The most useful options of \texttt{awk} are \texttt{-f} (like with
  \texttt{sed}, it introduces a script), \texttt{-v} followed by the
  (re)definition of a variable and \texttt{-F} followed by the
  \emph{regexp} separating the fields (one or more spaces/tabs is the
  default), a shorthand for \texttt{-v FS=}\emph{regexp}.
\end{frame}

\begin{frame}
  \frametitle{\texttt{awk} program structure}
  Letting aside the possibility to define functions (with the
  \texttt{function} keyword), which can \texttt{return}, an awk
  program is a sequence of condition-action statements.

  \vfill
  \pause

  Following the order of those statements, input records are
  processed, one by one.  \texttt{next} abandons the current record.
  The next record is processed, starting with the first
  condition-action.
\end{frame}

\begin{frame}
  \frametitle{Conditions and actions}
  Like in \texttt{sed}, the condition can be a regexp between '/' and
  intervals are possible.  Nevertheless, any test that could be in an
  action can be, as well, a condition.  Two ``special'' conditions are
  particularly useful: \texttt{BEGIN} matches the beginning of the
  input (before the first record); \texttt{END} its end (after the
  last record). By default (no condition), every record is matched.

  \vfill
  \pause

  The action is between braces.  It is executed on the records
  satisfying the condition.  By default (a condition but no action),
  every record satisfying the condition is printed.
\end{frame}

\begin{frame}[containsverbatim]
  \frametitle{A simple example}
  Scott Pakin. Regular Expressions and Gender Guessing. In
  \emph{Computer Language Magazine}, 8(12):59--68, December 1991:
\begin{verbatim}                                   { sex = "m" }
# Female names ending in a/e/i/y.
/^.*[aeiy]$/                       { sex = "f" }
# Allison (and variations)
/^All?[iy]((ss?)|z)on$/            { sex = "f" }\end{verbatim}
\begin{tabular}{cc}
  \vdots & (a total of 41 regexps)
\end{tabular}
\begin{verbatim}# Pete, Serge, Shane, ...
/^[^BG](e[rst]|ha)[^il]*e$/        { sex = "m" }
# Angel, Gail, Isabel, ...
/^[ADFGIM][^r]*([bg]e[lr]|il|wn)$/ { sex = "f" }
                                   { print sex }\end{verbatim}
\end{frame}

\begin{frame}
  \frametitle{Some predefined variables}
  By default, records are lines and one or more space/tabulation
  separates its fields.  Redefining the \texttt{RS}/\texttt{FS}
  variable changes the input record/field separator.  Input separators
  can be any regexp.  If \texttt{RS} is \texttt{""}, blank lines
  separate records.

  \vfill
  \pause

  When \texttt{print}ing, every '\texttt{,}' inserts \texttt{OFS} and
  \texttt{ORS} is appended at the end.  \texttt{ORS}/\texttt{OFS} are
  \texttt{"\textbackslash n"}/\texttt{" "} by default but can be
  redefined.

  \vfill
  \pause

  \texttt{ARGC} is the number of files in arguments, \texttt{ARGV}
  their names, \texttt{FILENAME} that of the currently processed file,
  \texttt{NR} the current record number (and \texttt{FNR} is reset for
  each file).  That record is \texttt{\$0}. It has \texttt{NF} fields:
  \texttt{\$1}, \texttt{\$2}, \ldots, \texttt{\$NF} (literally, \ie,
  the last field can be processed without knowing its number).
\end{frame}

\begin{frame}
  \frametitle{Instructions à la \texttt{C}}
  \begin{description}
  \item[Numerical computation] Numerical constants/variables, which
    can be compared, operated, incremented, decremented, and
    functions: \texttt{sqrt}, \texttt{exp}, \texttt{log},
    trigonometry, \texttt{int} to truncate to integer, \texttt{rand}
    for RNG in $[0, 1)$.
  \item[Boolean operations] \texttt{\&\&}, \texttt{||}, \texttt{!},
    etc.
  \item[Control statements] \texttt{if}, \texttt{for}, \texttt{while},
    \texttt{break}, \texttt{continue}, \texttt{exit}, etc.
  \end{description}

  \vfill
  \pause

  Like in \texttt{C}, '\texttt{;}' separates the instructions (not
  required if on different lines), blocks are between '\texttt{\{}'
  and '\texttt{\}}', strings are between '\texttt{"}'s, '\texttt{=}'
  attributes a value (on the right) to a variable (on the left), etc.

  \vfill
  \pause

  Unlike in \texttt{C}, variables need not be declared.  They are
  dynamically initialized: at \texttt{0} if a number is needed; at
  \texttt{""} otherwise.
\end{frame}

\begin{frame}
  \frametitle{Getting the data}
  \texttt{wget --no-check-certificate -O - \textbackslash{}\\
    \url{dcc.ufmg.br/~lcerf/data.tar.xz} | tar -xJ}
\end{frame}

\begin{frame}
  \frametitle{Exercises}
  \begin{exampleblock}{Exercises}
    From \texttt{brasileirão/retweets.txt}:
    \begin{enumerate}
    \item Print the fields in the opposite order (user first) and
      transform every number of retweets $t$ into $\frac{1}{1 + e^{0.2
          (20 - t)}}$.
    \item In these logs, how many times was \texttt{milton\_neves}
      retweeted?
    \item The log starts at the end of 2010 and ends at the beginning
      of 2011.  In 2010, every week number was moved back by one week
      in the past. Correct this error.
    \item List the distinct users who were retweeted at least 20 times
      when writing about a specific team during a specific week (you
      are allowed to process \texttt{awk}'s output with
      \texttt{sort}).
    \end{enumerate}
  \end{exampleblock}
\end{frame}

\section{Associative arrays}

\begin{frame}
  \frametitle{Associative arrays}
  \texttt{array[key]} is the value that \texttt{array} associates with
  \texttt{key} (multidimensional with '\texttt{,}').  There is no
  simpler collection.

  \vfill
  \pause

  \texttt{variable in array} checks whether \texttt{variable} is a key
  in \texttt{array}, except in a \texttt{for} statement (\ie,
  \texttt{for (variable in array)}), where \texttt{variable} is,
  iteration after iteration, every \texttt{key} in \texttt{array}.

  \vfill
  \pause

  \texttt{delete array[key]} removes \texttt{key} and the associated
  value.
\end{frame}

\begin{frame}
  \frametitle{Exercises}
  \begin{exampleblock}{Exercises}
    From \texttt{brasileirão/retweets.txt}:
    \begin{enumerate}
    \item List the distinct users who were retweeted at least 20 times
      when writing about a specific team during a specific week (you
      are \emph{not} allowed to use \texttt{sort}!).
    \item Compute the total numbers of retweets per team.
    \end{enumerate}
  \end{exampleblock}
\end{frame}

\begin{frame}
  \frametitle{Summary of \texttt{awk}'s basics}
  An \texttt{awk} script:
  \begin{itemize}
  \item is a sequence of condition-action statements;
  \item sequentially processes records having fields;
  \item can use variables that are implicitly initialized;
  \item can do Boolean and numerical operations;
  \item can use control statements with a \texttt{C}-like syntax;
  \item can associate values to keys and no simpler collection exists.
  \end{itemize}
\end{frame}

% \section{Exercises for next Wednesday}
\section{Correction of last session exercise}

\begin{frame}
  \frametitle{Exercises}
  % \begin{alertblock}{Exercises for next Wednesday}
  \begin{alertblock}{Exercises for today}
    \begin{enumerate}
    \item List, in a file named \texttt{distros}, the distributions
      that appear in \texttt{DistroWatch/*/}. \texttt{info ls} says:
      ``List one file per line.  This is the default for ‘ls’ when
      standard output is not a terminal.''.
    \item \texttt{wget -B http://distrowatch.com/ -i distros -P ../dw}
      downloaded \texttt{DistroWatch.com}'s HTML descriptions of those
      distributions, including their countries of origin.  Associate
      every distribution (first column) with its country of origin
      (second column).  Note: there exists a solution without
      \texttt{sed} or \texttt{awk}.
    \end{enumerate}
  \end{alertblock}
\end{frame}

\begin{frame}
  \frametitle{Downloading the distribution pages}
  \begin{exampleblock}{Listing the distributions using \texttt{ls
        */*}}
    \texttt{ls */* | cut -d / -f 2 | grep -v INDEX}\\
    \texttt{| sort -uo distros}
  \end{exampleblock}

  \vfill
  \pause

  \begin{exampleblock}{Listing the distributions using \texttt{ls */}}
    \texttt{ls */ | grep -ve INDEX -e / -e '\^{}\$'}\\
    \texttt{| sort -uo distros}
  \end{exampleblock}

  \vfill
  \pause

  \begin{exampleblock}{Downloading the distribution pages}
    \texttt{wget -B http://distrowatch.com/ -i distros -P
      ../dw\_pages}
  \end{exampleblock}
\end{frame}

\begin{frame}
  \frametitle{The origins of the distributions}
  \begin{exampleblock}{With \texttt{sed}\only<3->{ \alert{(0.13s)}}}
    \texttt{sed -n
      's/.*?origin=\textbackslash(.*\textbackslash)\#.*/\textbackslash1/p'
      ../dw\_pages/* | paste distros -}
  \end{exampleblock}

  \vfill
  \pause

  \begin{exampleblock}{With \texttt{sed}\only<3->{ \alert{(0.10s)}}}
    \texttt{sed -n
      '/?origin=/
      s/.*origin=\textbackslash(.*\textbackslash)\#.*/\textbackslash1/p'
      ../dw\_pages/* | paste distros -}
  \end{exampleblock}

  \vfill

  \uncover<4->{
    \begin{exampleblock}{Without \texttt{sed}
        \alert{(0.0\only<4>{4}\only<5>{3}s)}}
      \texttt{grep -o\only<5>{\alert{m 1}} 'origin=[\^{}\#]*'
        ../dw\_pages/* |}\\
      \texttt{cut -d = -f 2 | paste distros -}
    \end{exampleblock}}
\end{frame}

\section{String processing}

\begin{frame}
  \frametitle{Built-in string functions}
  Classics from \texttt{C}: \texttt{printf} and \texttt{sprintf}.

  \vfill
  \pause

  Simple predefined functions: \texttt{length} (of \texttt{\$0} if no
  argument is given), \texttt{tolower}, \texttt{toupper},
  \texttt{index} (which starts at 1; 0 if not found) of the first
  occurrence of a substring, \texttt{substr} (using positions, which
  start at 1).  A space between strings concatenates.

  \vfill
  \pause

  More complex ones: \texttt{split} (a string into an array indexed
  from 1 to the returned length; any regexp can be a delimiter),
  \texttt{match} (part of a string with a regexp and returns where the
  first match is).

  \vfill
  \pause

  The operator \texttt{\~} (resp.  \texttt{!\~}) checks whether
  (resp. no) part of the string (on the left) matches the regexp (on
  the right, between '\texttt{/}').
\end{frame}

\begin{frame}
  \frametitle{\texttt{sed}'s substitution power!}
  \begin{itemize}
  \item \texttt{gensub(/regexp/, replacement, flag[, str])} performs
    like \texttt{sed 's/regexp/replacement/flag'} on \texttt{str} (by
    default \texttt{\$0}).
  \item \texttt{sub(/regexp/, replacement[, str])} performs like
    \texttt{gensub(/regexp/, replacement, 1[, str])}.
  \item \texttt{gsub(/regexp/, replacement[, str])} performs like
    \texttt{gensub(/regexp/, replacement, "g"[, str])}.
  \end{itemize}

  \vfill

  \texttt{sub} and \texttt{gsub} modify (or not) \texttt{str} and
  return 1 (or 0).  \texttt{gensub} only exists in GNU Awk.  It does
  not alter \texttt{str}, returns the modified string, and can use
  \texttt{\textbackslash\textbackslash n} in \texttt{replacement} for
  the string matching the part of the regexp between the
  \texttt{n}$^{\text{th}}$ \texttt{( )}.
\end{frame}

\begin{frame}
  \frametitle{Exercises}
  \begin{exampleblock}{Exercise}
    Processing a data table with AWK, one would like to use the names
    of the fields in the first input line rather than their numbers,
    \eg, processing \texttt{bears.csv}, \texttt{\$NECK} instead of
    \texttt{\$6}.  Write \texttt{field\_names.awk}, which outputs the
    variable assignments relating to the first line, \eg, \texttt{ -v
      AGE=1 -v MONTH=2 -v SEX=3 (\dots) -v CHEST=8 -v WEIGHT=9} if
    given \texttt{bears.csv}.  Turn every non-alphanumeric character
    in the names into ``\_''.
  \end{exampleblock}

  \vfill

  One can then call that shell script with three or more arguments:\\
  \texttt{fs="\$1"\\
    shift\\
    awk -F "\$fs"\$(./field\_names.awk -F "\$fs" "\$2") "\$@"}
\end{frame}

\begin{frame}
  \frametitle{Exercises}
  \begin{exampleblock}{Exercises}
    Given a list of \texttt{keywords} and a \texttt{text}, use
    \texttt{awk} to count the occurrences of each keyword in the text:
    \begin{enumerate}
      \only<-2>{\item[1.] from the output of \texttt{grep -iwof
          keywords text};}
      \only<1,3->{\item[2.] only using \texttt{awk}.}
    \end{enumerate}
  \end{exampleblock}

  \vfill

  \uncover<2->{
    \only<-2>{
      \begin{exampleblock}{Counting preselected keywords with awk}
        \texttt{grep -iwof keywords text |}\\
        \texttt{awk '\{ ++words[tolower(\$0)] \}}\\
        \texttt{END \{ for (w in words) print w, words[w] \}'}
      \end{exampleblock}
    }\only<3>{
      \begin{exampleblock}{Doing everything with awk}
        \texttt{awk -v RS=[[:space:][:punct:]]+ '}\\
        \texttt{FNR == 1 \{ ++argind \}}\\
        \texttt{argind == 1 \{ words[\$0] = 0 \}}\\
        \texttt{argind == 2 \{ w = tolower(\$0)}\\
        \texttt{if (w in words) ++words[w] \}}\\
        \texttt{END \{ for (w in words) print w, words[w] \}' keywords
          text}
      \end{exampleblock}
    }\only<4>{
      \begin{exampleblock}{Doing everything with \alert{g}awk}
        \texttt{awk -v RS=[[:space:][:punct:]]+ '}\\
        \texttt{ARGIND == 1 \{ words[\$0] = 0 \}}\\
        \texttt{ARGIND == 2 \{ w = tolower(\$0)}\\
        \texttt{if (w in words) ++words[w] \}}\\
        \texttt{END \{ for (w in words) print w, words[w] \}' keywords
          text}
      \end{exampleblock}
    }
  }
\end{frame}

\begin{frame}
  \frametitle{GNU extensions}
  Using \texttt{gawk} (GNU Awk), many useful extensions are available:
  \begin{itemize}[<+->]
  \item the more powerful \texttt{gensub} substitution;
  \item \texttt{ARGIND} giving the index in \texttt{ARGV} of the
    current file;
  \item \texttt{FS} can be \texttt{""} to split between characters;
  \item \texttt{FPAT} specifies the fields rather than their
    separation;
  \item the \texttt{asorti}/\texttt{asort} function sorts an array
    w.r.t. its keys/values (the information that is not sorted is lost
    and the keys become the sequential integers, starting at 1; the
    length is returned);
  \item \texttt{delete} and \texttt{length} of an array;
  \item etc.
  \end{itemize}
\end{frame}

\begin{frame}
  \frametitle{That's (almost) all folks!}
  These slides presented almost all the language!

  \vfill

  They did not discuss the \texttt{getline} function, system calls,
  piping, redirecting and closing I/O.  Those features are more rarely
  used.
\end{frame}

% \section{Exercises for next Wednesday}
\section{Correction of last week exercises}

\begin{frame}
  \frametitle{Exercises}
  \begin{alertblock}{Exercises for today}
    The teams have very different total numbers of retweets.  A
    normalization w.r.t. the ``popularity'' is wished.  Let
    $\mu_{\mathtt{team}}$ the total number of retweets for a specific
    \texttt{team} and $\mu$ their arithmetic mean over all teams.
    Multiply every number of retweets by
    $\frac{\mu}{\mu_{\mathtt{team}}}$, where \texttt{team} is the team
    the number of retweets relates to.  You can proceed in two steps:
    \begin{enumerate}
    \item From \texttt{retweets.txt}, list every \texttt{team} id
      ($200$, $201$, \dots, $219$) along with the coefficient
      $\frac{\mu}{\mu_{\mathtt{team}}}$ in a file named
      \texttt{coeff}.
    \item Write an \texttt{awk} program that takes both \texttt{coeff}
      and \texttt{retweets.txt} as arguments and applies the
      coefficients.
    \end{enumerate}
    Hint: understanding the solutions to the exercises on the two
    previous slides helps.
  \end{alertblock}
\end{frame}

\begin{frame}
  \frametitle{Two-step per-team normalization}
  \begin{exampleblock}{Computing the coefficients
      (\only<-2>{POSIX-compliant}\only<3>{using \texttt{gawk} or
        \texttt{mawk}})}
    \texttt{awk '\{ teams[\$2] += \$1 \}}\\
    \only<-2>{\texttt{END \{ for (t in teams) \{ ++nb; avg +=
        teams[t] \}}\\
      \texttt{avg /= nb}\\}
    \only<3>{\texttt{END \{ for (t in teams) avg += teams[t]}\\
      \texttt{avg /= length(teams)}\\}
    \texttt{for (t in teams) print t, avg / teams[t] \}'}\\
    \texttt{retweets.txt > coeff}
  \end{exampleblock}

  \vfill
  \pause

  \begin{exampleblock}{Applying the coefficients
      (\only<-2>{POSIX-compliant}\only<3>{using \texttt{gawk}})}
    \only<-2>{\texttt{awk 'FNR == 1 \{ ++argind \}}\\
      \texttt{argind == 1 \{ coeff[\$1] = \$2 \}}\\
      \texttt{argind == 2 \{ \$1 *= coeff[\$2]; print \}'}\\}
    \only<3>{\texttt{awk '}\\
      \texttt{ARGIND == 1 \{ coeff[\$1] = \$2 \}}\\
      \texttt{ARGIND == 2 \{ \$1 *= coeff[\$2]; print \}'}\\}
    \texttt{coeff retweets.txt}
  \end{exampleblock}
\end{frame}

\begin{frame}
  \frametitle{One-step per-team normalization}
  \begin{exampleblock}{Per-team normalization
      (\only<1>{POSIX-compliant}\only<2>{using \texttt{gawk} or
        \texttt{mawk}})}
    \texttt{awk 'BEGIN \{}\\
    \texttt{ while (getline < ARGV[1]) coeff[\$2] += \$1}\\
    \only<1>{\texttt{for (t in coeff) \{ ++nb; avg += coeff[t] \}}\\
      \texttt{avg /= nb}\\}
    \only<2>{\texttt{for (t in coeff) avg += coeff[t]}\\
      \texttt{avg /= length(teams)}\\}
    \texttt{for (t in coeff)}\\
    \texttt{coeff[t] = avg / coeff[t] \}}\\
    \texttt{\{ \$1 *= coeff[\$2]}\\
    \texttt{print \}' retweets.txt}
  \end{exampleblock}
\end{frame}

\section*{}

\begin{frame}
  \frametitle{TapTempo}
  The TapTempo C++ program measures the BPM of a music whose beat is
  tapped on [Enter].  The $k^{\text{th}}$ time it is tapped:
  \begin{itemize}
  \item nothing is output if $k = 1$;
  \item ``Tempo : $x$ bpm'' is output otherwise.
  \end{itemize}
  $x$ is the integer part of the BPM computed from:
  \begin{itemize}
  \item the first and the $k^{\text{th}}$ times the key was tapped,
    if $k \leq 5$;
  \item the $(k-4)^{\text{th}}$ and the $k^{\text{th}}$ times the
    key was tapped, otherwise.
  \end{itemize}
  After 5 seconds with no tapping, reset $k$ to 0.
\end{frame}

\begin{frame}
  \frametitle{Challenge: shortest TapTempo program}
  \begin{block}{A 131-character Perl script, but no reset and no
      ``Tempo''}
    % perl -M'Time::HiRes qw/gettimeofday/' -ne '$t[@t]+=gettimeofday; shift @t if @t>5; printf "%3.0f bpm",60*$#t/($t[-1]-$t[0]) if $#t'
    \texttt{perl -M'Time::HiRes qw/gettimeofday/' -ne
      '\$t[@t]+=gettimeofday; shift @t if @t>5; printf "\%3.0f
      \%bpm",60*\$\#t/(\$t[-1]-\$t[0]) if \$\#t'}
  \end{block}

  \vfill
  \pause

  \begin{alertblock}{A 100-character command line with \texttt{ts} and
      AWK}
    % ts %.s|awk '$1-t[k]>5{k=r=0}{t[++k]=$1}k>5{r=k-5}k>1{printf"Tempo : %i bpm",60*(k-r-1)/($1-t[r+1])}'
    \texttt{ts \%.s|awk
      '\$1-t[k]>5\{k=r=0\}\{t[++k]=\$1\}k>5\{r=k-5\}}
    \texttt{k>1\{printf"Tempo : \%i bpm",60*(k-r-1)/(\$1-t[r+1])\}'}
  \end{alertblock}
\end{frame}

\section*{}

\begin{frame}
  \frametitle{License}
  \begin{block}{\copyright 2012--2025 Loïc Cerf}
    \includegraphics[height=1.5ex]{img/licenses/cc-by-sa}\hskip1pt
    These slides are licensed under the Creative Commons
    Attribution-ShareAlike 4.0 International License.
  \end{block}
\end{frame}

\end{document}