Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
## Sort

* Bubble Sort
* Quick Sort
92 changes: 92 additions & 0 deletions src/Sort/Quick.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Halnique\Algorithms\Sort;


class Quick implements Sortable
{
/**
* 1. ピボットとして一つ選びそれをPとする。
* 2. 左から順に値を調べ、P以上のものを見つけたらその位置をiとする。
* 3. 右から順に値を調べ、P以下のものを見つけたらその位置をjとする。
* 4. iがjより左にあるのならばその二つの位置を入れ替え、2に戻る。ただし、次の2での探索はiの一つ右、次の3での探索はjの一つ左から行う。
* 5. 2に戻らなかった場合、iの左側を境界に分割を行って2つの領域に分け、そのそれぞれに対して再帰的に1からの手順を行う。要素数が1以下の領域ができた場合、その領域は確定とする。
*
* @param array $items
* @return array
*/
public function sort(array $items): array
{
return $this->sortRecursive($items, 0, count($items) - 1, $items[array_rand($items)]);
}

private function sortRecursive(array $items, int $start, int $end, $pivot): array
{
$leftIndex = $this->searchLeft($items, $pivot, $start);
$rightIndex = $this->searchRight($items, $pivot, $end);

if ($leftIndex < $rightIndex) {
$items = $this->swap($items, $leftIndex, $rightIndex);
return $this->sortRecursive($items, $leftIndex + 1, $rightIndex - 1, $pivot);
}

[$leftItems, $rightItems] = $this->slice($items, $leftIndex);

if (count($leftItems) > 1) {
$leftItems = $this->sort($leftItems);
}

if (count($rightItems) > 1) {
$rightItems = $this->sort($rightItems);
}

return array_merge($leftItems, $rightItems);
}

private function searchLeft(array $items, $pivot, int $offset): int
{
$length = count($items);
for ($index = $offset; $index < $length; $index++) {
$item = $items[$index];

if ($item < $pivot) {
continue;
}

break;
}

return $index;
}

private function searchRight(array $items, $pivot, int $offset): int
{
for ($index = $offset; $index >= 0; $index--) {
$item = $items[$index];

if ($item > $pivot) {
continue;
}

break;
}

return $index;
}

private function swap(array $items, int $baseIndex, int $targetIndex): array
{
$baseItem = $items[$baseIndex];
$targetItem = $items[$targetIndex];
$items[$baseIndex] = $targetItem;
$items[$targetIndex] = $baseItem;
return $items;
}

private function slice(array $items, int $index): array
{
$leftItems = array_slice($items, 0, $index);
$rightItems = array_slice($items, $index);
return [$leftItems, $rightItems];
}
}
52 changes: 52 additions & 0 deletions tests/Sort/QuickTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace HalniqueTest\Algorithms\Sort;

use Halnique\Algorithms\Sort\Quick;
use HalniqueTest\Algorithms\TestCase;


class QuickTest extends TestCase
{
/** @var Quick */
private $quick;

protected function setUp(): void
{
parent::setUp();

$this->quick = new Quick();
}

public function sortDataProvider(): array
{
return [
'pattern int 1' => [
'actual' => [4, 1, 3, 2],
'expected' => [1, 2, 3, 4],
],
'pattern int 2' => [
'actual' => [7, 17, 5, 3, 13, 2, 11],
'expected' => [2, 3, 5, 7, 11, 13, 17],
],
'pattern float 1' => [
'actual' => [2.4, 2.7, 0.3, 1.1, 1.0],
'expected' => [0.3, 1.0, 1.1, 2.4, 2.7],
],
'pattern string 1' => [
'actual' => ['monster', 'drink', 'channel', 'speaker', 'towel', 'phone'],
'expected' => ['channel', 'drink', 'monster', 'phone', 'speaker', 'towel'],
],
];
}

/**
* @dataProvider sortDataProvider
* @param array $actual
* @param array $expected
*/
public function testSort(array $actual, array $expected)
{
$this->assertEquals($expected, $this->quick->sort($actual));
}
}