Skip to content

Commit 85c850b

Browse files
committed
Change the sorting algorithm to qsort
1 parent fb33502 commit 85c850b

File tree

2 files changed

+51
-103
lines changed

2 files changed

+51
-103
lines changed

packages/playground/data-liberation/src/import/WP_Topological_Sorter.php

+36-88
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,11 @@ public function map_post( $byte_offset, $data ) {
7272
--$this->orphan_post_counter;
7373
}
7474

75-
// This is an array saved as: [ parent, byte_offset, moved ], to save space and not using an associative one.
75+
// This is an array saved as: [ parent, byte_offset ], to save
76+
// space and not using an associative one.
7677
$this->posts[ $data['post_id'] ] = array(
7778
$data['post_parent'],
7879
$byte_offset,
79-
false,
8080
);
8181
}
8282

@@ -120,110 +120,58 @@ public function is_sorted() {
120120
*
121121
* Sorted posts will be stored as attachments and posts/pages separately.
122122
*/
123-
public function sort_topologically( $empty_memory = true ) {
123+
public function sort_topologically( $free_space = true ) {
124124
foreach ( $this->categories as $slug => $category ) {
125125
$this->topological_category_sort( $slug, $category );
126126
}
127127

128-
$this->sort_parent_child( $this->posts );
128+
$this->sort_elements( $this->posts );
129129

130-
// Empty some memory.
131-
if ( $empty_memory ) {
130+
// Free some space.
131+
if ( $free_space ) {
132+
/**
133+
* @TODO: all the elements that have not been moved can be flushed away.
134+
*/
132135
foreach ( $this->posts as $id => $element ) {
133-
if ( ! $element[2] ) {
134-
// The element have not been moved, unset it.
135-
unset( $this->posts[ $id ] );
136-
} else {
137-
// Save only the byte offset.
138-
$this->posts[ $id ] = $element[1];
139-
}
136+
// Save only the byte offset.
137+
$this->posts[ $id ] = $element[1];
140138
}
141139
}
142140

143141
$this->sorted = true;
144142
}
145143

146144
/**
147-
* Recursive topological sorting.
148-
* @todo Check for circular dependencies.
149-
*
150-
* @param array $elements The elements to sort.
145+
* Recursive sort elements. Posts with parents will be moved to the correct position.
151146
*
152-
* @return void
147+
* @return true
153148
*/
154-
private function sort_parent_child( &$elements ) {
155-
// Sort the array in-place.
156-
// reset( $elements );
157-
$position = 0; // key( $elements );
158-
$length = count( $elements );
159-
160-
if ( $length < 2 ) {
161-
// No need to sort.
162-
return;
163-
}
164-
165-
if ( 2 === $length ) {
166-
$keys = array_keys( $elements );
167-
168-
// First element has a parent and is the second.
169-
if ( $elements[ $keys[0] ][0] && $keys[1] === $elements[ $keys[0] ][0] ) {
170-
// Swap.
171-
$elements = array_reverse( $elements, true );
172-
173-
// Set the second as 'moved'.
174-
$elements[ $keys[1] ][2] = true;
149+
private function sort_elements( &$elements ) {
150+
$sort_callback = function ( $a, $b ) use ( &$elements ) {
151+
$parent_a = $elements[ $a ][0];
152+
$parent_b = $elements[ $b ][0];
153+
154+
if ( ! $parent_a && ! $parent_b ) {
155+
// No parents.
156+
return 0;
157+
} elseif ( $a === $parent_b ) {
158+
// A is the parent of B.
159+
return -1;
160+
} elseif ( $b === $parent_a ) {
161+
// B is the parent of A.
162+
return 1;
175163
}
176164

177-
return;
178-
}
179-
180-
foreach ( $elements as $id => $element ) {
181-
if ( empty( $element[0] ) ) {
182-
$this->move_element( $elements, $id, $position );
183-
}
184-
}
185-
}
186-
187-
/**
188-
* Move an element to a new position.
189-
*
190-
* @param array $elements The elements to sort.
191-
* @param int $id The ID of the element to move.
192-
* @param int $position The new position of the element.
193-
*
194-
* @return void
195-
*/
196-
private function move_element( &$elements, $id, &$position ) {
197-
if ( ! isset( $elements[ $id ] ) ) {
198-
return;
199-
}
200-
201-
$element = $elements[ $id ];
165+
return 0;
166+
};
202167

203-
if ( $id < $position ) {
204-
// Already in the correct position.
205-
return;
206-
}
207-
208-
// Move the element to the current position.
209-
unset( $elements[ $id ] );
210-
211-
// Set as 'moved'.
212-
$element[2] = true;
213-
214-
// Generate the new array.
215-
$elements = array_slice( $elements, 0, $position, true ) +
216-
array( $id => $element ) +
217-
array_slice( $elements, $position, null, true );
218-
219-
++$position;
220-
221-
// Move children.
222-
foreach ( $elements as $child_id => $child_element ) {
223-
if ( $id === $child_element[0] ) {
224-
$this->move_element( $elements, $child_id, $position );
225-
}
226-
}
168+
/**
169+
* @TODO: PHP uses quicksort: https://github.com/php/php-src/blob/master/Zend/zend_sort.c
170+
* WordPress export posts by ID and so are likely to be already in order.
171+
* Quicksort performs badly on already sorted arrays, O(n^2) is the worst case.
172+
* Let's consider using a different sorting algorithm.
173+
*/
174+
uksort( $elements, $sort_callback );
227175
}
228176

229177
/**

packages/playground/data-liberation/tests/WPTopologicalSorterTests.php

+15-15
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,10 @@ public function test_parent_after_child() {
2222
$sorter->map_post( 20, $this->generate_post( 2, 0 ) );
2323
$sorter->sort_topologically();
2424

25-
$this->assertEquals( array( 2 => 20 ), $sorter->posts );
26-
$this->assertFalse( $sorter->get_byte_offset( 1 ) );
25+
$this->assertEquals( array( 2 => 20, 1 => 10 ), $sorter->posts );
26+
$this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
2727
$this->assertEquals( 20, $sorter->get_byte_offset( 2 ) );
28+
$this->assertFalse( $sorter->is_sorted() );
2829
}
2930

3031
public function test_child_after_parent() {
@@ -35,8 +36,8 @@ public function test_child_after_parent() {
3536
$sorter->map_post( 30, $this->generate_post( 3, 2 ) );
3637
$sorter->sort_topologically();
3738

38-
$this->assertEquals( array(), $sorter->posts );
39-
$this->assertFalse( $sorter->get_byte_offset( 1 ) );
39+
$this->assertEquals( array( 1 => 10, 2 => 20, 3 => 30 ), $sorter->posts );
40+
$this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
4041
}
4142

4243
public function test_orphaned_post() {
@@ -46,7 +47,8 @@ public function test_orphaned_post() {
4647
$sorter->map_post( 20, $this->generate_post( 2, 0 ) );
4748
$sorter->sort_topologically();
4849

49-
$this->assertEquals( array( 2 => 20 ), $sorter->posts );
50+
$this->assertEquals( array( 1 => 10, 2 => 20 ), $sorter->posts );
51+
$this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
5052
$this->assertEquals( 20, $sorter->get_byte_offset( 2 ) );
5153
}
5254

@@ -58,7 +60,7 @@ public function test_chain_parent_child_after() {
5860
$sorter->map_post( 30, $this->generate_post( 3, 0 ) );
5961
$sorter->sort_topologically();
6062

61-
$this->assertEquals( array( 3 => 30, 2 => 20 ), $sorter->posts );
63+
$this->assertEquals( array( 3 => 30, 2 => 20, 1 => 10 ), $sorter->posts );
6264
}
6365

6466
public function test_reverse_order() {
@@ -67,23 +69,21 @@ public function test_reverse_order() {
6769
$this->multiple_map_posts( $sorter, array( 3, 2, 1 ) );
6870
$sorter->sort_topologically();
6971

70-
$this->assertEquals( array(), $sorter->posts );
72+
$this->assertEquals( array( 1 => 10, 2 => 20, 3 => 30 ), $sorter->posts );
7173
}
7274

7375
public function test_get_byte_offsets_consume_array() {
7476
$sorter = new WP_Topological_Sorter();
7577

76-
$this->multiple_map_posts( $sorter, array( 3, 1, 2 ) );
78+
$this->multiple_map_posts( $sorter, array( 2, 3, 0 ) );
7779
$sorter->sort_topologically();
7880

79-
$this->assertEquals( array( 3 => 10 ), $sorter->posts );
80-
81-
// $this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
82-
// $this->assertEquals( 20, $sorter->get_byte_offset( 2 ) );
83-
// $this->assertEquals( 30, $sorter->get_byte_offset( 3 ) );
81+
$this->assertEquals( array( 3 => 30, 2 => 20, 1 => 10 ), $sorter->posts );
8482

85-
$this->assertFalse( $sorter->get_byte_offset( 1 ) );
86-
$this->assertFalse( $sorter->is_sorted() );
83+
$this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
84+
$this->assertEquals( 20, $sorter->get_byte_offset( 2 ) );
85+
$this->assertEquals( 30, $sorter->get_byte_offset( 3 ) );
86+
$this->assertCount( 0, $sorter->posts );
8787
}
8888

8989
/**

0 commit comments

Comments
 (0)