Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Solving LinkedIn Zip by Linear Optimization

Zip Banner

How to Play Zip

Figure 1:Example of a Zip game board (Source: LinkedIn Zip)

The board of Zip is made up with some numbered squares and walls (walls are optional).

Objective
To trace a single path that runs through all the squares on the board.
Rule
The path must move through numbered squares in ascending order, starting from square number 1 to the one with the highest number.

Problem Modeling

The best way to solve the game is to consider the squares as nodes in a directed graph (digraph), where each node has a set of pairs of edges that arrive at and depart from the nodes, as shown in the following figure.

Zip game modeled on a directed graph. Source: created by the author

Figure 2:Zip game modeled on a directed graph. Source: created by the author

Considering this graph model, it was enacted the Abstract LO Model by defining its following components:

  • Ranges

  • Sets

  • Decision variables

  • Parameter

  • Objective function

  • Constraints

Ranges

To properly formulate the LO model, we will start from the premise that the squares on the board will be the Cartesian product of the ranges II of rows and JJ of columns present on the board. In addition, it is necessary to have defined an interval KK for the set of numbered squares.

I={1,,n}I = \{1, \cdots, n\}
The row range, where nn is the total number of rows (in this case, n=6n = 6)
J={1,,m}J = \{1, \cdots, m\}
The column range, where mm is the total number of columns (in this case, m=6m = 6 as well).

Sets

The definition of the sets will be more than fundamental to reducing the complexity of the model, as we will avoid having to deal with numerous exceptions. From the ranges determined previously, we can define the set of vertices (VV) and edges (EE) existing in the network logic. In addition to these, it is also necessary to consider the subsets of numbered vertices (NN) and blocked edges (WW), since, in some instances of Zip game, there may be walls between two squares that prevent the path from being traced between them.

V={(i,j)iI,jJ}V = \{(i,j) \mid \forall i \in I, \forall j \in J \}
Set of all squares (vertices). There must be at least 2 vertices in the grid, as it is only possible to trace a path with an initial and a final square. Therefore, V2|V| \ge 2
E={((i,j),(i+1,j))(i,j)V,i+1I}{((i,j),(i1,j))(i,j)V,i1I}{((i,j),(i,j+1))(i,j)V,j+1J}{((i,j),(i,j1))(i,j)V,j1J}E = \begin{array}{lr} \{((i,j), (i+1,j)) \mid (i,j) \in V, i + 1 \in I \} & \cup \\ \{((i,j), (i-1,j)) \mid (i,j) \in V, i - 1 \in I \} & \cup \\ \{((i,j), (i,j+1)) \mid (i,j) \in V, j + 1 \in J \} & \cup \\ \{((i,j), (i,j-1)) \mid (i,j) \in V, j - 1 \in J \} & \end{array}
Set of all edges present in the graph. Its definition is a bit more complex as it is necessary to clarify that, for each vertex, there will only be edges between neighbors immediately to the left, immediately to the right, immediately above, and immediately below it, if they exist.
KVK \subseteq V
Set of numbered squares, which will consist of a set of all vertices indexed by a number on board game.
WEW \subset E
Set of blocked edges (walls) that may appear on the grid.

Decision Variables

By using the network logic already presented, it is possible to define the edges as binary decision variables xijrsx_{ijrs}, which again makes our LOP a BLOP. In addition to xijrsx_{ijrs}, variables uiju_{ij} will also be considered, representing the absolute ordinal position of the square (i,j)(i,j) within the path (these variables will be better explained later).

xijrs{0,1}x_{ijrs} \in \B
xijrs=1x_{ijrs}=1, if the edge that starts from square (i,j)(i,j) and arrives at square (r,s)(r,s) is part of the path
xijrs=0x_{ijrs}=0, otherwise
uijNu_{ij} \in \mathbb{N}
Absolute ordinal positions of the squares (i,j)(i,j) within the path.

Parameters

kij{1,,K},(i,j)Kk_{ij} \in \{1, \cdots, |K|\}, \forall (i,j) \in K
For each numbered board square in KK set, it’ll be defined a integer kijk_{ij} that matches its number displayed on game board.

Objective Function

Once again, our problem does not have a function to be optimized, since we only want to find a solution that fulfills all the rules of the game. Therefore, Zip’s BLOP is a feasibility problem, and the objective function of the model consists of maximizing (or minimizing) an arbitrary constant.

Min C\text{Min} \ C

Constraints

Decision variables domain constraints

First, let’s not forget to define our variables xijrsx_{ijrs} as binary and the variables uiju_{ij} as non-negative real numbers. To simplify, whenever possible, we can replace the notation xijrsx_{ijrs} with xvwx_{vw} and uiju_{ij} with just uvu_v, since the sequence (i,j)=v(i,j) = v always refers to some vertex belonging to VV set.

xv,w{0,1},(v,w)Euv0,vV\begin{array}{} x_{v,w} \in \B, \forall (v,w) \in E \\ u_v \ge 0, \forall v \in V \end{array}
Blocked edges constraints

If there is a wall between a pair of squares, then the sum of the edges connecting those ones must be equal to 0.

xv,w+xw,v=0,(v,w)Wx_{v,w} + x_{w,v} = 0, \forall (v,w) \in W
Continuity constraints

Since it is only possible to enter and exit a node once (except for the initial and destination nodes), then we will have the following:

Outgoing-Edges Constraints
The sum of the edges emanating from a node must equal 1 (except for the destination square).
wVxvw=1,vV\{v:kv=K}:(v,w)E\sum_{w \in V}{x_{vw}} = 1, \forall v \in V \backslash \{v:k_v=|K|\}: (v,w) \in E
Incoming-Edges Constraints
The sum of the edges arriving at a node must equal 1 (except for the initial square).
vVxvw=1,wV\{w:kw=1}:(v,w)E\sum_{v \in V}{x_{vw}} = 1, \forall w \in V \backslash \{w:k_w=1\}: (v,w) \in E
Arrival Square Constraint
The sum of the edges emanating from the destination node must be 0.
wVxvw=0,(v,w)E,kv=K\sum_{w \in V}{x_{vw}} = 0, (v,w) \in E, k_v = |K|
Starting Square Constraint
The sum of the edges arriving at the initial node must be 0.
vVxvw=0,(v,w)E,kw=1\sum_{v \in V}{x_{vw}} = 0, (v,w) \in E, k_w = 1
Subroute Elimination Constraints

The advantage of the MTZ formulation is that the number of constraints to be included to guarantee subroute elimination is equal to E=4nm2n2m|E| = 4nm - 2n - 2m, that is, the number of edges in the graph. Thus, the number of constraints grows linearly as a function of the number of edges, instead of exponentially as a function of the number of vertices. For it to work, it requires the addition of more decision variables (the previously mentioned uiju_{ij}), which represent the absolute ordinal position of square (i,j)(i,j) within the path.

Position of Starting Square Constraint
First, the position of the starting square must be equal to 1, since it will always be the first position on the path.
uv=1:kv=1u_v = 1: k_v = 1
MTZ Constraints
Then, the positions of the remaining squares on the board should be given as follows.
uwuv+1M(1xvw),(v,w)E:wV\{w:kw=1}u_w \ge u_v + 1 - M(1 - x_{vw}), \forall (v,w) \in E: w \in V \backslash \{w:k_w=1\}
Positon of Arrival Square Constraint
Although not necessary, one can restrict the position of the arrival square to be equal to the number of squares on the board, just to make the uvu_v range from 1 to 36.
uv=V:kv=Ku_v = |V|: k_v = |K|
Order of Precedence Constraints
In order for the path to traverse the numbered squares in ascending numerical order, it is necessary to impose the constraint that the position of a numbered square ii must be at least equal to the position subsequent to the last numbered square.
uvuw+1,v,wK:kv=kw+1u_v \ge u_w + 1, \forall v, w \in K : k_v = k_w + 1

Abstract Model

With all the formulas elaborated, we have the complete abstract model for the Zip minigame.

Min C\text{Min} \ C
S.t.:wVxvw=1,vV\{v:kv=K}:(v,w)EvVxvw=0,kv=K:(v,w)EvVxvw=1,wV\{w:kw=1}:(v,w)EvVxvw=0,kw=1:(v,w)Exvw+xwv=0,(v,w)Wuv=1,kv=1uv=V,kv=Kuwuv+1V(1xvw),(v,w)E:wV\{w:kw=1}uvuw+1,v,wK:kv=kw+1xvw{0,1},(v,w)Euv0,vV\begin{array}{lll} \text{S.t.:} & & \\ & \sum_{w \in V}{x_{vw}}=1, & \forall v \in V \backslash \{v:k_v=|K|\}: (v,w) \in E \\ & \sum_{v \in V}{x_{vw}}=0, & k_v = |K| : (v,w) \in E \\ & \sum_{v \in V}{x_{vw}}=1, & \forall w \in V \backslash \{w:k_w=1\}: (v,w) \in E \\ & \sum_{v \in V}{x_{vw}}=0, & k_w = 1: (v,w) \in E \\ & x_{vw} + x_{wv} = 0, & \forall (v,w) \in W \\ & u_v = 1, & k_v = 1 \\ & u_v = |V|, & k_v = |K| \\ & u_w \ge u_v + 1 - |V|(1-x_{vw}), & \forall (v,w) \in E: w \in V \backslash \{w:k_w=1\} \\ & u_v \ge u_w + 1, & \forall v,w \in K : k_v = k_w + 1 \\ & x_{vw} \in \B, & \forall (v,w) \in E \\ & u_v \ge 0, & \forall v \in V \end{array}

Concrete Model

The game selected to be solved is the Zip No. 166, published on LinkedIn on August 29th, 2025

Zip Problem

Figure 3:Zip No. 166, August 29th, 2025 (Source: LinkedIn Zip)

Based on this specific case, we have the following concrete model:

Min 0\text{Min} \ 0

S.t.:

Outgoing edges constraints
x1112+x1121=1x_{1112} + x_{1121} = 1 (Square (1,1))
x1211+x1222+x1213=1x_{1211} + x_{1222} + x_{1213} = 1 (Square (1,2))
x1312+x1323+x1314=1x_{1312} + x_{1323} + x_{1314} = 1 (Square (1,3))
x1413+x1424+x1415=1x_{1413} + x_{1424} + x_{1415} = 1 (Square (1,4))
x1514+x1525+x1516=1x_{1514} + x_{1525} + x_{1516} = 1 (Square (1,5))
x1615+x1626=1x_{1615} + x_{1626} = 1 (Square (1,6))
x2111+x2122+x2131=1x_{2111} + x_{2122} + x_{2131} = 1 (Square (2,1))
x2212+x2221+x2223+x2232=1x_{2212} + x_{2221} + x_{2223} + x_{2232} = 1 (Square (2,2))
x2313+x2322+x2324+x2333=1x_{2313} + x_{2322} + x_{2324} + x_{2333} = 1 (Square (2,3))
x2414+x2423+x2425+x2434=1x_{2414} + x_{2423} + x_{2425} + x_{2434} = 1 (Square (2,4))
x2515+x2524+x2526+x2535=1x_{2515} + x_{2524} + x_{2526} + x_{2535} = 1 (Square (2,5))
x2616+x2625+x2636=1x_{2616} + x_{2625} + x_{2636} = 1 (Square (2,6))
x3121+x3132+x3141=1x_{3121} + x_{3132} + x_{3141} = 1 (Square (3,1))
x3222+x3231+x3233+x3242=1x_{3222} + x_{3231} + x_{3233} + x_{3242} = 1 (Square (3,2))
x3323+x3332+x3334+x3343=1x_{3323} + x_{3332} + x_{3334} + x_{3343} = 1 (Square (3,3))
x3424+x3433+x3435+x3444=1x_{3424} + x_{3433} + x_{3435} + x_{3444} = 1 (Square (3,4))
x3525+x3534+x3536+x3545=1x_{3525} + x_{3534} + x_{3536} + x_{3545} = 1 (Square (3,5))
x3626+x3635+x3646=1x_{3626} + x_{3635} + x_{3646} = 1 (Square (3,6))
x4131+x4142+x4151=1x_{4131} + x_{4142} + x_{4151} = 1 (Square (4,1))
x4232+x4241+x4243+x4252=1x_{4232} + x_{4241} + x_{4243} + x_{4252} = 1 (Square (4,2))
x4333+x4342+x4344+x4353=1x_{4333} + x_{4342} + x_{4344} + x_{4353} = 1 (Square (4,3))
x4434+x4443+x4445+x4454=1x_{4434} + x_{4443} + x_{4445} + x_{4454} = 1 (Square (4,4))
x4535+x4544+x4546+x4555=1x_{4535} + x_{4544} + x_{4546} + x_{4555} = 1 (Square (4,5))
x4636+x4645+x4656=0x_{4636} + x_{4645} + x_{4656} = 0 (Square (4,6))
x5141+x5152+x5161=1x_{5141} + x_{5152} + x_{5161} = 1 (Square (5,1))
x5242+x5251+x5253+x5262=1x_{5242} + x_{5251} + x_{5253} + x_{5262} = 1 (Square (5,2))
x5343+x5352+x5354+x5363=1x_{5343} + x_{5352} + x_{5354} + x_{5363} = 1 (Square (5,3))
x5444+x5453+x5455+x5464=1x_{5444} + x_{5453} + x_{5455} + x_{5464} = 1 (Square (5,4))
x5545+x5554+x5556+x5565=1x_{5545} + x_{5554} + x_{5556} + x_{5565} = 1 (Square (5,5))
x5646+x5655+x5666=1x_{5646} + x_{5655} + x_{5666} = 1 (Square (5,6))
x6151+x6162=1x_{6151} + x_{6162} = 1 (Square (6,1))
x6252+x6261+x6263=1x_{6252} + x_{6261} + x_{6263} = 1 (Square (6,2))
x6353+x6362+x6364=1x_{6353} + x_{6362} + x_{6364} = 1 (Square (6,3))
x6454+x6463+x6465=1x_{6454} + x_{6463} + x_{6465} = 1 (Square (6,4))
x6555+x6564+x6566=1x_{6555} + x_{6564} + x_{6566} = 1 (Square (6,5))
x6656+x6665=1x_{6656} + x_{6665} = 1 (Square (6,6))
Incoming edges cosntraints
x1211+x2111=1x_{1211} + x_{2111} = 1 (Square (1,1))
x1112+x2212+x1312=1x_{1112} + x_{2212} + x_{1312} = 1 (Square (1,2))
x1213+x2313+x1413=1x_{1213} + x_{2313} + x_{1413} = 1 (Square (1,3))
x1314+x2414+x1514=1x_{1314} + x_{2414} + x_{1514} = 1 (Square (1,4))
x1415+x2515+x1615=1x_{1415} + x_{2515} + x_{1615} = 1 (Square (1,5))
x1516+x2616=1x_{1516} + x_{2616} = 1 (Square (1,6))
x1121+x2221+x3121=1x_{1121} + x_{2221} + x_{3121} = 1 (Square (2,1))
x1222+x2122+x2322+x3222=1x_{1222} + x_{2122} + x_{2322} + x_{3222} = 1 (Square (2,2))
x1323+x2223+x2423+x3323=1x_{1323} + x_{2223} + x_{2423} + x_{3323} = 1 (Square (2,3))
x1424+x2324+x2524+x3424=1x_{1424} + x_{2324} + x_{2524} + x_{3424} = 1 (Square (2,4))
x1525+x2425+x2625+x3525=1x_{1525} + x_{2425} + x_{2625} + x_{3525} = 1 (Square (2,5))
x1626+x2526+x3626=1x_{1626} + x_{2526} + x_{3626} = 1 (Square (2,6))
x2131+x3231+x4131=1x_{2131} + x_{3231} + x_{4131} = 1 (Square (3,1))
x2232+x3132+x3332+x4232=1x_{2232} + x_{3132} + x_{3332} + x_{4232} = 1 (Square (3,2))
x2333+x3233+x3433+x4333=1x_{2333} + x_{3233} + x_{3433} + x_{4333} = 1 (Square (3,3))
x2434+x3334+x3534+x4434=1x_{2434} + x_{3334} + x_{3534} + x_{4434} = 1 (Square (3,4))
x2535+x3435+x3635+x4535=1x_{2535} + x_{3435} + x_{3635} + x_{4535} = 1 (Square (3,5))
x2636+x3536+x4636=1x_{2636} + x_{3536} + x_{4636} = 1 (Square (3,6))
x3141+x4241+x5141=1x_{3141} + x_{4241} + x_{5141} = 1 (Square (4,1))
x3242+x4142+x4342+x5242=1x_{3242} + x_{4142} + x_{4342} + x_{5242} = 1 (Square (4,2))
x3343+x4243+x4443+x5343=1x_{3343} + x_{4243} + x_{4443} + x_{5343} = 1 (Square (4,3))
x3444+x4344+x4544+x5444=1x_{3444} + x_{4344} + x_{4544} + x_{5444} = 1 (Square (4,4))
x3545+x4445+x4645+x5545=1x_{3545} + x_{4445} + x_{4645} + x_{5545} = 1 (Square (4,5))
x3646+x4546+x5646=1x_{3646} + x_{4546} + x_{5646} = 1 (Square (4,6))
x4151+x5251+x6151=1x_{4151} + x_{5251} + x_{6151} = 1 (Square (5,1))
x4252+x5152+x5352+x6252=1x_{4252} + x_{5152} + x_{5352} + x_{6252} = 1 (Square (5,2))
x4353+x5253+x5453+x6353=1x_{4353} + x_{5253} + x_{5453} + x_{6353} = 1 (Square (5,3))
x4454+x5354+x5554+x6454=1x_{4454} + x_{5354} + x_{5554} + x_{6454} = 1 (Square (5,4))
x4555+x5455+x5655+x6555=1x_{4555} + x_{5455} + x_{5655} + x_{6555} = 1 (Square (5,5))
x4656+x5556+x6656=1x_{4656} + x_{5556} + x_{6656} = 1 (Square (5,6))
x5161+x6261=1x_{5161} + x_{6261} = 1 (Square (6,1))
x5262+x6162+x6362=1x_{5262} + x_{6162} + x_{6362} = 1 (Square (6,2))
x5363+x6263+x6463=1x_{5363} + x_{6263} + x_{6463} = 1 (Square (6,3))
x5464+x6364+x6564=1x_{5464} + x_{6364} + x_{6564} = 1 (Square (6,4))
x5565+x6465+x6665=0x_{5565} + x_{6465} + x_{6665} = 0 (Square (6,5))
x5666+x6566=1x_{5666} + x_{6566} = 1 (Square (6,6))
Ordinal position constraints
u65=1u_{65} = 1 (Square 1)
u66u65+1u_{66} \ge u_{65} + 1 (Square 2)
u56u66+1u_{56} \ge u_{66} + 1 (Square 3)
u64u56+1u_{64} \ge u_{56} + 1 (Square 4)
u53u64+1u_{53} \ge u_{64} + 1 (Square 5)
u42u53+1u_{42} \ge u_{53} + 1 (Square 6)
u31u42+1u_{31} \ge u_{42} + 1 (Square 7)
u21u31+1u_{21} \ge u_{31} + 1 (Square 8)
u11u21+1u_{11} \ge u_{21} + 1 (Square 9)
u12u11+1u_{12} \ge u_{11} + 1 (Square 10)
u13u12+1u_{13} \ge u_{12} + 1 (Square 11)
u35u13+1u_{35} \ge u_{13} + 1 (Square 12)
u24u35+1u_{24} \ge u_{35} + 1 (Square 13)
u34u24+1u_{34} \ge u_{24} + 1 (Square 14)
u43u34+1u_{43} \ge u_{34} + 1 (Square 15)
u46u43+1u_{46} \ge u_{43} + 1 (Square 16)
u46=36u_{46} = 36 (Square 16)
Subroute elimination constraints
u11u12+136(1x1211)u_{11} \ge u_{12} + 1 - 36(1 - x_{1211})
u11u21+136(1x2111)u_{11} \ge u_{21} + 1 - 36(1 - x_{2111})
u12u11+136(1x1112)u_{12} \ge u_{11} + 1 - 36(1 - x_{1112})
u12u22+136(1x2212)u_{12} \ge u_{22} + 1 - 36(1 - x_{2212})
u12u13+136(1x1312)u_{12} \ge u_{13} + 1 - 36(1 - x_{1312})
u13u12+136(1x1213)u_{13} \ge u_{12} + 1 - 36(1 - x_{1213})
u13u23+136(1x2313)u_{13} \ge u_{23} + 1 - 36(1 - x_{2313})
u13u14+136(1x1413)u_{13} \ge u_{14} + 1 - 36(1 - x_{1413})
u14u13+136(1x1314)u_{14} \ge u_{13} + 1 - 36(1 - x_{1314})
u14u24+136(1x2414)u_{14} \ge u_{24} + 1 - 36(1 - x_{2414})
u14u15+136(1x1514)u_{14} \ge u_{15} + 1 - 36(1 - x_{1514})
u15u14+136(1x1415)u_{15} \ge u_{14} + 1 - 36(1 - x_{1415})
u15u25+136(1x2515)u_{15} \ge u_{25} + 1 - 36(1 - x_{2515})
u15u16+136(1x1615)u_{15} \ge u_{16} + 1 - 36(1 - x_{1615})
u16u15+136(1x1516)u_{16} \ge u_{15} + 1 - 36(1 - x_{1516})
u16u26+136(1x2616)u_{16} \ge u_{26} + 1 - 36(1 - x_{2616})
u21u11+136(1x1121)u_{21} \ge u_{11} + 1 - 36(1 - x_{1121})
u21u22+136(1x2221)u_{21} \ge u_{22} + 1 - 36(1 - x_{2221})
u21u31+136(1x3121)u_{21} \ge u_{31} + 1 - 36(1 - x_{3121})
u22u12+136(1x1222)u_{22} \ge u_{12} + 1 - 36(1 - x_{1222})
u22u21+136(1x2122)u_{22} \ge u_{21} + 1 - 36(1 - x_{2122})
u22u23+136(1x2322)u_{22} \ge u_{23} + 1 - 36(1 - x_{2322})
u22u32+136(1x3222)u_{22} \ge u_{32} + 1 - 36(1 - x_{3222})
u23u13+136(1x1323)u_{23} \ge u_{13} + 1 - 36(1 - x_{1323})
u23u22+136(1x2223)u_{23} \ge u_{22} + 1 - 36(1 - x_{2223})
u23u24+136(1x2423)u_{23} \ge u_{24} + 1 - 36(1 - x_{2423})
u23u33+136(1x3323)u_{23} \ge u_{33} + 1 - 36(1 - x_{3323})
u24u14+136(1x1424)u_{24} \ge u_{14} + 1 - 36(1 - x_{1424})
u24u23+136(1x2324)u_{24} \ge u_{23} + 1 - 36(1 - x_{2324})
u24u25+136(1x2524)u_{24} \ge u_{25} + 1 - 36(1 - x_{2524})
u24u34+136(1x3424)u_{24} \ge u_{34} + 1 - 36(1 - x_{3424})
u25u15+136(1x1525)u_{25} \ge u_{15} + 1 - 36(1 - x_{1525})
u25u24+136(1x2425)u_{25} \ge u_{24} + 1 - 36(1 - x_{2425})
u25u26+136(1x2625)u_{25} \ge u_{26} + 1 - 36(1 - x_{2625})
u25u35+136(1x3525)u_{25} \ge u_{35} + 1 - 36(1 - x_{3525})
u26u16+136(1x1626)u_{26} \ge u_{16} + 1 - 36(1 - x_{1626})
u26u25+136(1x2526)u_{26} \ge u_{25} + 1 - 36(1 - x_{2526})
u26u36+136(1x3626)u_{26} \ge u_{36} + 1 - 36(1 - x_{3626})
u31u21+136(1x2131)u_{31} \ge u_{21} + 1 - 36(1 - x_{2131})
u31u32+136(1x3231)u_{31} \ge u_{32} + 1 - 36(1 - x_{3231})
u31u41+136(1x4131)u_{31} \ge u_{41} + 1 - 36(1 - x_{4131})
u32u22+136(1x2232)u_{32} \ge u_{22} + 1 - 36(1 - x_{2232})
u32u31+136(1x3132)u_{32} \ge u_{31} + 1 - 36(1 - x_{3132})
u32u33+136(1x3332)u_{32} \ge u_{33} + 1 - 36(1 - x_{3332})
u32u42+136(1x4232)u_{32} \ge u_{42} + 1 - 36(1 - x_{4232})
u33u23+136(1x2333)u_{33} \ge u_{23} + 1 - 36(1 - x_{2333})
u33u32+136(1x3233)u_{33} \ge u_{32} + 1 - 36(1 - x_{3233})
u33u34+136(1x3433)u_{33} \ge u_{34} + 1 - 36(1 - x_{3433})
u33u43+136(1x4333)u_{33} \ge u_{43} + 1 - 36(1 - x_{4333})
u34u24+136(1x2434)u_{34} \ge u_{24} + 1 - 36(1 - x_{2434})
u34u33+136(1x3334)u_{34} \ge u_{33} + 1 - 36(1 - x_{3334})
u34u35+136(1x3534)u_{34} \ge u_{35} + 1 - 36(1 - x_{3534})
u34u44+136(1x4434)u_{34} \ge u_{44} + 1 - 36(1 - x_{4434})
u35u25+136(1x2535)u_{35} \ge u_{25} + 1 - 36(1 - x_{2535})
u35u34+136(1x3435)u_{35} \ge u_{34} + 1 - 36(1 - x_{3435})
u35u36+136(1x3635)u_{35} \ge u_{36} + 1 - 36(1 - x_{3635})
u35u45+136(1x4535)u_{35} \ge u_{45} + 1 - 36(1 - x_{4535})
u36u26+136(1x2636)u_{36} \ge u_{26} + 1 - 36(1 - x_{2636})
u36u35+136(1x3536)u_{36} \ge u_{35} + 1 - 36(1 - x_{3536})
u36u46+136(1x4636)u_{36} \ge u_{46} + 1 - 36(1 - x_{4636})
u41u31+136(1x3141)u_{41} \ge u_{31} + 1 - 36(1 - x_{3141})
u41u42+136(1x4241)u_{41} \ge u_{42} + 1 - 36(1 - x_{4241})
u41u51+136(1x5141)u_{41} \ge u_{51} + 1 - 36(1 - x_{5141})
u42u32+136(1x3242)u_{42} \ge u_{32} + 1 - 36(1 - x_{3242})
u42u41+136(1x4142)u_{42} \ge u_{41} + 1 - 36(1 - x_{4142})
u42u43+136(1x4342)u_{42} \ge u_{43} + 1 - 36(1 - x_{4342})
u42u52+136(1x5242)u_{42} \ge u_{52} + 1 - 36(1 - x_{5242})
u43u33+136(1x3343)u_{43} \ge u_{33} + 1 - 36(1 - x_{3343})
u43u42+136(1x4243)u_{43} \ge u_{42} + 1 - 36(1 - x_{4243})
u43u44+136(1x4443)u_{43} \ge u_{44} + 1 - 36(1 - x_{4443})
u43u53+136(1x5343)u_{43} \ge u_{53} + 1 - 36(1 - x_{5343})
u44u34+136(1x3444)u_{44} \ge u_{34} + 1 - 36(1 - x_{3444})
u44u43+136(1x4344)u_{44} \ge u_{43} + 1 - 36(1 - x_{4344})
u44u45+136(1x4544)u_{44} \ge u_{45} + 1 - 36(1 - x_{4544})
u44u54+136(1x5444)u_{44} \ge u_{54} + 1 - 36(1 - x_{5444})
u45u35+136(1x3545)u_{45} \ge u_{35} + 1 - 36(1 - x_{3545})
u45u44+136(1x4445)u_{45} \ge u_{44} + 1 - 36(1 - x_{4445})
u45u46+136(1x4645)u_{45} \ge u_{46} + 1 - 36(1 - x_{4645})
u45u55+136(1x5545)u_{45} \ge u_{55} + 1 - 36(1 - x_{5545})
u46u36+136(1x3646)u_{46} \ge u_{36} + 1 - 36(1 - x_{3646})
u46u45+136(1x4546)u_{46} \ge u_{45} + 1 - 36(1 - x_{4546})
u46u56+136(1x5646)u_{46} \ge u_{56} + 1 - 36(1 - x_{5646})
u51u41+136(1x4151)u_{51} \ge u_{41} + 1 - 36(1 - x_{4151})
u51u52+136(1x5251)u_{51} \ge u_{52} + 1 - 36(1 - x_{5251})
u51u61+136(1x6151)u_{51} \ge u_{61} + 1 - 36(1 - x_{6151})
u52u42+136(1x4252)u_{52} \ge u_{42} + 1 - 36(1 - x_{4252})
u52u51+136(1x5152)u_{52} \ge u_{51} + 1 - 36(1 - x_{5152})
u52u53+136(1x5352)u_{52} \ge u_{53} + 1 - 36(1 - x_{5352})
u52u62+136(1x6252)u_{52} \ge u_{62} + 1 - 36(1 - x_{6252})
u53u43+136(1x4353)u_{53} \ge u_{43} + 1 - 36(1 - x_{4353})
u53u52+136(1x5253)u_{53} \ge u_{52} + 1 - 36(1 - x_{5253})
u53u54+136(1x5453)u_{53} \ge u_{54} + 1 - 36(1 - x_{5453})
u53u63+136(1x6353)u_{53} \ge u_{63} + 1 - 36(1 - x_{6353})
u54u44+136(1x4454)u_{54} \ge u_{44} + 1 - 36(1 - x_{4454})
u54u53+136(1x5354)u_{54} \ge u_{53} + 1 - 36(1 - x_{5354})
u54u55+136(1x5554)u_{54} \ge u_{55} + 1 - 36(1 - x_{5554})
u54u64+136(1x6454)u_{54} \ge u_{64} + 1 - 36(1 - x_{6454})
u55u45+136(1x4555)u_{55} \ge u_{45} + 1 - 36(1 - x_{4555})
u55u54+136(1x5455)u_{55} \ge u_{54} + 1 - 36(1 - x_{5455})
u55u56+136(1x5655)u_{55} \ge u_{56} + 1 - 36(1 - x_{5655})
u55u65+136(1x6555)u_{55} \ge u_{65} + 1 - 36(1 - x_{6555})
u56u46+136(1x4656)u_{56} \ge u_{46} + 1 - 36(1 - x_{4656})
u56u55+136(1x5556)u_{56} \ge u_{55} + 1 - 36(1 - x_{5556})
u56u66+136(1x6656)u_{56} \ge u_{66} + 1 - 36(1 - x_{6656})
u61u51+136(1x5161)u_{61} \ge u_{51} + 1 - 36(1 - x_{5161})
u61u62+136(1x6261)u_{61} \ge u_{62} + 1 - 36(1 - x_{6261})
u62u52+136(1x5262)u_{62} \ge u_{52} + 1 - 36(1 - x_{5262})
u62u61+136(1x6162)u_{62} \ge u_{61} + 1 - 36(1 - x_{6162})
u62u63+136(1x6362)u_{62} \ge u_{63} + 1 - 36(1 - x_{6362})
u63u53+136(1x5363)u_{63} \ge u_{53} + 1 - 36(1 - x_{5363})
u63u62+136(1x6263)u_{63} \ge u_{62} + 1 - 36(1 - x_{6263})
u63u64+136(1x6463)u_{63} \ge u_{64} + 1 - 36(1 - x_{6463})
u64u54+136(1x5464)u_{64} \ge u_{54} + 1 - 36(1 - x_{5464})
u64u63+136(1x6364)u_{64} \ge u_{63} + 1 - 36(1 - x_{6364})
u64u65+136(1x6564)u_{64} \ge u_{65} + 1 - 36(1 - x_{6564})
u65u55+136(1x5565)u_{65} \ge u_{55} + 1 - 36(1 - x_{5565})
u65u64+136(1x6465)u_{65} \ge u_{64} + 1 - 36(1 - x_{6465})
u65u66+136(1x6665)u_{65} \ge u_{66} + 1 - 36(1 - x_{6665})
u66u56+136(1x5666)u_{66} \ge u_{56} + 1 - 36(1 - x_{5666})
u66u65+136(1x6566)u_{66} \ge u_{65} + 1 - 36(1 - x_{6566})
Binarity constraints for edge variables
x1112{0,1}x_{1112} \in \B
x1121{0,1}x_{1121} \in \B
x1211{0,1}x_{1211} \in \B
x1222{0,1}x_{1222} \in \B
x1213{0,1}x_{1213} \in \B
x1312{0,1}x_{1312} \in \B
x1323{0,1}x_{1323} \in \B
x1314{0,1}x_{1314} \in \B
x1413{0,1}x_{1413} \in \B
x1424{0,1}x_{1424} \in \B
x1415{0,1}x_{1415} \in \B
x1514{0,1}x_{1514} \in \B
x1525{0,1}x_{1525} \in \B
x1516{0,1}x_{1516} \in \B
x1615{0,1}x_{1615} \in \B
x1626{0,1}x_{1626} \in \B
x2111{0,1}x_{2111} \in \B
x2122{0,1}x_{2122} \in \B
x2131{0,1}x_{2131} \in \B
x2212{0,1}x_{2212} \in \B
x2221{0,1}x_{2221} \in \B
x2223{0,1}x_{2223} \in \B
x2232{0,1}x_{2232} \in \B
x2313{0,1}x_{2313} \in \B
x2322{0,1}x_{2322} \in \B
x2324{0,1}x_{2324} \in \B
x2333{0,1}x_{2333} \in \B
x2414{0,1}x_{2414} \in \B
x2423{0,1}x_{2423} \in \B
x2425{0,1}x_{2425} \in \B
x2434{0,1}x_{2434} \in \B
x2515{0,1}x_{2515} \in \B
x2524{0,1}x_{2524} \in \B
x2526{0,1}x_{2526} \in \B
x2535{0,1}x_{2535} \in \B
x2616{0,1}x_{2616} \in \B
x2625{0,1}x_{2625} \in \B
x2636{0,1}x_{2636} \in \B
x3121{0,1}x_{3121} \in \B
x3132{0,1}x_{3132} \in \B
x3141{0,1}x_{3141} \in \B
x3222{0,1}x_{3222} \in \B
x3231{0,1}x_{3231} \in \B
x3233{0,1}x_{3233} \in \B
x3242{0,1}x_{3242} \in \B
x3323{0,1}x_{3323} \in \B
x3332{0,1}x_{3332} \in \B
x3334{0,1}x_{3334} \in \B
x3343{0,1}x_{3343} \in \B
x3424{0,1}x_{3424} \in \B
x3433{0,1}x_{3433} \in \B
x3435{0,1}x_{3435} \in \B
x3444{0,1}x_{3444} \in \B
x3525{0,1}x_{3525} \in \B
x3534{0,1}x_{3534} \in \B
x3536{0,1}x_{3536} \in \B
x3545{0,1}x_{3545} \in \B
x3626{0,1}x_{3626} \in \B
x3635{0,1}x_{3635} \in \B
x3646{0,1}x_{3646} \in \B
x4131{0,1}x_{4131} \in \B
x4142{0,1}x_{4142} \in \B
x4151{0,1}x_{4151} \in \B
x4232{0,1}x_{4232} \in \B
x4241{0,1}x_{4241} \in \B
x4243{0,1}x_{4243} \in \B
x4252{0,1}x_{4252} \in \B
x4333{0,1}x_{4333} \in \B
x4342{0,1}x_{4342} \in \B
x4344{0,1}x_{4344} \in \B
x4353{0,1}x_{4353} \in \B
x4434{0,1}x_{4434} \in \B
x4443{0,1}x_{4443} \in \B
x4445{0,1}x_{4445} \in \B
x4454{0,1}x_{4454} \in \B
x4535{0,1}x_{4535} \in \B
x4544{0,1}x_{4544} \in \B
x4546{0,1}x_{4546} \in \B
x4555{0,1}x_{4555} \in \B
x4636{0,1}x_{4636} \in \B
x4645{0,1}x_{4645} \in \B
x4656{0,1}x_{4656} \in \B
x5141{0,1}x_{5141} \in \B
x5152{0,1}x_{5152} \in \B
x5161{0,1}x_{5161} \in \B
x5242{0,1}x_{5242} \in \B
x5251{0,1}x_{5251} \in \B
x5253{0,1}x_{5253} \in \B
x5262{0,1}x_{5262} \in \B
x5343{0,1}x_{5343} \in \B
x5352{0,1}x_{5352} \in \B
x5354{0,1}x_{5354} \in \B
x5363{0,1}x_{5363} \in \B
x5444{0,1}x_{5444} \in \B
x5453{0,1}x_{5453} \in \B
x5455{0,1}x_{5455} \in \B
x5464{0,1}x_{5464} \in \B
x5545{0,1}x_{5545} \in \B
x5554{0,1}x_{5554} \in \B
x5556{0,1}x_{5556} \in \B
x5565{0,1}x_{5565} \in \B
x5646{0,1}x_{5646} \in \B
x5655{0,1}x_{5655} \in \B
x5666{0,1}x_{5666} \in \B
x6151{0,1}x_{6151} \in \B
x6162{0,1}x_{6162} \in \B
x6252{0,1}x_{6252} \in \B
x6261{0,1}x_{6261} \in \B
x6263{0,1}x_{6263} \in \B
x6353{0,1}x_{6353} \in \B
x6362{0,1}x_{6362} \in \B
x6364{0,1}x_{6364} \in \B
x6454{0,1}x_{6454} \in \B
x6463{0,1}x_{6463} \in \B
x6465{0,1}x_{6465} \in \B
x6555{0,1}x_{6555} \in \B
x6564{0,1}x_{6564} \in \B
x6566{0,1}x_{6566} \in \B
x6656{0,1}x_{6656} \in \B
x6665{0,1}x_{6665} \in \B
Non-negativity constraints for positional variables
u110u_{11} \ge 0 (Square (1,1))
u120u_{12} \ge 0 (Square (1,2))
u130u_{13} \ge 0 (Square (1,3))
u140u_{14} \ge 0 (Square (1,4))
u150u_{15} \ge 0 (Square (1,5))
u160u_{16} \ge 0 (Square (1,6))
u210u_{21} \ge 0 (Square (2,1))
u220u_{22} \ge 0 (Square (2,2))
u230u_{23} \ge 0 (Square (2,3))
u240u_{24} \ge 0 (Square (2,4))
u250u_{25} \ge 0 (Square (2,5))
u260u_{26} \ge 0 (Square (2,6))
u310u_{31} \ge 0 (Square (3,1))
u320u_{32} \ge 0 (Square (3,2))
u330u_{33} \ge 0 (Square (3,3))
u340u_{34} \ge 0 (Square (3,4))
u350u_{35} \ge 0 (Square (3,5))
u360u_{36} \ge 0 (Square (3,6))
u410u_{41} \ge 0 (Square (4,1))
u420u_{42} \ge 0 (Square (4,2))
u430u_{43} \ge 0 (Square (4,3))
u440u_{44} \ge 0 (Square (4,4))
u450u_{45} \ge 0 (Square (4,5))
u460u_{46} \ge 0 (Square (4,6))
u510u_{51} \ge 0 (Square (5,1))
u520u_{52} \ge 0 (Square (5,2))
u530u_{53} \ge 0 (Square (5,3))
u540u_{54} \ge 0 (Square (5,4))
u550u_{55} \ge 0 (Square (5,5))
u560u_{56} \ge 0 (Square (5,6))
u610u_{61} \ge 0 (Square (6,1))
u620u_{62} \ge 0 (Square (6,2))
u630u_{63} \ge 0 (Square (6,3))
u640u_{64} \ge 0 (Square (6,4))
u650u_{65} \ge 0 (Square (6,5))
u660u_{66} \ge 0 (Square (6,6))

Solving Zip

The linkedin-games library counts on Zip class, that implements the Zip game and its constraints, as well as methods to solve it and visualize the solution.

To instantiate the game, it is necessary to provide two sets of inputs regarding the game in question:

numbered_squares
A dictionary of (row, column): number items representing the starting filled squares, where number is the required relative position of the square on the path.
walls
A tuple of pairs of squares (edges) representing walls on game board.
from linkedin_games import Zip


# Solving Zip No. 166

numbered_squares = [
    (6,5), (6,6), (5,6), (6,4), (5,3), (4,2), (3,1), (2,1),
    (1,1), (1,2), (1,3), (3,5), (2,4), (3,4), (4,3), (4,6)
]

zip_game = Zip(6, numbered_squares)

The Zip class features the model attribute, which implements the ZipModel object to structure the Linear Optimization logic behind the Zip’s game.

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


class ZipModel(TaxicabDistanceMixin, pyo.ConcreteModel):
    """The Linear Optimization Model for LinkedIn Zip game."""

    def __init__(self,
            grid_dims: tuple[int, int],
            numbered_squares: list[tuple[int, int]],
            walls: list[tuple[tuple[int, int], tuple[int, int]]] | None
        ) -> None:
        """
        Args:
            grid_dims: Grid dimensions as a `(row, column)` tuple.
            numbered_squares: Squares with a assigned number as a dictionary of `(row, column): number` items.
            walls: Pairs of squares separated by a walls as a tuple of `((row1, column1), (row2, column2))`.
        """
        super().__init__()

        # PARAMETERS
        m, n = grid_dims
        M = m * n # Big M
        self.m = pyo.Param(initialize=m, domain=pyo.PositiveIntegers)
        self.n = pyo.Param(initialize=n, domain=pyo.PositiveIntegers)

        # RANGE SETS
        I = self.I = pyo.RangeSet(n) # Rows
        J = self.J = pyo.RangeSet(m) # Columns
        K = self.K = pyo.RangeSet(len(numbered_squares))

        # COMPOSITE SETS
        S = self.S = pyo.Set(initialize=lambda model: [(i, j) for i in I for j in J]) # Grid Squares
        E = self.E = pyo.Set(initialize=lambda model: # Edges
            [((i,j), (i+1, j)) for i in I for j in J if i+1 in I] +
            [((i,j), (i-1, j)) for i in I for j in J if i-1 in I] +
            [((i,j), (i, j+1)) for i in I for j in J if j+1 in J] +
            [((i,j), (i, j-1)) for i in I for j in J if j-1 in J]
        )
        W = self.W = pyo.Set(initialize=walls, domain=E) # Walls
        N = self.N = pyo.Set(initialize=numbered_squares, domain=S)

        # DECISION VARIABLES
        x = self.x = pyo.Var(E, domain=pyo.Binary, initialize=0) # Decision to go from square (i,j) to (r,s)
        u = self.u = pyo.Var( # Visitiation order of a square (i, j)
            S, domain=pyo.PositiveIntegers, initialize=1, bounds=(1, M)
        )

        # OBJECTIVE FUNCTION
        self.obj = pyo.Objective(expr=0) # feasibility problem

        # CONSTRAINTS

        ## Edge constraints
        neighbors = { # This dictionary is important to access all neighbors of a square quickly.
            (i, j): [
                (r, c) for r, c in [
                    (i-1, j),
                    (i+1, j),
                    (i, j-1),
                    (i, j+1)
                ] if (r, c) in S
            ] for (i, j) in S
        }
        self.outgoing_edges_constraints = pyo.Constraint(
            S, rule=lambda model, i, j:
                pyo.quicksum(x[(i,j), w] for w in neighbors[(i,j)]) == 0 if N.at(len(K)) == (i,j) else
                pyo.quicksum(x[(i,j), w] for w in neighbors[(i,j)]) == 1
        )
        self.incoming_edges_constraints = pyo.Constraint(
            S, rule=lambda model, i, j:
                pyo.quicksum(x[s, (i,j)] for s in neighbors[(i,j)]) == 0 if N.at(1) == (i,j) else
                pyo.quicksum(x[s, (i,j)] for s in neighbors[(i,j)]) == 1
        )

        ## Blocked paths constraints
        self.wall_constraints = pyo.Constraint(
            W, rule=lambda model, i, j, r, s: x[i,j,r,s] + x[r,s,i,j] == 0
        )

        # Miller-Tucker-Zemlin constraints with lifting
        self.subroute_elimination_constraints = pyo.Constraint(
            E, rule=lambda model, i, j, r, s: u[r,s] >= u[i,j] + 1 - M * (1 - x[i,j,r,s]) + (M - 2) * x[r,s,i,j]
        )

        ## Visitation order constraints
        self.visitation_order_constraints = pyo.Constraint(
            K, rule= lambda model, k:
                u[N.at(k)] == 1 if k == 1 else
                u[N.at(k)] == M if k == len(N) else
                u[N.at(k)] >= u[N.at(k-1)] + self._taxicab_distance(N.at(k), N.at(k-1))
        )

Program 1:Creating the model with Pyomo components.

After the model is built, the public method solve() calls the restricted method _set_solution() to save the game’s solution to _board property, which is a NetworkX Graph representing the game’s board with the positions on the path, and to private attribute __path, which can be accessed by public path attribute.

zip.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23



    @property
    def path(self) -> list[tuple[int, int]] | None:
        """
        The solving path of Zip game.
        
        The path that visits all the grid squares, starting from 1-numbered squared to the highest-numbered square.

        Returns:
            The solving path as a list of squares as `(row, column)`.
        """
        if not self.is_solved:
            return None
        return self.__path


    def _set_solution(self, verbose:bool=False) -> None:

        S = self.model.S
        E = self.model.E
        u = self.model.u

Program 2:Saving the game’s solution to path attribute.

With the solution obtained, the method show() can be called to plot the Zip’s board.

zip.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81

        x = self.model.x

        nx.set_node_attributes(
            self.grid,
            name="value",
            values={(i-1, j-1): round(pyo.value(u[i,j])) for i, j in S}
        )
        nx.set_edge_attributes(
            self.grid,
            name="value",
            values={((i-1, j-1), (r-1, s-1)): round(pyo.value(x[i,j,r,s])) for i, j, r, s in E}
        )
        path = nx.get_node_attributes(self.grid, "value")
        path = sorted(path.keys(), key=path.get)
        self.__path = [(i+1, j+1) for (i, j) in path]
        if verbose:
            print("This is the path that solves the games:")
            pprint(self.path)


    def show(self) -> None:
        """Show Zip's grid."""

        E = self.model.E
        N = self.model.N
        K = self.model.K
        x = self.model.x

        width = height = self.size * 0.7
        plt.figure(figsize=(width, height))
        path_color = super()._generate_hex_code()
        labels = {N.at(k): k for k in K}
        labels = {(i-1, j-1): k for (i,j), k in labels.items()}
        pos={(i,j): (j,-i) for i, j in self.grid.nodes()}

        if self.walls is not None:
            walls = nx.draw_networkx_edges(
                self.grid,
                pos=pos,
                edgelist=[((i-1, j-1), (r-1, s-1)) for (i,j),(r,s) in self.walls],
                edge_color="#000000",
                hide_ticks=True,
                arrows=False,
                width=30
            )
            walls.set_zorder(0)

        grid_squares = nx.draw_networkx_nodes(
            self.grid,
            pos= pos,
            node_shape="s",
            node_size= 1100,
            node_color= "#FFFFFF",
            linewidths= 2,
        )
        grid_squares.set_zorder(1)

        nx.draw( # Drawing the path
            self.grid,
            pos= pos,
            with_labels= True,
            labels=labels,
            arrows=False,
            node_shape="o" if self.is_solved else "s",
            node_size= 800,
            node_color= [
                "white" if (i+1,j+1) in self.numbered_squares else path_color
                for (i,j) in self.grid.nodes()
            ],
            edge_color= path_color,
            edgecolors= path_color,
            linewidths= 1,
            width= 30,
            edgelist= [
                ((i-1, j-1), (r-1, s-1)) for i,j,r,s in E
                if round(pyo.value(x[i,j,r,s])) == 1
            ]
        )

        plt.show()

Program 3:Implementation of show() function.

So to solve the game and display its results, just call the public methods solve() and show(), at this order.

zip_game.solve()
zip_game.show()
<Figure size 340x340 with 1 Axes>

As expected, the displayed path satisfies all the rules of Zip No. 166.

Zip game solution. Source: LinkedIn Zip

Figure 4:Zip game solution. Source: LinkedIn Zip


References

  • Miller-Tucker-Zemlin Formulation. AIMMS. December 13th, 2024. Accessed on September 2nd, 2025.