Skip to content

Commit

Permalink
Change the sorting algorithm to qsort
Browse files Browse the repository at this point in the history
  • Loading branch information
zaerl committed Nov 28, 2024
1 parent fb33502 commit 85c850b
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 103 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ public function map_post( $byte_offset, $data ) {
--$this->orphan_post_counter;
}

// This is an array saved as: [ parent, byte_offset, moved ], to save space and not using an associative one.
// This is an array saved as: [ parent, byte_offset ], to save
// space and not using an associative one.
$this->posts[ $data['post_id'] ] = array(
$data['post_parent'],
$byte_offset,
false,
);
}

Expand Down Expand Up @@ -120,110 +120,58 @@ public function is_sorted() {
*
* Sorted posts will be stored as attachments and posts/pages separately.
*/
public function sort_topologically( $empty_memory = true ) {
public function sort_topologically( $free_space = true ) {
foreach ( $this->categories as $slug => $category ) {
$this->topological_category_sort( $slug, $category );
}

$this->sort_parent_child( $this->posts );
$this->sort_elements( $this->posts );

// Empty some memory.
if ( $empty_memory ) {
// Free some space.
if ( $free_space ) {
/**
* @TODO: all the elements that have not been moved can be flushed away.
*/
foreach ( $this->posts as $id => $element ) {
if ( ! $element[2] ) {
// The element have not been moved, unset it.
unset( $this->posts[ $id ] );
} else {
// Save only the byte offset.
$this->posts[ $id ] = $element[1];
}
// Save only the byte offset.
$this->posts[ $id ] = $element[1];
}
}

$this->sorted = true;
}

/**
* Recursive topological sorting.
* @todo Check for circular dependencies.
*
* @param array $elements The elements to sort.
* Recursive sort elements. Posts with parents will be moved to the correct position.
*
* @return void
* @return true
*/
private function sort_parent_child( &$elements ) {
// Sort the array in-place.
// reset( $elements );
$position = 0; // key( $elements );
$length = count( $elements );

if ( $length < 2 ) {
// No need to sort.
return;
}

if ( 2 === $length ) {
$keys = array_keys( $elements );

// First element has a parent and is the second.
if ( $elements[ $keys[0] ][0] && $keys[1] === $elements[ $keys[0] ][0] ) {
// Swap.
$elements = array_reverse( $elements, true );

// Set the second as 'moved'.
$elements[ $keys[1] ][2] = true;
private function sort_elements( &$elements ) {
$sort_callback = function ( $a, $b ) use ( &$elements ) {
$parent_a = $elements[ $a ][0];
$parent_b = $elements[ $b ][0];

if ( ! $parent_a && ! $parent_b ) {
// No parents.
return 0;
} elseif ( $a === $parent_b ) {
// A is the parent of B.
return -1;
} elseif ( $b === $parent_a ) {
// B is the parent of A.
return 1;
}

return;
}

foreach ( $elements as $id => $element ) {
if ( empty( $element[0] ) ) {
$this->move_element( $elements, $id, $position );
}
}
}

/**
* Move an element to a new position.
*
* @param array $elements The elements to sort.
* @param int $id The ID of the element to move.
* @param int $position The new position of the element.
*
* @return void
*/
private function move_element( &$elements, $id, &$position ) {
if ( ! isset( $elements[ $id ] ) ) {
return;
}

$element = $elements[ $id ];
return 0;
};

if ( $id < $position ) {
// Already in the correct position.
return;
}

// Move the element to the current position.
unset( $elements[ $id ] );

// Set as 'moved'.
$element[2] = true;

// Generate the new array.
$elements = array_slice( $elements, 0, $position, true ) +
array( $id => $element ) +
array_slice( $elements, $position, null, true );

++$position;

// Move children.
foreach ( $elements as $child_id => $child_element ) {
if ( $id === $child_element[0] ) {
$this->move_element( $elements, $child_id, $position );
}
}
/**
* @TODO: PHP uses quicksort: https://github.com/php/php-src/blob/master/Zend/zend_sort.c
* WordPress export posts by ID and so are likely to be already in order.
* Quicksort performs badly on already sorted arrays, O(n^2) is the worst case.
* Let's consider using a different sorting algorithm.
*/
uksort( $elements, $sort_callback );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ public function test_parent_after_child() {
$sorter->map_post( 20, $this->generate_post( 2, 0 ) );
$sorter->sort_topologically();

$this->assertEquals( array( 2 => 20 ), $sorter->posts );
$this->assertFalse( $sorter->get_byte_offset( 1 ) );
$this->assertEquals( array( 2 => 20, 1 => 10 ), $sorter->posts );
$this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
$this->assertEquals( 20, $sorter->get_byte_offset( 2 ) );
$this->assertFalse( $sorter->is_sorted() );
}

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

$this->assertEquals( array(), $sorter->posts );
$this->assertFalse( $sorter->get_byte_offset( 1 ) );
$this->assertEquals( array( 1 => 10, 2 => 20, 3 => 30 ), $sorter->posts );
$this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
}

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

$this->assertEquals( array( 2 => 20 ), $sorter->posts );
$this->assertEquals( array( 1 => 10, 2 => 20 ), $sorter->posts );
$this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
$this->assertEquals( 20, $sorter->get_byte_offset( 2 ) );
}

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

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

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

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

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

$this->multiple_map_posts( $sorter, array( 3, 1, 2 ) );
$this->multiple_map_posts( $sorter, array( 2, 3, 0 ) );
$sorter->sort_topologically();

$this->assertEquals( array( 3 => 10 ), $sorter->posts );

// $this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
// $this->assertEquals( 20, $sorter->get_byte_offset( 2 ) );
// $this->assertEquals( 30, $sorter->get_byte_offset( 3 ) );
$this->assertEquals( array( 3 => 30, 2 => 20, 1 => 10 ), $sorter->posts );

$this->assertFalse( $sorter->get_byte_offset( 1 ) );
$this->assertFalse( $sorter->is_sorted() );
$this->assertEquals( 10, $sorter->get_byte_offset( 1 ) );
$this->assertEquals( 20, $sorter->get_byte_offset( 2 ) );
$this->assertEquals( 30, $sorter->get_byte_offset( 3 ) );
$this->assertCount( 0, $sorter->posts );
}

/**
Expand Down

0 comments on commit 85c850b

Please sign in to comment.