--- title: "Haskell implementation of the Robinson-Schensted algorithm" date: "2016-08-07" --- ```{r setup, include=FALSE} # use highlight: pygments, prettify=FALSE ``` Given a permutation $\sigma$ of $\{1, \ldots, n\}$, the Robinson-Schensted correspondence associates to $\sigma$ a pair $(P,Q)$ of two standard Young tableaux of weight $n$ having the same shape. This map $RS\colon \sigma \mapsto (P,Q)$ is obtained by the Schensted algorithm, and we give an Haskell implementation of this algorithm below (I gave a R implementation of this map in [a previous article](http://stla.github.io/stlapblog/posts/YoungTableaux.html#robinson-schensted-correspondence)). A nice explanation of this algorithm is given in [R. T. Bayley's master thesis](https://www.google.be/url?sa=t&rct=j&q=&esrc=s&source=web&cd=1&cad=rja&uact=8&ved=0ahUKEwiOx5X-hK_OAhXkDsAKHX9wA8kQFggbMAA&url=http%3A%2F%2Fwww.maths.qmul.ac.uk%2F~rtb%2Fmathpage%2FRichard%2520Bayley%27s%2520Homepage_files%2Frichardmmath.pdf&usg=AFQjCNE7yebCMY2jwykMkBqF1BKhaYiEXg&sig2=gOJekMgyDQx5af2HUcyltQ&bvm=bv.129391328,d.d24), to which we refer for more details, if needed. In Haskell, we represent a permutation as a list of integers and a Young tableau as a list of lists of integers. For example, the Haskell list `[[1,2,4,7,8], [3,5,6,9], [10]]` represents this (standard) Young tableau: a Young tableau The main part of the algorithm giving the pair $(P,Q)=RS(\sigma)$ is the construction of the first Young tableau $P$. Given the permutation $\sigma=(\sigma_1, \ldots, \sigma_n)$, the standard Young tableau $P$ is the final Young tableau $P_n$ of the sequence $(P_0, \ldots, P_n)$ of Young tableaux which is recursively obtained as follows. One starts with the empty Young tableau $P_0=[\,]$ and one recursively defines $P_{k+1}$ as the Young tableau obtained by *bumping* $\sigma_{k+1}$ to $P_k$, an action to be explained below. This is denoted by $P_{k+1} = P_k \leftarrow \sigma_{k+1}$, so that the final Young tableau is $$ P_n = \Bigl(\cdots\bigl(([\,] \leftarrow \sigma_1) \leftarrow \sigma_2\bigr) \cdots \leftarrow \sigma_n\Bigr). $$ Now we explain the *bumping* action. The *bumping* action of an integer $e$ to a Young tableau $P$ is a Young tableau denoted by $P \leftarrow e$ which is obtained by this algorithm: - if $P$ is empty then $P \leftarrow e = [[e]]$; - if $e$ is strictly greater than all the numbers in the first row of $P$, then the Young tableau $P \leftarrow e$ is obtained from $P$ by placing $e$ at the end of its first row; - otherwise, $P \leftarrow e$ is the Young tableau whose first row is obtained by replacing the first element $w$ of the first row of $P$ that is larger than $e$ with $e$, and by stacking this new row on the top of $P_{-1} \leftarrow w$, where $P_{-1}$ is the Young tableau obtained from $P$ by removing its first row. Thus, the shape of $P \leftarrow e$ is the shape of $P$ plus an additional square. Consider for example the permutation $\sigma = (2,3,1)$. The action of $\sigma_1=2$ to the empty tableau $P_0=[\,]$ generates the tableau $P_1 := P_0 \leftarrow \sigma_1 = [[2]]$. The action of $\sigma_2=3$ to the tableau $P_1$ generates the tableau $P_2 := P_1 \leftarrow \sigma_2 = [[2,3]]$. The action of $\sigma_3=1$ to the tableau $P_2$ generates the tableau $P_3 := P_2 \leftarrow \sigma_3 = [[1,3], [2]]$. The `replace` function below performs the replacement. It takes as arguments a list of integers `xs` and an integer `e`. It finds the index of the first element of `xs` greater than `e`, replaces this element with `e`, and returns the new list and the replaced element in a pair. ```haskell import Control.Lens import Data.List let replace :: [Int] -> Int -> ([Int], Int); replace xs e = ((element i .~ e) xs, xs !! i) where i = (\(Just x) -> x) (findIndex (>= e) xs) ``` For example: ```haskell > replace [1, 3, 5, 6] 4 ([1,3,4,6],5) ``` This function does not work if there is no element greater `e` in the list. Now, the Young tableau $Q$ is the final Young tableau $Q_n$ of the sequence $(Q_0, \ldots, Q_n)$ constructed in parallel as follows. This sequence starts with the empty Young tableau $Q_0 = [\,]$. Each Young tableau $Q_k$ has the same shape as $P_k$. The Young tableau $Q_k$ is obtained by adding a square to $Q_{k-1}$ at the same location of the square which is added to $P_{k-1}$ to get $P_k$, and by putting the integer $k$ in this square. For the above example where $\sigma=(2,3,1)$, this gives $Q_1=[[1]]$, $Q_2=[[1,2]]$ and $Q_3=[[1,2], [3]]$. Finally, the Schensted algorithm has the following form:
*given a permutation $\sigma=(\sigma_1, \ldots, \sigma_n)$, do:*
* $(P_0, Q_0) \leftarrow ([\,],[\,])$ * for $i$ in $1:n$ + $(P_i, Q_i) \leftarrow \text{bump}(P_{i-1}, Q_{i-1}, \sigma_i, i)$
Therefore, once we get an implementation of the `bump` function, the $RS$ map is implemented as follows in Haskell: ```haskell let rs :: [Int] -> ([[Int]],[[Int]]); rs sigma | (n == 1) = bump [] [] (sigma !! 0) 1 | otherwise = bump p q (last sigma) n where (p,q) = rs (fst (splitAt (n-1) sigma)) n = length sigma ``` It remains to write the `bump` function. ```haskell let bump :: [[Int]] -> [[Int]] -> Int -> Int -> ([[Int]],[[Int]]); bump p q e i = if p==[] then ([[e]], [[i]]) else if e > (last (p !! 0)) then (((p1 !! 0) ++ [e]) : pend, ((q1 !! 0) ++ [i]) : qend) else (newp1 : p2, (q !! 0) : q2) where (p1, pend) = splitAt 1 p (q1, qend) = splitAt 1 q (newp1, w) = replace (p !! 0) e (p2, q2) = bump (drop 1 p) (drop 1 q) w i ``` As an example: ```haskell > rs [1, 3, 6, 4, 7, 5, 2] ([[1,2,4,5],[3,7],[6]],[[1,2,3,5],[4,6],[7]]) ```