README.md
# Topological Sort / Dependency resolver in PHP
[![Build Status](https://travis-ci.org/marcj/topsort.php.svg)](https://travis-ci.org/marcj/topsort.php)
[![Code Climate](https://codeclimate.com/github/marcj/topsort.php/badges/gpa.svg?)](https://codeclimate.com/github/marcj/topsort.php)
[![Test Coverage](https://codeclimate.com/github/marcj/topsort.php/badges/coverage.svg?)](https://codeclimate.com/github/marcj/topsort.php)
This library provides several implementations of a Topological Sort (topSort).
In additional to the plain sorting algorithm it provides several implementations of a Grouped Topological Sort,
means you can pass items with a type which will be grouped together in the sorting. With its implementation
of using strings instead of arrays its over 20x faster than regular implementations.
## What is it?
A topological sort is useful for determining dependency loading. It tells you which elements need to be proceeded first
in order to fulfill all dependencies in the correct order.
Example usage: Unit of Work (relations), simple Package manager, Dependency Injection, ...
Examples:
```php
$sorter = new StringSort();
$sorter->add('car1', ['owner1', 'brand1']);
$sorter->add('brand1');
$sorter->add('brand2');
$sorter->add('owner1', ['brand1']);
$sorter->add('owner2', ['brand2']);
$result = $sorter->sort();
// output would be:
[
'brand1',
'owner1',
'car1',
'brand2',
'owner2'
]
```
Sometimes you want to group equal types together (imagine a UnitOfWork which wants to combine all elements from the
same type to stored those in one batch):
```php
$sorter = new GroupedStringSort();
$sorter->add('car1', 'car', ['owner1', 'brand1']);
$sorter->add('brand1', 'brand');
$sorter->add('brand2', 'brand');
$sorter->add('owner1', 'user', ['brand1']);
$sorter->add('owner2', 'user', ['brand2']);
$result = $sorter->sort();
// output would be:
[
'brand2',
'brand1',
'owner2',
'owner1',
'car1'
]
$groups = $sorter->getGroups();
[
{type: 'brand', level: 0, position: 0, length: 2},
{type: 'user', level: 1, position: 2, length: 2},
{type: 'car', level: 2, position: 4, length: 1},
]
//of course there may be several groups with the same type, if the dependency graphs makes this necessary.
foreach ($groups as $group) {
$firstItem = $result[$group->position];
$allItemsOfThisGroup = array_slice($result, $group->position, $group->length);
}
```
You can only store strings as elements.
To sort PHP objects you can stored its hash instead. `$sorter->add(spl_object_hash($obj1), [spl_object_hash($objt1Dep)])`.
## Installation
Use composer package: [marcj/topsort](https://packagist.org/packages/marcj/topsort)
```
{
"require": {
"marcj/topsort": "~0.1"
}
}
```
```php
include 'vendor/autoload.php';
$sorter = new GroupedStringSort;
$sorter->add(...);
$result = $sorter->sort();
```
## Implementations
tl;dr: Use `FixedArraySort` for normal topSort or `GroupedStringSort` for grouped topSort since its always the fastest
and has a good memory footprint.
### ArraySort
This is the most basic, most inefficient implementation of topSort using plain php arrays.
### FixedArraySort
This uses \SplFixedArray of php and is therefore much more memory friendly.
### StringSort
This uses a string as storage and has therefore no array overhead. It's thus a bit faster and has almost equal
memory footprint like FixedArraySort.
Small drawback: You can not store element ids containing a null byte.
### GroupedArraySort
This is the most basic, not so efficient implementation of grouped topSort using plain php arrays.
### GroupedStringSort
This uses a string as storage and has therefore no array operations overhead. It's extremely faster
and has better memory footprint than GroupedArraySort.
Small drawback: You can not store element ids containing a null byte.
## Benchmarks with PHP 7.0.9
Test data: 1/3 has two edges, 1/3 has one edge and 1/3 has no edges. Use the `benchmark` command in `./bin/console`
to play with it.
| Count | Implementation | Memory | Duration |
|-----------|----------------|--------------|----------|
| 50 | FixedArraySort | 0b | 0.0001s |
| 50 | ArraySort | 0b | 0.0001s |
| 50 | StringSort | 0b | 0.0001s |
| 1,000 | FixedArraySort | 53,432b | 0.0013s |
| 1,000 | ArraySort | 37,720b | 0.0012s |
| 1,000 | StringSort | 89,112b | 0.0013s |
| 10,000 | FixedArraySort | 692,464b | 0.0141s |
| 10,000 | ArraySort | 529,240b | 0.0138s |
| 10,000 | StringSort | 1,080,472b | 0.0154s |
| 100,000 | FixedArraySort | 5,800,200b | 0.1540s |
| 100,000 | ArraySort | 4,199,280b | 0.1499s |
| 100,000 | StringSort | 10,124,000b | 0.1645s |
| 1,000,000 | FixedArraySort | 49,561,888b | 1.5456s |
| 1,000,000 | ArraySort | 33,559,408b | 1.5597s |
| 1,000,000 | StringSort | 95,480,848b | 1.7942s |
| Count | Implementation | Memory | Duration |
|-----------|-------------------|--------------|-----------|
| 50 | GroupedArraySort | 0b | 0.0002s |
| 50 | GroupedStringSort | 0b | 0.0002s |
| 1,000 | GroupedArraySort | 112,280b | 0.0090s |
| 1,000 | GroupedStringSort | 99,440b | 0.0025s |
| 10,000 | GroupedArraySort | 1,431,320b | 0.8385s |
| 10,000 | GroupedStringSort | 1,176,304b | 0.0267s |
| 100,000 | GroupedArraySort | 12,318,072b | 132.9709s |
| 100,000 | GroupedStringSort | 11,129,144b | 0.2788s |
| 1,000,000 | GroupedArraySort | - | too long |
| 1,000,000 | GroupedStringSort | 106,488,496b | 3.0879s |