Skip to content

Commit 615025d

Browse files
committed
✨ feat: refactor dlx
1 parent b99221c commit 615025d

File tree

8 files changed

+255
-254
lines changed

8 files changed

+255
-254
lines changed

Diff for: MIGRATION.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ import { dijkstraBigint } from '@algorithm.ts/dijkstra'
111111

112112
### @algorithm.ts/dinic
113113

114-
1. Perform class style API, use `new Dinic()` instead of `createDinic()`
114+
1. Use `new Dinic()` instead of `createDinic()`
115115
2. `.maxFlow()` is renamed to `.maxflow()`
116116
3. `.solve()` is removed, if you want to access the residual network after run the `.maxflow()`,
117117
you can try to extend the `Dinic` and export a method such as `getSnapshot()`.
@@ -131,3 +131,7 @@ import { dijkstraBigint } from '@algorithm.ts/dijkstra'
131131
}
132132
}
133133
```
134+
135+
### @algorithm.ts/dlx
136+
137+
1. Use `new DancingLinkX({ MAX_N: <number> })` instead of `createDLX(<number>)`

Diff for: packages/dlx/README.md

+10-18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<header>
22
<h1 align="center">
3-
<a href="https://github.com/guanghechen/algorithm.ts/tree/release-2.x.x/packages/dlx#readme">@algorithm.ts/dlx</a>
3+
<a href="https://github.com/guanghechen/algorithm.ts/tree/release-3.x.x/packages/dlx#readme">@algorithm.ts/dlx</a>
44
</h1>
55
<div align="center">
66
<a href="https://www.npmjs.com/package/@algorithm.ts/dlx">
@@ -52,8 +52,8 @@
5252

5353
A typescript implementation of the **DLX** algorithm.
5454

55-
DLX is the Algorithm X that applied the dancing-link. The algorithm is used to
56-
solve the exact-cover problem.
55+
DLX is the Algorithm X that applied the dancing-link. The algorithm is used to solve the
56+
exact-cover problem.
5757

5858
If you are curious about this algorithm, you can visit [here][dlx] for more details.
5959

@@ -72,23 +72,18 @@ If you are curious about this algorithm, you can visit [here][dlx] for more deta
7272
yarn add @algorithm.ts/dlx
7373
```
7474

75-
* deno
76-
77-
```typescript
78-
import { createDLX } from 'https://raw.githubusercontent.com/guanghechen/algorithm.ts/main/packages/dlx/src/index.ts'
79-
```
8075

8176
## Usage
8277

8378
* Use dlx to solve a 9x9 sudoku problem:
8479

8580
```typescript
86-
import { createDLX } from '../src'
81+
import { DancingLinkX } from '@algorithm.ts/dlx'
8782

8883
const ebs = 1e-6
8984
const DL_TOTAL_COLUMNS = 9 * 9 * 4
9085
const DL_MAX_NODES = DL_TOTAL_COLUMNS * (9 * 9 * 9) + 10
91-
const dlx = createDLX(DL_MAX_NODES)
86+
const dlx = new DancingLinkX({ MAX_N: DL_MAX_NODES })
9287

9388
// Sudoku constraints.
9489
export enum SudokuConstraint {
@@ -98,12 +93,8 @@ If you are curious about this algorithm, you can visit [here][dlx] for more deta
9893
SUB = 3, // Sub(a, b): a-th sub-square matrix must have the number b
9994
}
10095

101-
export function solveSudoku(
102-
puzzle: ReadonlyArray<number[]>,
103-
solution: number[][],
104-
): boolean {
105-
const encode = (a: number, b: number, c: number): number =>
106-
a * 81 + b * 9 + c + 1
96+
export function solveSudoku(puzzle: ReadonlyArray<number[]>, solution: number[][]): boolean {
97+
const encode = (a: number, b: number, c: number): number => a * 81 + b * 9 + c + 1
10798

10899
dlx.init(DL_TOTAL_COLUMNS)
109100
const columns: number[] = new Array<number>(4)
@@ -136,6 +127,7 @@ If you are curious about this algorithm, you can visit [here][dlx] for more deta
136127
code = Math.floor(code / 9 + ebs)
137128
const a = code
138129

130+
// eslint-disable-next-line no-param-reassign
139131
solution[a][b] = c
140132
}
141133
return true
@@ -178,5 +170,5 @@ If you are curious about this algorithm, you can visit [here][dlx] for more deta
178170
* [洗牌问题和 dlx 算法][dlx]
179171

180172

181-
[homepage]: https://github.com/guanghechen/algorithm.ts/tree/release-2.x.x/packages/dlx#readme
182-
[dlx]: https://me.guanghechen.com/post/algorithm/shuffle/#heading-dlx
173+
[homepage]: https://github.com/guanghechen/algorithm.ts/tree/release-3.x.x/packages/dlx#readme
174+
[dlx]: https://me.guanghechen.com/post/algorithm/shuffle/#heading-dlx

Diff for: packages/dlx/__test__/dlx.spec.ts renamed to packages/dlx/__test__/DancingLinkX.spec.ts

+3-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { createDLX } from '@algorithm.ts/dlx'
1+
import { DancingLinkX } from '../src'
22
import multipleSudoku3x3 from './fixtures/sudoku9x9/multiple.json'
33
import uniqueSudoku3x3 from './fixtures/sudoku9x9/unique.json'
4-
import { solveSudoku } from './sudoku3x3'
4+
import { solveSudoku } from './sudoku9x9'
55

66
describe('dlx', function () {
77
describe('sudoku9x9', function () {
@@ -38,11 +38,9 @@ describe('dlx', function () {
3838
})
3939

4040
test('destroy', function () {
41-
const dlx = createDLX(10)
42-
41+
const dlx = new DancingLinkX({ MAX_N: 10 })
4342
dlx.init(10)
4443
dlx.destroy()
45-
4644
expect(dlx.solve()).toEqual(null)
4745
})
4846
})

Diff for: packages/dlx/__test__/sudoku3x3.ts renamed to packages/dlx/__test__/sudoku9x9.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { createDLX } from '../src'
1+
import { DancingLinkX } from '../src'
22

33
const ebs = 1e-6
44
const DL_TOTAL_COLUMNS = 9 * 9 * 4
55
const DL_MAX_NODES = DL_TOTAL_COLUMNS * (9 * 9 * 9) + 10
6-
const dlx = createDLX(DL_MAX_NODES)
6+
const dlx = new DancingLinkX({ MAX_N: DL_MAX_NODES })
77

88
// Sudoku constraints.
99
export enum SudokuConstraint {

Diff for: packages/dlx/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
},
99
"repository": {
1010
"type": "git",
11-
"url": "https://github.com/guanghechen/algorithm.ts/tree/release-2.x.x",
11+
"url": "https://github.com/guanghechen/algorithm.ts/tree/release-3.x.x",
1212
"directory": "packages/dlx"
1313
},
14-
"homepage": "https://github.com/guanghechen/algorithm.ts/tree/release-2.x.x/packages/dlx#readme",
14+
"homepage": "https://github.com/guanghechen/algorithm.ts/tree/release-3.x.x/packages/dlx#readme",
1515
"keywords": [
1616
"algorithm",
1717
"Dancing link",

Diff for: packages/dlx/src/DancingLinkX.ts

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
import type { IDancingLinkX } from '@algorithm.ts/dlx'
2+
3+
export interface IDancingLinkXProps {
4+
/**
5+
* Maximum number of nodes in the dancing-link.
6+
*/
7+
MAX_N: number
8+
}
9+
10+
/**
11+
* An implementation of the Dancing-Link algorithm.
12+
*
13+
* !!!NOTE
14+
* - Row number start from 1.
15+
* - Column number start from 1.
16+
*/
17+
export class DancingLinkX implements IDancingLinkX {
18+
protected readonly _count: number[] // the number of nodes of a column in the dancing-link
19+
protected readonly _row: number[] // the _row number of a node in the dancing-link
20+
protected readonly _col: number[] // the column number of a node in the dancing-link
21+
protected readonly _L: number[] // left pointer of cross-link list
22+
protected readonly _R: number[] // right pointer of cross-link list
23+
protected readonly _U: number[] // up pointer of cross-link list
24+
protected readonly _D: number[] // down pointer of cross-link list
25+
protected _sz: number // The number of nodes in the dancing-link (including the virtual nodes on the column)
26+
27+
constructor(props: IDancingLinkXProps) {
28+
const { MAX_N } = props
29+
this._count = new Array(MAX_N)
30+
this._row = new Array(MAX_N)
31+
this._col = new Array(MAX_N)
32+
this._L = new Array(MAX_N)
33+
this._R = new Array(MAX_N)
34+
this._U = new Array(MAX_N)
35+
this._D = new Array(MAX_N)
36+
this._sz = 0
37+
}
38+
39+
public destroy(): void {
40+
this._sz = 0
41+
this._count.length = 0
42+
this._row.length = 0
43+
this._col.length = 0
44+
this._L.length = 0
45+
this._R.length = 0
46+
this._U.length = 0
47+
this._D.length = 0
48+
}
49+
50+
public init(columnCount: number): void {
51+
const { _L, _R, _U, _D, _count } = this
52+
const _sz = columnCount + 1
53+
54+
for (let i = 0; i < _sz; ++i) {
55+
_L[i] = i - 1
56+
_R[i] = i + 1
57+
_U[i] = i
58+
_D[i] = i
59+
}
60+
_R[columnCount] = 0
61+
_L[0] = columnCount
62+
63+
_count.fill(0, 0, _sz)
64+
this._sz = _sz
65+
}
66+
67+
public addRow(r: number, columns: ReadonlyArray<number>): void {
68+
const { _count, _row, _col, _L, _R, _U, _D } = this
69+
let { _sz } = this
70+
const first = _sz
71+
for (let i = 0; i < columns.length; ++i, ++_sz) {
72+
const c = columns[i]
73+
_row[_sz] = r
74+
_col[_sz] = c
75+
_count[c] += 1
76+
77+
// Connect left and right nodes
78+
_L[_sz] = _sz - 1
79+
_R[_sz] = _sz + 1
80+
81+
// Connect top and bottom nodes,
82+
// c is the virtual node on the c-th column, and is also the head pointer
83+
// of the linked list of the column, so at this time _U[c] is the last
84+
// element of the column
85+
_D[_sz] = c
86+
_D[_U[c]] = _sz
87+
_U[_sz] = _U[c]
88+
_U[c] = _sz
89+
}
90+
91+
// Since this is a circular linked list, the first and last columns of the
92+
// current _row are connected to each other.
93+
_R[_sz - 1] = first
94+
_L[first] = _sz - 1
95+
this._sz = _sz
96+
}
97+
98+
public solve(): number[] | null {
99+
if (this._sz === 0) return null
100+
const selectedRowNos: number[] = []
101+
return this._algorithmX(0, selectedRowNos) ? selectedRowNos : null
102+
}
103+
104+
/**
105+
* Algorithm X.
106+
*
107+
* Recursively solve the problem of precise coverage, enumerate which rows are
108+
* selected in the recursive process, remove the selected rows and all the
109+
* columns on the rows, and restore these rows and columns during the
110+
* backtrack.
111+
*
112+
* @param dep recursion depth
113+
* @private
114+
*/
115+
protected _algorithmX(dep: number, selectedRowNos: number[]): boolean {
116+
const { _count, _row, _col, _L, _R, _D } = this
117+
118+
// Find a solution when the dancing-link is empty.
119+
if (_R[0] === 0) {
120+
// Clip the length of the solution.
121+
// eslint-disable-next-line no-param-reassign
122+
selectedRowNos.length = dep
123+
return true
124+
}
125+
126+
/**
127+
* Optimization: Find the column with the least number of nodes, and try to
128+
* cover from this column.
129+
*/
130+
let c = _R[0]
131+
for (let i = _R[0]; i !== 0; i = _R[i]) {
132+
if (_count[i] < _count[c]) c = i
133+
}
134+
135+
// Remove this column.
136+
this._removeColumn(c)
137+
for (let i = _D[c]; i !== c; i = _D[i]) {
138+
// eslint-disable-next-line no-param-reassign
139+
selectedRowNos[dep] = _row[i]
140+
for (let j = _R[i]; j !== i; j = _R[j]) this._removeColumn(_col[j])
141+
142+
// Recursively processing.
143+
if (this._algorithmX(dep + 1, selectedRowNos)) return true
144+
145+
// Backtrack.
146+
for (let j = _L[i]; j !== i; j = _L[j]) this._restoreColumn(_col[j])
147+
}
148+
// Backtrack.
149+
this._restoreColumn(c)
150+
151+
return false
152+
}
153+
154+
/**
155+
* Remove a column from the dancing-link.
156+
* @param c column number
157+
* @private
158+
*/
159+
protected _removeColumn(c: number): void {
160+
const { _count, _col, _L, _R, _U, _D } = this
161+
_L[_R[c]] = _L[c]
162+
_R[_L[c]] = _R[c]
163+
for (let i = _D[c]; i !== c; i = _D[i]) {
164+
for (let j = _R[i]; j !== i; j = _R[j]) {
165+
_U[_D[j]] = _U[j]
166+
_D[_U[j]] = _D[j]
167+
_count[_col[j]] -= 1
168+
}
169+
}
170+
}
171+
172+
/**
173+
* Restore a previously deleted column
174+
* @param c column number
175+
* @private
176+
*/
177+
protected _restoreColumn(c: number): void {
178+
const { _count, _col, _L, _R, _U, _D } = this
179+
for (let i = _U[c]; i !== c; i = _U[i]) {
180+
for (let j = _L[i]; j !== i; j = _L[j]) {
181+
_count[_col[j]] += 1
182+
_U[_D[j]] = j
183+
_D[_U[j]] = j
184+
}
185+
}
186+
_L[_R[c]] = c
187+
_R[_L[c]] = c
188+
}
189+
}

0 commit comments

Comments
 (0)