packages/ztd-query-core/src/Shadow/Row/RowMultiset.php
1<?php
2
3declare(strict_types=1);
4
5namespace ZtdQuery\Shadow\Row;
6
7use ZtdQuery\Schema\TableDefinition;
8
9/**
10 * Rows counted with their repeats: what one set of rows has that another does not.
11 *
12 * A table may hold the same row twice, so comparing two states of it cannot be
13 * done with set arithmetic — a row that appears three times before and once
14 * after has been removed twice. Every row here is paired off against at most
15 * one row on the other side, and what stays unpaired is the difference.
16 *
17 * Column order is not part of a row's identity: the same columns carrying the
18 * same values are the same row however the reader happened to order them.
19 *
20 * @phpstan-import-type Row from TableDefinition
21 */
22final class RowMultiset
23{
24 /**
25 * @param RowMatch $match Decides when two rows are the same row
26 */
27 public function __construct(private readonly RowMatch $match = new RowMatch())
28 {
29 }
30
31 /**
32 * Answers the rows on the left that nothing on the right pairs with.
33 *
34 * @param array<int, Row> $left Rows to account for
35 * @param array<int, Row> $right Rows they are paired off against
36 *
37 * @return list<Row> The unpaired rows, in the order the left held them
38 */
39 public function difference(array $left, array $right): array
40 {
41 $remaining = $right;
42 $difference = [];
43 foreach ($left as $row) {
44 $paired = null;
45 foreach ($remaining as $index => $candidate) {
46 if ($this->match->sameRow($row, $candidate)) {
47 $paired = $index;
48 break;
49 }
50 }
51 if ($paired === null) {
52 $difference[] = $row;
53 continue;
54 }
55 unset($remaining[$paired]);
56 }
57
58 return $difference;
59 }
60}
61