-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSlice.php
More file actions
102 lines (86 loc) · 2.88 KB
/
Copy pathSlice.php
File metadata and controls
102 lines (86 loc) · 2.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
<?php
declare(strict_types=1);
namespace PhpMlKit\NDArray;
use PhpMlKit\NDArray\Exceptions\IndexException;
/**
* Value object representing a slice operation (start:stop:step).
*
* Handles parsing of string slice syntax (e.g., "0:5", "::2", ":") and
* resolution of indices against a specific dimension size, following
* NumPy-compatible rules.
*/
final class Slice
{
public function __construct(
public readonly ?int $start,
public readonly ?int $stop,
public readonly int $step = 1
) {
if (0 === $step) {
throw new IndexException('Slice step cannot be zero');
}
if ($step < 0) {
throw new IndexException('Negative slice steps are not yet supported');
}
}
/**
* Parse a slice string into a Slice object.
*
* Formats:
* ":" -> start=null, stop=null, step=1
* "i:j" -> start=i, stop=j, step=1
* "i:j:k" -> start=i, stop=j, step=k
* "::k" -> start=null, stop=null, step=k
*/
public static function parse(string $spec): self
{
$parts = explode(':', $spec);
$count = \count($parts);
if ($count > 3) {
throw new IndexException("Invalid slice syntax '{$spec}': too many colons");
}
// Helper to convert empty strings to null, numeric strings to int
$toInt = static fn (string $s, string $ctx): ?int => '' === $s ? null : (
is_numeric($s) ? (int) $s : throw new IndexException("Invalid slice component '{$s}' in '{$spec}'")
);
$start = $toInt(trim($parts[0]), 'start');
$stop = isset($parts[1]) ? $toInt(trim($parts[1]), 'stop') : null;
$step = isset($parts[2]) ? $toInt(trim($parts[2]), 'step') : 1;
// If step is not provided in string, default is 1
return new self($start, $stop, $step ?? 1);
}
/**
* Resolve slice indices against a dimension size.
*
* Returns the concrete start, stop, step, and the number of elements (shape).
*
* @param int $dimSize Size of the dimension
*
* @return array{start: int, stop: int, step: int, shape: int}
*/
public function resolve(int $dimSize): array
{
$step = $this->step;
$start = $this->start ?? 0;
$stop = $this->stop ?? $dimSize;
if ($start < 0) {
$start += $dimSize;
}
if ($stop < 0) {
$stop += $dimSize;
}
$start = max(0, min($dimSize, $start));
$stop = max(0, min($dimSize, $stop));
if ($start >= $stop) {
return ['start' => $start, 'stop' => $start, 'step' => $step, 'shape' => 0];
}
$diff = $stop - $start;
$shape = (int) \ceil($diff / $step);
return [
'start' => $start,
'stop' => $stop,
'step' => $step,
'shape' => $shape,
];
}
}