7 min read

Langford problem

Table of Contents

The Langford Problem

In combinatorial mathematics, a Langford pairing\textbf{Langford pairing}, also called a Langford sequence\textbf{Langford sequence}, is a permutation of the sequence of 2n2n numbers 1,1,2,2,...,n,n1, 1, 2, 2, ..., n, n in which the two 11s are one unit apart, the two 22s are two units apart, and more generally the two copies of each number kk are kk units apart. Langford pairings are named after C. Dudley Langford, who posed the problem of constructing them in 1958. Langford’s problem\textbf{Langford's problem} is the task of finding Langford pairings L(n)L(n) for a given value of nn.

Solution using Constraint Programming

Let (xis,xie)(x_{is}, x_{ie}) be the tuple of decision variables indicating the starting and ending position of number ii in the Langford sequence. The decision variables need to satisfy the following constraints:

1≤xis,xie≤2n,1≤i≤nxie=xis+(i+1),1≤i≤nAll members of the set xit∣1≤i≤n,t∈s,e are different.1 \leq x_{is}, x_{ie} \leq 2n , 1 \leq i \leq n \\\\ x_{ie} = x_{is} + (i + 1) , 1 \leq i \leq n \\\\ \text{All members of the set $\\{x_{it} | 1 \leq i \leq n, t \in {s, e} \\}$ are different}.

Here is the Python code implementing the above model using the fantastic Google OR-Tools\textbf{OR-Tools} library:

from ortools.sat.python import cp_model
from collections import defaultdict

def langford_seq_checker(seq):
    if not seq:
        return False
    pos_map = defaultdict(list)
    for i, n in enumerate(seq):
        pos_map[n].append(i)
    for n,p in pos_map.items():
        if len(p) != 2:
            return False
        if (p[1] - p[0]) != n + 1:
            return False
    return True

def langford_cp(n):
    model = cp_model.CpModel()
    x = [[model.NewIntVar(1, 2*n, 'x[%i][%i]' % (i,j)) for j in range(2)] for i in range(n)]
    
    model.AddAllDifferent([x[i][j] for i in range(n) for j in range(2)])
    for i in range(n):
        model.Add(x[i][1] - x[i][0] == i+2)

    solver = cp_model.CpSolver()
    status = solver.Solve(model)
    if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE:
        out = [0]*(2*n+1)
        for i in range(n):
            for j in range(2):
                out[solver.Value(x[i][j])] = i+1
        return out[1:]
    else:
        return None

Here is the sequence L(100)L(100) calculated using the above code:

[51, 79, 80, 82, 1, 30, 1, 64, 70, 87, 95, 50, 21, 66, 33, 4, 29, 97, 20, 15, 4, 69, 22, 52, 59, 28, 100, 81, 46, 26, 36, 57, 49, 8, 21, 15, 30, 91, 31, 20, 83, 86, 8, 34, 23, 22, 29, 68, 33, 40, 53, 14, 51, 72, 28, 84, 26, 74, 89, 63, 32, 55, 50, 75, 98, 76, 14, 36, 23, 7, 31, 48, 64, 93, 96, 46, 52, 7, 34, 70, 66, 79, 49, 80, 59, 65, 82, 92, 71, 57, 40, 69, 94, 32, 99, 78, 61, 87, 67, 90, 6, 43, 62, 88, 53, 19, 95, 6, 60, 81, 35, 77, 24, 85, 73, 97, 68, 55, 56, 11, 48, 54, 58, 63, 83, 19, 72, 100, 86, 91, 25, 11, 74, 13, 27, 47, 17, 24, 45, 75, 84, 10, 76, 38, 41, 43, 35, 13, 89, 9, 44, 65, 10, 42, 17, 37, 25, 39, 61, 9, 71, 16, 27, 98, 12, 62, 67, 93, 3, 60, 2, 96, 3, 2, 78, 56, 54, 12, 16, 18, 92, 58, 38, 47, 45, 5, 41, 94, 73, 77, 90, 5, 88, 37, 99, 44, 42, 39, 18, 85]

Solution using Integer Programming

Another way to solve the Langford problem is to treat it as a set covering problem. To visualize this we make use of the following array for L(3)L(3):

-123456
111
211
311
411
522
622
722
833
933

To solve the problem, we need to select one row for the 11‘s in the sequence, one row for the 22‘s and one row for the 33‘s, such that if we stack these rows on top of each other, no column contains more than one number.

In case of L(n)L(n) it is easy to see that the number of columns in the matrix will be 2n2n and the number of rows will be r=2n−2+...+n−1r = 2n-2 + ... + n-1. Let {xi∣1≤i≤r}\{x_i|1 \leq i \leq r\} be the set of decision variables, one for each row in the matrix such that xi∈{0,1}x_i \in \{0,1\}. We have the following constraints:

  1. We choose only 11 row among all rows containing the number kk in the matrix where 1≤k≤n1 \leq k \leq n.
  2. For each column, we choose only 11 row among all rows containing non zero values.

The Python code implementing the above model using the Google OR-Tools\textbf{OR-Tools} library is given below:

from ortools.linear_solver import pywraplp

def langford_ip(n):
    solver = pywraplp.Solver.CreateSolver('SCIP')

    n_rows, n_cols = sum(range(n-1, 2*n-1)), 2*n
    matrix = [[0 for j in range(n_cols)] for i in range(n_rows)] 
    out = [0 for i in range(n_cols)]

    # setting up the covering matrix
    j = 0
    for i in range(n):
        for k in range(2*n-i-2):
            matrix[j][k] = i + 1
            matrix[j][k+i+2] = i + 1
            j += 1
    
    x = [solver.IntVar(0, 1, 'x[%i]' % j) for j in range(n_rows)]

    # row constraints
    j = 0
    for i in range(n):
        solver.Add(sum([x[k] for k in range(j, j + 2*n-i-2)])==1)
        j += 2*n-i-2
    
    # column constraints
    for i in range(n_cols):
        inds = []
        for j in range(n_rows):
            if matrix[j][i]:
                inds.append(j)
        solver.Add(sum([x[k] for k in inds])==1)

    solver.Minimize(sum([x[i] for i in  range(n_rows)]))

    status = solver.Solve()
    if status == pywraplp.Solver.OPTIMAL:
        for i in range(n_rows):
            if x[i].solution_value():
                for j in range(n_cols):
                    out[j] += matrix[i][j]
        return out
    else:
        return None

Beautiful analytical solution

For the positive cases (n=4kn = 4k or 4k+34k+3) an algorithm for calculating the sequence can be found here. This beautiful algorithm was discovered by Roy Davies in 19591959.

Here are the details, where RR denotes the reversal of a sequence.

x=Ceiling[n/4]{a,b,c,d}={2x−1,4x−2,4x−1,4x}p=odds in [1,a−1]q=evens in [2,a−1]r=odds in [a+2,b−1]s=evens in [a+1,b−1]x = Ceiling[n/4] \\\\ \{a, b, c, d\} = \{2x-1, 4x-2, 4x-1, 4x\} \\\\ p = \text{odds in $[1, a-1]$} \\\\ q = \text{evens in $[2, a-1]$} \\\\ r = \text{odds in $[a+2, b-1]$} \\\\ s = \text{evens in $[a+1, b-1]$}

If 44 divides nn, the sequence is {R[s],R[p],b,p,c,s,d,R[r],R[q],b,a,q,c,r,a,d}\{R[s], R[p], b, p, c, s, d, R[r], R[q], b, a, q, c, r, a, d\}.

If n≡3(mod  4)n \equiv 3 (\mod 4), it is {R[s],R[p],b,p,c,s,a,R[r],R[q],b,a,q,c,r}\{R[s], R[p], b, p, c, s, a, R[r], R[q], b, a, q, c, r\}.

The Python code implementing the above algorithm is given below

from math import ceil

def R(l):
    return list(reversed(l))

def langford_davies(n):
    x = ceil(n/4)
    a, b, c, d = 2*x-1, 4*x-2, 4*x-1, 4*x
    p = [i for i in range(1, a) if i % 2==1] 
    q = [i for i in range(2, a) if i % 2==0] 
    r = [i for i in range(a+2, b) if i % 2==1] 
    s = [i for i in range(a+1, b) if i % 2==0] 
    if n%4 == 0:
        return R(s) + R(p) + [b] + p + [c] + s + [d] + R(r) + R(q) + [b,a] + q + [c]  + r + [a, d]
    if n%4 == 3:
        return R(s) + R(p) + [b] + p + [c] + s + [a] + R(r) + R(q) + [b,a] + q + [c]  + r 
    return None