Skip to content

Commit ac7dae9

Browse files
committed
Initial commit
0 parents  commit ac7dae9

13 files changed

+981
-0
lines changed

.github/dependabot.yml

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
2+
3+
version: 2
4+
updates:
5+
- package-ecosystem: "composer"
6+
directory: "/"
7+
schedule:
8+
interval: "daily"
9+
- package-ecosystem: "github-actions"
10+
directory: "/"
11+
schedule:
12+
interval: "daily"

.github/workflows/build.yml

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# https://help.github.com/en/categories/automating-your-workflow-with-github-actions
2+
3+
name: "Build"
4+
5+
on:
6+
pull_request:
7+
push:
8+
branches:
9+
- "main"
10+
11+
jobs:
12+
tests:
13+
name: "Tests"
14+
15+
runs-on: "ubuntu-latest"
16+
17+
strategy:
18+
matrix:
19+
php-version:
20+
- "8.2"
21+
- "8.3"
22+
23+
steps:
24+
- name: "Checkout"
25+
uses: "actions/checkout@v4"
26+
27+
- name: "Install PHP"
28+
uses: "shivammathur/setup-php@v2"
29+
with:
30+
coverage: "none"
31+
php-version: "${{ matrix.php-version }}"
32+
33+
- name: "Cache dependencies"
34+
uses: "actions/cache@v4"
35+
with:
36+
path: "~/.composer/cache"
37+
key: "php-${{ matrix.php-version }}-composer-${{ hashFiles('**/composer.json') }}"
38+
restore-keys: "php-${{ matrix.php-version }}-composer-"
39+
40+
- name: "Install highest dependencies"
41+
run: "composer update --prefer-dist --no-interaction --no-progress --no-suggest"
42+
43+
- name: "Code Style"
44+
run: "vendor/bin/phpcs"
45+
46+
- name: "Static Analysis"
47+
run: "vendor/bin/phpstan analyze"
48+
49+
- name: "Rector"
50+
run: "vendor/bin/rector --dry-run"
51+
52+
- name: "Tests"
53+
run: "vendor/bin/phpunit"

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/.phpunit.cache/
2+
/vendor/
3+
.phpcs-cache
4+
composer.lock

LICENSE

+504
Large diffs are not rendered by default.

README.md

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Tree
2+
3+
A PHP library for working with tree data structures.
4+
5+
## Installation
6+
7+
```shell
8+
$ composer require plook/tree
9+
```
10+
11+
## Working with trees that are stored in arrays
12+
13+
### Traversing a left/right tree that is ordered by the left value
14+
15+
```php
16+
$tree = [
17+
['id' => 1, 'left' => 1, 'right' => 14],
18+
['id' => 2, 'left' => 2, 'right' => 7],
19+
['id' => 3, 'left' => 3, 'right' => 4],
20+
['id' => 4, 'left' => 5, 'right' => 6],
21+
['id' => 5, 'left' => 8, 'right' => 13],
22+
['id' => 6, 'left' => 9, 'right' => 10],
23+
['id' => 7, 'left' => 11, 'right' => 12],
24+
];
25+
26+
/** @var TraverseLeftRightTree<array{id: int, left: int, right: int}> $traverse */
27+
$traverse = new TraverseLeftRightTree();
28+
29+
$traverse(
30+
$tree,
31+
'left',
32+
'right',
33+
static function (array $node): void {
34+
echo sprintf('Processing node %s%s', $node['id'], PHP_EOL);
35+
},
36+
static function (array $node): void {
37+
echo sprintf('Childrens of node %s have been processed%s', $node['id'], PHP_EOL);
38+
},
39+
);
40+
```
41+
42+
```text
43+
Processing node 1
44+
Processing node 2
45+
Processing node 3
46+
Childrens of node 3 have been processed
47+
Processing node 4
48+
Childrens of node 4 have been processed
49+
Childrens of node 2 have been processed
50+
Processing node 5
51+
Processing node 6
52+
Childrens of node 6 have been processed
53+
Processing node 7
54+
Childrens of node 7 have been processed
55+
Childrens of node 5 have been processed
56+
Childrens of node 1 have been processed
57+
```

composer.json

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "plook/tree",
3+
"type": "library",
4+
"description": "Library to work with tree data structures.",
5+
"license": "LGPL-2.1-or-later",
6+
"authors": [
7+
{
8+
"name": "Phillip Look",
9+
"email": "[email protected]"
10+
}
11+
],
12+
"require": {
13+
"php": "^8.2"
14+
},
15+
"require-dev": {
16+
"brainbits/phpcs-standard": "^7.0",
17+
"ergebnis/phpstan-rules": "^2.2.0",
18+
"thecodingmachine/phpstan-safe-rule": "^1.2.0",
19+
"thecodingmachine/phpstan-strict-rules": "^1.0.0",
20+
"phpstan/phpstan-phpunit": "^1.3.15",
21+
"squizlabs/php_codesniffer": "^3.9.0",
22+
"phpstan/phpstan": "^1.10.58",
23+
"phpunit/phpunit": "^11.0.3",
24+
"rector/rector": "^1.0.1"
25+
},
26+
"autoload": {
27+
"psr-4": {
28+
"Plook\\Tree\\": "src/"
29+
}
30+
},
31+
"autoload-dev": {
32+
"psr-4": {
33+
"Plook\\Tests\\Tree\\": "tests/"
34+
}
35+
},
36+
"config": {
37+
"allow-plugins": {
38+
"dealerdirect/phpcodesniffer-composer-installer": true
39+
}
40+
}
41+
}

examples/traverse_left_right_tree.php

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
require_once dirname(__DIR__) . '/vendor/autoload.php';
6+
7+
use Plook\Tree\Array\TraverseLeftRightTree;
8+
9+
$tree = [
10+
['id' => 1, 'left' => 1, 'right' => 14],
11+
['id' => 2, 'left' => 2, 'right' => 7],
12+
['id' => 3, 'left' => 3, 'right' => 4],
13+
['id' => 4, 'left' => 5, 'right' => 6],
14+
['id' => 5, 'left' => 8, 'right' => 13],
15+
['id' => 6, 'left' => 9, 'right' => 10],
16+
['id' => 7, 'left' => 11, 'right' => 12],
17+
];
18+
19+
/** @var TraverseLeftRightTree<array{id: int, left: int, right: int}> $traverse */
20+
$traverse = new TraverseLeftRightTree();
21+
22+
$traverse(
23+
$tree,
24+
'left',
25+
'right',
26+
static function (array $node): void {
27+
echo sprintf('Processing node %s%s', $node['id'], PHP_EOL);
28+
},
29+
static function (array $node): void {
30+
echo sprintf('Childrens of node %s have been processed%s', $node['id'], PHP_EOL);
31+
},
32+
);

phpcs.xml.dist

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0"?>
2+
<ruleset name="plook">
3+
<description>The plook coding standard.</description>
4+
5+
<file>examples</file>
6+
<file>src</file>
7+
<file>tests</file>
8+
<file>rector.php</file>
9+
10+
<!-- Start here -->
11+
<arg name="basepath" value="."/>
12+
<!-- Only .php -->
13+
<arg name="extensions" value="php"/>
14+
<!-- 10 parallel -->
15+
<arg name="parallel" value="10"/>
16+
<!-- User cache dir -->
17+
<arg name="cache" value=".phpcs-cache"/>
18+
<!-- Show colors -->
19+
<arg name="colors"/>
20+
<!-- Show progress -->
21+
<arg value="p"/>
22+
<!-- Show sniff names -->
23+
<arg value="s"/>
24+
25+
<rule ref="Brainbits"/>
26+
<rule ref="Generic.Commenting.Todo.TaskFound" />
27+
</ruleset>

phpstan.neon.dist

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
parameters:
2+
level: max
3+
paths:
4+
- examples
5+
- src
6+
- tests
7+
- rector.php
8+
# excludePaths:
9+
# ignoreErrors:
10+
# ergebnis:
11+
# noExtends:
12+
# classesAllowedToBeExtended:
13+
14+
includes:
15+
# - vendor/brainbits/phpstan-rules/rules.neon
16+
- vendor/ergebnis/phpstan-rules/rules.neon
17+
- vendor/phpstan/phpstan-phpunit/extension.neon
18+
- vendor/phpstan/phpstan-phpunit/rules.neon
19+
- vendor/thecodingmachine/phpstan-strict-rules/phpstan-strict-rules.neon

phpunit.xml.dist

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.0/phpunit.xsd"
4+
bootstrap="vendor/autoload.php"
5+
cacheDirectory=".phpunit.cache"
6+
executionOrder="depends,defects"
7+
requireCoverageMetadata="true"
8+
beStrictAboutCoverageMetadata="true"
9+
beStrictAboutOutputDuringTests="true"
10+
failOnRisky="true"
11+
failOnWarning="true">
12+
<testsuites>
13+
<testsuite name="default">
14+
<directory>tests</directory>
15+
</testsuite>
16+
</testsuites>
17+
18+
<source restrictDeprecations="true" restrictNotices="true" restrictWarnings="true">
19+
<include>
20+
<directory>src</directory>
21+
</include>
22+
</source>
23+
</phpunit>

rector.php

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Rector\Config\RectorConfig;
6+
use Rector\Naming\Rector\Class_\RenamePropertyToMatchTypeRector;
7+
use Rector\Set\ValueObject\LevelSetList;
8+
use Rector\Set\ValueObject\SetList;
9+
10+
return static function (RectorConfig $rectorConfig): void {
11+
$rectorConfig->paths([
12+
__DIR__ . '/examples',
13+
__DIR__ . '/src',
14+
__DIR__ . '/tests',
15+
__DIR__ . '/rector.php',
16+
]);
17+
18+
$rectorConfig->sets([
19+
LevelSetList::UP_TO_PHP_82,
20+
SetList::CODE_QUALITY,
21+
SetList::CODING_STYLE,
22+
SetList::DEAD_CODE,
23+
SetList::STRICT_BOOLEANS,
24+
SetList::NAMING,
25+
SetList::PRIVATIZATION,
26+
SetList::TYPE_DECLARATION,
27+
SetList::EARLY_RETURN,
28+
SetList::INSTANCEOF,
29+
]);
30+
31+
$rectorConfig->skip([
32+
RenamePropertyToMatchTypeRector::class => [
33+
__DIR__ . '/tests',
34+
],
35+
]);
36+
};

src/Array/TraverseLeftRightTree.php

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Plook\Tree\Array;
6+
7+
use function array_pop;
8+
use function count;
9+
10+
/** @template T of array */
11+
final class TraverseLeftRightTree
12+
{
13+
/**
14+
* @param T[] $tree
15+
* @param callable(T): void $beforeChildren
16+
* @param callable(T): void $afterChildren
17+
*/
18+
public function __invoke(
19+
array $tree,
20+
int|string $leftKey,
21+
int|string $rightKey,
22+
callable $beforeChildren,
23+
callable $afterChildren,
24+
): void {
25+
$parents = [];
26+
27+
foreach ($tree as $node) {
28+
while ($parents !== [] && $parents[count($parents) - 1][$rightKey] < $node[$leftKey]) {
29+
$afterChildren(array_pop($parents));
30+
}
31+
32+
$beforeChildren($node);
33+
34+
$parents[] = $node;
35+
}
36+
37+
while ($parents !== []) {
38+
$afterChildren(array_pop($parents));
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)