flpit 0.1.7__tar.gz → 0.2.0.dev2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {flpit-0.1.7 → flpit-0.2.0.dev2}/PKG-INFO +74 -21
- {flpit-0.1.7 → flpit-0.2.0.dev2}/README.md +73 -20
- {flpit-0.1.7 → flpit-0.2.0.dev2}/pyproject.toml +2 -1
- {flpit-0.1.7 → flpit-0.2.0.dev2}/src/flpit/core/linq.py +321 -123
- {flpit-0.1.7 → flpit-0.2.0.dev2}/.gitignore +0 -0
- {flpit-0.1.7 → flpit-0.2.0.dev2}/LICENSE +0 -0
- {flpit-0.1.7 → flpit-0.2.0.dev2}/src/flp/__init__.py +0 -0
- {flpit-0.1.7 → flpit-0.2.0.dev2}/src/flpit/__init__.py +0 -0
- {flpit-0.1.7 → flpit-0.2.0.dev2}/src/flpit/core/__init__.py +0 -0
- {flpit-0.1.7 → flpit-0.2.0.dev2}/src/flpit/flp.py +0 -0
- {flpit-0.1.7 → flpit-0.2.0.dev2}/src/flpit/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: flpit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0.dev2
|
|
4
4
|
Summary: Fluent LINQ for Python — flip your iterables
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -28,11 +28,23 @@ Description-Content-Type: text/markdown
|
|
|
28
28
|
> Where Python's language or type system requires a different API contract, the contract is adapted accordingly.
|
|
29
29
|
> The implementation is actively developed, and semantic deviations may still exist.
|
|
30
30
|
|
|
31
|
+
> **⚠️ Breaking Change — `FlpList` — v0.2.0**
|
|
32
|
+
>
|
|
33
|
+
> `FlpList` no longer derives from `collections.UserList`.
|
|
34
|
+
>
|
|
35
|
+
> **Retained:** Python collection behavior such as iteration, `len()`, `in`, indexing, and slicing.
|
|
36
|
+
>
|
|
37
|
+
> **LINQ:** `append()`, `concat()`, and other LINQ operations remain available with lazy semantics.
|
|
38
|
+
>
|
|
39
|
+
> **`List<T>` semantics:** `add()`, `add_range()`, and other mutating operations follow the `List<T>` API where applicable.
|
|
40
|
+
>
|
|
41
|
+
> **Removed:** `copy()`, `+`, `*`, and other `UserList`-specific operations that are not part of the `FlpList` API.
|
|
42
|
+
|
|
31
43
|
## ⚡ Key Features
|
|
32
44
|
|
|
33
|
-
* **⚡ Lazy Evaluation (`FlpIt`):** Deferred, streaming execution
|
|
34
|
-
* **📦 Materialized Container (`FlpList`):** Eager,
|
|
35
|
-
* **🎯 LINQ Semantics:**
|
|
45
|
+
* **⚡ Lazy Evaluation (`FlpIt`):** Deferred, streaming execution wrapping any standard Python `Iterable[T]`.
|
|
46
|
+
* **📦 Materialized Container (`FlpList`):** Eager collection implementing Python's `Collection` protocol (with indexing and slicing), retaining familiar collection access while providing .NET `List<T>`-style semantics and fluent LINQ query operations.
|
|
47
|
+
* **🎯 LINQ Semantics:** Familiar operators like `.any()`, `.first()`, `.single()`, `.order_by()`, and `.then_by()` with predictable, composable behavior.
|
|
36
48
|
* **🔀 Independent Query Pipelines:** Derived queries are independent and never mutate or retroactively affect separately created queries.
|
|
37
49
|
* **🧠 Lazy Internal Materialization:** Some operators, such as ordering and grouping, require internal materialization during deferred execution, while the query itself remains lazy.
|
|
38
50
|
* **🔄 Source Enumeration Semantics** — FlpIt preserves the enumeration characteristics of its source. A query over a re-enumerable source can be enumerated repeatedly; a one-shot source such as a Python generator remains one-shot. FlpIt does not clone or rewind arbitrary Python iterators.
|
|
@@ -186,26 +198,31 @@ list(query) # [0, 1, 2]
|
|
|
186
198
|
list(query) # []
|
|
187
199
|
```
|
|
188
200
|
|
|
189
|
-
## Intentional Deviation
|
|
201
|
+
## Intentional Semantic Deviation: `count()`
|
|
190
202
|
|
|
191
|
-
|
|
203
|
+
`FlpList` is a LINQ-oriented collection. Its query methods follow LINQ semantics even when those semantics differ from Python's built-in collection methods.
|
|
192
204
|
|
|
193
|
-
|
|
194
|
-
* **FlpIt `FlpList.count()`** counts all elements.
|
|
195
|
-
* **FlpIt `FlpList.count(predicate)`** counts elements matching the predicate.
|
|
205
|
+
In Python, `list.count(value)` counts occurrences of a specific value:
|
|
196
206
|
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
total_evens = eager_list.count(lambda x: x % 2 == 0)
|
|
207
|
+
```
|
|
208
|
+
[1, 2, 2, 3].count(2) # 2
|
|
200
209
|
```
|
|
201
210
|
|
|
202
|
-
|
|
211
|
+
In LINQ, `Count()` returns the total number of elements, or elements matching a predicate:
|
|
203
212
|
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
|
|
213
|
+
```
|
|
214
|
+
FlpList([1, 2, 2, 3]).count() # 4
|
|
215
|
+
FlpList([1, 2, 2, 3]).count(lambda x: x > 1) # 3
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`FlpList.count()` deliberately follows the LINQ meaning. Python-style value counting remains available through normal Python operations when needed:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
list(FlpList([1, 2, 2, 3])).count(2) # 2
|
|
207
222
|
```
|
|
208
223
|
|
|
224
|
+
This behavior is a core part of `FlpList`'s LINQ-oriented contract, not an incomplete `list` implementation.
|
|
225
|
+
|
|
209
226
|
## 🔗 Independent Query Pipelines
|
|
210
227
|
|
|
211
228
|
Query pipelines can be composed independently without mutating the queries they were derived from:
|
|
@@ -260,14 +277,22 @@ Lazy execution does not mean that every operator is streaming. Some operators mu
|
|
|
260
277
|
`group_by()` produces a query of groupings rather than terminating the pipeline:
|
|
261
278
|
|
|
262
279
|
```python
|
|
263
|
-
|
|
280
|
+
from typing import NamedTuple
|
|
281
|
+
|
|
282
|
+
class GroupResult(NamedTuple):
|
|
283
|
+
remainder: int
|
|
284
|
+
values: list[int]
|
|
264
285
|
|
|
265
286
|
result = (
|
|
266
|
-
|
|
267
|
-
.
|
|
268
|
-
.select(
|
|
287
|
+
flp.it(range(1, 11))
|
|
288
|
+
.group_by(lambda x: x % 2)
|
|
289
|
+
.select(lambda group: GroupResult(
|
|
290
|
+
remainder=group.key,
|
|
291
|
+
values=list(group),
|
|
292
|
+
))
|
|
269
293
|
.to_list()
|
|
270
294
|
)
|
|
295
|
+
|
|
271
296
|
```
|
|
272
297
|
|
|
273
298
|
Terminal operators instead evaluate the query and return a result rather than another query:
|
|
@@ -328,4 +353,32 @@ uv add flpit
|
|
|
328
353
|
|
|
329
354
|
## 📜 License
|
|
330
355
|
|
|
331
|
-
Distributed under the MIT License. See `LICENSE` for more information.
|
|
356
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
### Migrated .NET Unit Tests
|
|
360
|
+
|
|
361
|
+
> Migrated from the official .NET Runtime LINQ Tests.
|
|
362
|
+
> Note: The test cases exercise both core types, `FlpIt` and `FlpList`, with transitions between lazy and eager execution occurring as part of the individual test cases.
|
|
363
|
+
|
|
364
|
+
```
|
|
365
|
+
================================================================
|
|
366
|
+
| Test Module File | Test Cases |
|
|
367
|
+
----------------------------------------------------------------
|
|
368
|
+
| tests/nettests/ | |
|
|
369
|
+
| ├── test_all.py | 80 |
|
|
370
|
+
| ├── test_any.py | 90 |
|
|
371
|
+
| ├── test_average.py | 572 |
|
|
372
|
+
| ├── test_count.py | 37 |
|
|
373
|
+
| ├── test_generic_list.py | 95 |
|
|
374
|
+
| ├── test_last.py | 29 |
|
|
375
|
+
| ├── test_order_by.py | 100 |
|
|
376
|
+
| ├── test_order_descending_by.py | 45 |
|
|
377
|
+
| ├── test_sum.py | 134 |
|
|
378
|
+
| ├── test_take.py | 39 |
|
|
379
|
+
| ├── test_then_by.py | 40 |
|
|
380
|
+
| └── test_then_descending_by.py | 34 |
|
|
381
|
+
----------------------------------------------------------------
|
|
382
|
+
| GRAND TOTALS | 1295 |
|
|
383
|
+
================================================================
|
|
384
|
+
```
|
|
@@ -8,11 +8,23 @@
|
|
|
8
8
|
> Where Python's language or type system requires a different API contract, the contract is adapted accordingly.
|
|
9
9
|
> The implementation is actively developed, and semantic deviations may still exist.
|
|
10
10
|
|
|
11
|
+
> **⚠️ Breaking Change — `FlpList` — v0.2.0**
|
|
12
|
+
>
|
|
13
|
+
> `FlpList` no longer derives from `collections.UserList`.
|
|
14
|
+
>
|
|
15
|
+
> **Retained:** Python collection behavior such as iteration, `len()`, `in`, indexing, and slicing.
|
|
16
|
+
>
|
|
17
|
+
> **LINQ:** `append()`, `concat()`, and other LINQ operations remain available with lazy semantics.
|
|
18
|
+
>
|
|
19
|
+
> **`List<T>` semantics:** `add()`, `add_range()`, and other mutating operations follow the `List<T>` API where applicable.
|
|
20
|
+
>
|
|
21
|
+
> **Removed:** `copy()`, `+`, `*`, and other `UserList`-specific operations that are not part of the `FlpList` API.
|
|
22
|
+
|
|
11
23
|
## ⚡ Key Features
|
|
12
24
|
|
|
13
|
-
* **⚡ Lazy Evaluation (`FlpIt`):** Deferred, streaming execution
|
|
14
|
-
* **📦 Materialized Container (`FlpList`):** Eager,
|
|
15
|
-
* **🎯 LINQ Semantics:**
|
|
25
|
+
* **⚡ Lazy Evaluation (`FlpIt`):** Deferred, streaming execution wrapping any standard Python `Iterable[T]`.
|
|
26
|
+
* **📦 Materialized Container (`FlpList`):** Eager collection implementing Python's `Collection` protocol (with indexing and slicing), retaining familiar collection access while providing .NET `List<T>`-style semantics and fluent LINQ query operations.
|
|
27
|
+
* **🎯 LINQ Semantics:** Familiar operators like `.any()`, `.first()`, `.single()`, `.order_by()`, and `.then_by()` with predictable, composable behavior.
|
|
16
28
|
* **🔀 Independent Query Pipelines:** Derived queries are independent and never mutate or retroactively affect separately created queries.
|
|
17
29
|
* **🧠 Lazy Internal Materialization:** Some operators, such as ordering and grouping, require internal materialization during deferred execution, while the query itself remains lazy.
|
|
18
30
|
* **🔄 Source Enumeration Semantics** — FlpIt preserves the enumeration characteristics of its source. A query over a re-enumerable source can be enumerated repeatedly; a one-shot source such as a Python generator remains one-shot. FlpIt does not clone or rewind arbitrary Python iterators.
|
|
@@ -166,26 +178,31 @@ list(query) # [0, 1, 2]
|
|
|
166
178
|
list(query) # []
|
|
167
179
|
```
|
|
168
180
|
|
|
169
|
-
## Intentional Deviation
|
|
181
|
+
## Intentional Semantic Deviation: `count()`
|
|
170
182
|
|
|
171
|
-
|
|
183
|
+
`FlpList` is a LINQ-oriented collection. Its query methods follow LINQ semantics even when those semantics differ from Python's built-in collection methods.
|
|
172
184
|
|
|
173
|
-
|
|
174
|
-
* **FlpIt `FlpList.count()`** counts all elements.
|
|
175
|
-
* **FlpIt `FlpList.count(predicate)`** counts elements matching the predicate.
|
|
185
|
+
In Python, `list.count(value)` counts occurrences of a specific value:
|
|
176
186
|
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
total_evens = eager_list.count(lambda x: x % 2 == 0)
|
|
187
|
+
```
|
|
188
|
+
[1, 2, 2, 3].count(2) # 2
|
|
180
189
|
```
|
|
181
190
|
|
|
182
|
-
|
|
191
|
+
In LINQ, `Count()` returns the total number of elements, or elements matching a predicate:
|
|
183
192
|
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
|
|
193
|
+
```
|
|
194
|
+
FlpList([1, 2, 2, 3]).count() # 4
|
|
195
|
+
FlpList([1, 2, 2, 3]).count(lambda x: x > 1) # 3
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
`FlpList.count()` deliberately follows the LINQ meaning. Python-style value counting remains available through normal Python operations when needed:
|
|
199
|
+
|
|
200
|
+
```
|
|
201
|
+
list(FlpList([1, 2, 2, 3])).count(2) # 2
|
|
187
202
|
```
|
|
188
203
|
|
|
204
|
+
This behavior is a core part of `FlpList`'s LINQ-oriented contract, not an incomplete `list` implementation.
|
|
205
|
+
|
|
189
206
|
## 🔗 Independent Query Pipelines
|
|
190
207
|
|
|
191
208
|
Query pipelines can be composed independently without mutating the queries they were derived from:
|
|
@@ -240,14 +257,22 @@ Lazy execution does not mean that every operator is streaming. Some operators mu
|
|
|
240
257
|
`group_by()` produces a query of groupings rather than terminating the pipeline:
|
|
241
258
|
|
|
242
259
|
```python
|
|
243
|
-
|
|
260
|
+
from typing import NamedTuple
|
|
261
|
+
|
|
262
|
+
class GroupResult(NamedTuple):
|
|
263
|
+
remainder: int
|
|
264
|
+
values: list[int]
|
|
244
265
|
|
|
245
266
|
result = (
|
|
246
|
-
|
|
247
|
-
.
|
|
248
|
-
.select(
|
|
267
|
+
flp.it(range(1, 11))
|
|
268
|
+
.group_by(lambda x: x % 2)
|
|
269
|
+
.select(lambda group: GroupResult(
|
|
270
|
+
remainder=group.key,
|
|
271
|
+
values=list(group),
|
|
272
|
+
))
|
|
249
273
|
.to_list()
|
|
250
274
|
)
|
|
275
|
+
|
|
251
276
|
```
|
|
252
277
|
|
|
253
278
|
Terminal operators instead evaluate the query and return a result rather than another query:
|
|
@@ -308,4 +333,32 @@ uv add flpit
|
|
|
308
333
|
|
|
309
334
|
## 📜 License
|
|
310
335
|
|
|
311
|
-
Distributed under the MIT License. See `LICENSE` for more information.
|
|
336
|
+
Distributed under the MIT License. See `LICENSE` for more information.
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
### Migrated .NET Unit Tests
|
|
340
|
+
|
|
341
|
+
> Migrated from the official .NET Runtime LINQ Tests.
|
|
342
|
+
> Note: The test cases exercise both core types, `FlpIt` and `FlpList`, with transitions between lazy and eager execution occurring as part of the individual test cases.
|
|
343
|
+
|
|
344
|
+
```
|
|
345
|
+
================================================================
|
|
346
|
+
| Test Module File | Test Cases |
|
|
347
|
+
----------------------------------------------------------------
|
|
348
|
+
| tests/nettests/ | |
|
|
349
|
+
| ├── test_all.py | 80 |
|
|
350
|
+
| ├── test_any.py | 90 |
|
|
351
|
+
| ├── test_average.py | 572 |
|
|
352
|
+
| ├── test_count.py | 37 |
|
|
353
|
+
| ├── test_generic_list.py | 95 |
|
|
354
|
+
| ├── test_last.py | 29 |
|
|
355
|
+
| ├── test_order_by.py | 100 |
|
|
356
|
+
| ├── test_order_descending_by.py | 45 |
|
|
357
|
+
| ├── test_sum.py | 134 |
|
|
358
|
+
| ├── test_take.py | 39 |
|
|
359
|
+
| ├── test_then_by.py | 40 |
|
|
360
|
+
| └── test_then_descending_by.py | 34 |
|
|
361
|
+
----------------------------------------------------------------
|
|
362
|
+
| GRAND TOTALS | 1295 |
|
|
363
|
+
================================================================
|
|
364
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "flpit"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.2.0.dev2"
|
|
4
4
|
description = "Fluent LINQ for Python — flip your iterables"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -27,6 +27,7 @@ dev = [
|
|
|
27
27
|
"pyrefly>=1.3.1",
|
|
28
28
|
"pyright>=1.1.414",
|
|
29
29
|
"pytest-benchmark>=5.3.0",
|
|
30
|
+
"mypy>=2.4.0",
|
|
30
31
|
]
|
|
31
32
|
|
|
32
33
|
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from
|
|
2
|
+
from typing import SupportsIndex
|
|
3
|
+
import decimal
|
|
4
|
+
from decimal import Decimal, Overflow, Inexact
|
|
3
5
|
|
|
4
6
|
import builtins
|
|
5
7
|
from collections import UserList
|
|
6
8
|
from functools import wraps
|
|
7
9
|
from itertools import islice
|
|
10
|
+
|
|
8
11
|
from typing import (
|
|
9
12
|
Any,
|
|
10
13
|
Callable,
|
|
11
14
|
Generic,
|
|
12
15
|
Iterable,
|
|
13
16
|
Iterator,
|
|
17
|
+
Collection,
|
|
14
18
|
List,
|
|
15
|
-
Optional,
|
|
16
|
-
Sequence,
|
|
17
19
|
Set,
|
|
18
20
|
Type,
|
|
19
21
|
TypeVar,
|
|
@@ -22,7 +24,6 @@ from typing import (
|
|
|
22
24
|
overload,
|
|
23
25
|
cast as typing_cast,
|
|
24
26
|
)
|
|
25
|
-
|
|
26
27
|
TItem = TypeVar("TItem")
|
|
27
28
|
TResult = TypeVar("TResult")
|
|
28
29
|
TKey = TypeVar("TKey")
|
|
@@ -63,9 +64,13 @@ class PredicateNoneError(TypeError):
|
|
|
63
64
|
def __init__(self):
|
|
64
65
|
super().__init__("Value cannot be None. (Argument 'predicate')")
|
|
65
66
|
|
|
67
|
+
class CollectionNoneError(TypeError):
|
|
68
|
+
def __init__(self):
|
|
69
|
+
super().__init__("Value cannot be None. (Argument 'collection')")
|
|
70
|
+
|
|
66
71
|
class SelectorNoneError(TypeError):
|
|
67
72
|
def __init__(self):
|
|
68
|
-
super().__init__("Value cannot be
|
|
73
|
+
super().__init__("Value cannot be None. (Parameter 'keySelector')")
|
|
69
74
|
|
|
70
75
|
|
|
71
76
|
def _guard_empty(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
@@ -75,12 +80,23 @@ def _guard_empty(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
|
75
80
|
try:
|
|
76
81
|
return func(self, *args, **kwargs)
|
|
77
82
|
except ValueError as e:
|
|
78
|
-
if not self.data:
|
|
83
|
+
if not self._FlpList__list.data:
|
|
79
84
|
raise EmptySequenceError() from e
|
|
80
85
|
raise
|
|
81
86
|
return wrapper
|
|
82
87
|
|
|
83
88
|
|
|
89
|
+
# noinspection unresolved-references,protected-member
|
|
90
|
+
def dotnet_context() -> decimal._ContextManager: # awesome that type was not intended
|
|
91
|
+
# 1. Create and configure pure Context object
|
|
92
|
+
ctx = decimal.getcontext().copy()
|
|
93
|
+
ctx.prec = max(ctx.prec, 29)
|
|
94
|
+
ctx.traps[Overflow] = True
|
|
95
|
+
ctx.traps[Inexact] = True
|
|
96
|
+
|
|
97
|
+
return decimal.localcontext(ctx)
|
|
98
|
+
|
|
99
|
+
|
|
84
100
|
class _FactoryIterable(Iterable[TItem], Generic[TItem]):
|
|
85
101
|
"""
|
|
86
102
|
Wraps a generator factory function into a clean Iterable[T].
|
|
@@ -443,41 +459,61 @@ class FlpIt(Iterable[TItem], Generic[TItem]):
|
|
|
443
459
|
return self._extreme(builtins.max, key=key_selector)
|
|
444
460
|
|
|
445
461
|
def average(
|
|
446
|
-
self, selector: Callable[[TItem],
|
|
447
|
-
) -> float:
|
|
462
|
+
self, selector: Callable[[TItem], int | float | Decimal] | _Sentinel = _SENTINEL
|
|
463
|
+
) -> float | int | Decimal | None:
|
|
448
464
|
"""Computes the arithmetic mean of the sequence, optionally applying a selector."""
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
query = (selector(x) for x in self) if selector is not _SENTINEL else self
|
|
452
|
-
for item in query:
|
|
453
|
-
total += float(item) # type: ignore[arg-type]
|
|
454
|
-
count += 1
|
|
455
|
-
if count == 0:
|
|
456
|
-
raise EmptySequenceError()
|
|
457
|
-
return total / count
|
|
465
|
+
with dotnet_context():
|
|
466
|
+
total, count = self.__sum_and_count(selector)
|
|
458
467
|
|
|
459
|
-
|
|
468
|
+
if total is None:
|
|
469
|
+
return None
|
|
460
470
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
total
|
|
467
|
-
count += 1
|
|
468
|
-
if count == 0:
|
|
469
|
-
raise EmptySequenceError()
|
|
470
|
-
return total / count
|
|
471
|
+
if isinstance(total, Decimal):
|
|
472
|
+
return total / Decimal(count)
|
|
473
|
+
|
|
474
|
+
assert total is not None
|
|
475
|
+
assert isinstance(total, Decimal) or isinstance(total, int) or isinstance(total, float)
|
|
476
|
+
return total / count
|
|
471
477
|
|
|
472
|
-
|
|
478
|
+
avg = average
|
|
473
479
|
|
|
474
480
|
def sum(
|
|
475
|
-
self, selector: Callable[[TItem],
|
|
476
|
-
) -> Union[int, float]:
|
|
481
|
+
self, selector: Callable[[TItem], int | float | Decimal] | _Sentinel = _SENTINEL
|
|
482
|
+
) -> Union[int, float, Decimal]:
|
|
477
483
|
"""Calculates the sum of the sequence, optionally applying a selector."""
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
484
|
+
with dotnet_context():
|
|
485
|
+
total, count = self.__sum_and_count(selector)
|
|
486
|
+
|
|
487
|
+
if count == 0:
|
|
488
|
+
return 0
|
|
489
|
+
|
|
490
|
+
assert total is not None
|
|
491
|
+
assert isinstance(total, Decimal) or isinstance(total, int) or isinstance(total, float)
|
|
492
|
+
return total
|
|
493
|
+
|
|
494
|
+
def __sum_and_count(self, selector: Callable[[TItem], int | float | Decimal]) -> tuple[Decimal | float | int | TItem | None, int]:
|
|
495
|
+
if selector is None:
|
|
496
|
+
raise SelectorNoneError()
|
|
497
|
+
|
|
498
|
+
total = None
|
|
499
|
+
count: int = 0
|
|
500
|
+
query = (selector(x) for x in self) if selector is not _SENTINEL else self
|
|
501
|
+
|
|
502
|
+
for item in query:
|
|
503
|
+
if item is not None:
|
|
504
|
+
if total is None:
|
|
505
|
+
total = item
|
|
506
|
+
else:
|
|
507
|
+
try:
|
|
508
|
+
total += item
|
|
509
|
+
except (Overflow, Inexact):
|
|
510
|
+
raise OverflowError(
|
|
511
|
+
"System.OverflowException: Value was either too large or too small for a Decimal."
|
|
512
|
+
)
|
|
513
|
+
count += 1
|
|
514
|
+
|
|
515
|
+
return total, count
|
|
516
|
+
|
|
481
517
|
|
|
482
518
|
def count(
|
|
483
519
|
self,
|
|
@@ -582,31 +618,7 @@ class OrderedIt(FlpIt[TItem]):
|
|
|
582
618
|
else:
|
|
583
619
|
self._criteria = _criteria + ((selector, descending),)
|
|
584
620
|
|
|
585
|
-
|
|
586
|
-
# items = list(self._iterable)
|
|
587
|
-
#
|
|
588
|
-
# for selector, descending in reversed(self._criteria):
|
|
589
|
-
# keys = [selector(item) for item in items]
|
|
590
|
-
#
|
|
591
|
-
# if any(key is None for key in keys):
|
|
592
|
-
# # Null-aware path
|
|
593
|
-
# items = [
|
|
594
|
-
# item
|
|
595
|
-
# for _, item in sorted(
|
|
596
|
-
# zip(keys, items),
|
|
597
|
-
# key=lambda pair: (
|
|
598
|
-
# pair[0] is not None,
|
|
599
|
-
# pair[0] if pair[0] is not None else 0,
|
|
600
|
-
# ),
|
|
601
|
-
# reverse=descending,
|
|
602
|
-
# )
|
|
603
|
-
# ]
|
|
604
|
-
# else:
|
|
605
|
-
# # Fast path
|
|
606
|
-
# items.sort(key=selector, reverse=descending)
|
|
607
|
-
#
|
|
608
|
-
# yield from items
|
|
609
|
-
|
|
621
|
+
@override
|
|
610
622
|
def __iter__(self) -> Iterator[TItem]:
|
|
611
623
|
items = list(self._iterable)
|
|
612
624
|
|
|
@@ -665,7 +677,7 @@ class Grouping(FlpIt[TItem], Generic[TKey, TItem]):
|
|
|
665
677
|
return self.key == other.key and self.to_list() == other.to_list()
|
|
666
678
|
|
|
667
679
|
|
|
668
|
-
class FlpList(
|
|
680
|
+
class FlpList(Collection[TItem], Generic[TItem]):
|
|
669
681
|
"""
|
|
670
682
|
| Fluent List
|
|
671
683
|
A materialized list extending UserList that yields lazy FlpIt instances for query operations.
|
|
@@ -679,7 +691,88 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
679
691
|
else:
|
|
680
692
|
init_list = typing_cast(Iterable[TItem], source)
|
|
681
693
|
|
|
682
|
-
|
|
694
|
+
self.__list: UserList[TItem] = UserList[TItem](init_list)
|
|
695
|
+
|
|
696
|
+
# =====================================================================
|
|
697
|
+
# 1. THE ABC CONTRACT
|
|
698
|
+
# =====================================================================
|
|
699
|
+
def __iter__(self) -> Iterator[Any]:
|
|
700
|
+
return self.__list.__iter__()
|
|
701
|
+
|
|
702
|
+
def __len__(self) -> int:
|
|
703
|
+
"""Evaluates the stream to get total count."""
|
|
704
|
+
return self.__list.__len__()
|
|
705
|
+
|
|
706
|
+
def __contains__(self, item: TItem) -> bool:
|
|
707
|
+
return self.__list.__contains__(item)
|
|
708
|
+
|
|
709
|
+
# =====================================================================
|
|
710
|
+
# 2. Keep some of the Python UserList like behavior
|
|
711
|
+
# =====================================================================
|
|
712
|
+
|
|
713
|
+
def __repr__(self):
|
|
714
|
+
return self.__list.__repr__()
|
|
715
|
+
|
|
716
|
+
def __str__(self) -> str:
|
|
717
|
+
return self.__list.__str__()
|
|
718
|
+
|
|
719
|
+
def __eq__(self, other: Any) -> bool:
|
|
720
|
+
if isinstance(other, FlpList):
|
|
721
|
+
return self.__list.__eq__(other.__list)
|
|
722
|
+
return self.__list.__eq__(other)
|
|
723
|
+
|
|
724
|
+
def __lt__(self, other: Any) -> bool:
|
|
725
|
+
target = other.__list if isinstance(other, FlpList) else other
|
|
726
|
+
return self.__list.__lt__(target)
|
|
727
|
+
|
|
728
|
+
def __le__(self, other: Any) -> bool:
|
|
729
|
+
target = other.__list if isinstance(other, FlpList) else other
|
|
730
|
+
return self.__list.__le__(target)
|
|
731
|
+
|
|
732
|
+
def __gt__(self, other: Any) -> bool:
|
|
733
|
+
target = other.__list if isinstance(other, FlpList) else other
|
|
734
|
+
return self.__list.__gt__(target)
|
|
735
|
+
|
|
736
|
+
def __ge__(self, other: Any) -> bool:
|
|
737
|
+
target = other.__list if isinstance(other, FlpList) else other
|
|
738
|
+
return self.__list.__ge__(target)
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
@overload
|
|
742
|
+
def __getitem__(self, index: SupportsIndex) -> TItem: ...
|
|
743
|
+
|
|
744
|
+
@overload
|
|
745
|
+
def __getitem__(self, index: slice) -> FlpList[TItem]: ...
|
|
746
|
+
|
|
747
|
+
def __getitem__(self, index: SupportsIndex | slice):
|
|
748
|
+
if isinstance(index, slice):
|
|
749
|
+
return self.__class__(self.__list[index])
|
|
750
|
+
else:
|
|
751
|
+
return self.__list[index]
|
|
752
|
+
|
|
753
|
+
@overload
|
|
754
|
+
def __setitem__(self, index: SupportsIndex, value: TItem) -> None: ...
|
|
755
|
+
|
|
756
|
+
@overload
|
|
757
|
+
def __setitem__(self, index: slice, value: Iterable[TItem]) -> None: ...
|
|
758
|
+
|
|
759
|
+
def __setitem__(
|
|
760
|
+
self,
|
|
761
|
+
index: SupportsIndex | slice,
|
|
762
|
+
value: TItem | Iterable[TItem],
|
|
763
|
+
) -> None:
|
|
764
|
+
if isinstance(index, slice):
|
|
765
|
+
if not isinstance(value, Iterable):
|
|
766
|
+
raise TypeError("slice assignment requires an iterable value")
|
|
767
|
+
self.__list.data[index] = value
|
|
768
|
+
else:
|
|
769
|
+
if isinstance(value, Iterable):
|
|
770
|
+
raise TypeError("item assignment requires a non-iterable value")
|
|
771
|
+
self.__list.data[index] = value
|
|
772
|
+
|
|
773
|
+
# =====================================================================
|
|
774
|
+
# 3. Put .NET List<T> mechanics on top
|
|
775
|
+
# =====================================================================
|
|
683
776
|
|
|
684
777
|
def add(self, item: TItem) -> None:
|
|
685
778
|
"""
|
|
@@ -689,9 +782,9 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
689
782
|
- type checks
|
|
690
783
|
- manual of_type(...) filter if you don't trust your checks
|
|
691
784
|
"""
|
|
692
|
-
self.data.append(item)
|
|
785
|
+
self.__list.data.append(item)
|
|
693
786
|
|
|
694
|
-
def add_range(self,
|
|
787
|
+
def add_range(self, collection: Iterable[TItem]) -> None:
|
|
695
788
|
"""
|
|
696
789
|
| Adds an Iterable sequence or stream.
|
|
697
790
|
Optimizes paths based on input type without destroying volatile generators.
|
|
@@ -700,21 +793,131 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
700
793
|
- type checks
|
|
701
794
|
- manual of_type(...) filter if you don't trust your checks
|
|
702
795
|
"""
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
796
|
+
if collection is None:
|
|
797
|
+
raise CollectionNoneError()
|
|
798
|
+
|
|
799
|
+
if isinstance(collection, UserList):
|
|
800
|
+
self.__list.data.extend(collection.data)
|
|
801
|
+
elif isinstance(collection, FlpList):
|
|
802
|
+
self.__list.data.extend(collection.__list.data)
|
|
803
|
+
else:
|
|
804
|
+
self.__list.data.extend(collection)
|
|
805
|
+
|
|
806
|
+
def clear(self) -> None:
|
|
807
|
+
self.__list.data.clear()
|
|
808
|
+
|
|
809
|
+
def contains(self, item: TItem) -> bool:
|
|
810
|
+
return item in self.__list.data
|
|
811
|
+
|
|
812
|
+
def exists(self, predicate: Callable[[TItem], bool]) -> bool:
|
|
813
|
+
return any(predicate(item) for item in self.__list.data)
|
|
814
|
+
|
|
815
|
+
def find(self, predicate: Callable[[TItem], bool]) -> TItem | None:
|
|
816
|
+
if predicate is None:
|
|
817
|
+
raise PredicateNoneError()
|
|
818
|
+
|
|
819
|
+
for item in self.__list.data:
|
|
820
|
+
if predicate(item):
|
|
821
|
+
return item
|
|
822
|
+
|
|
823
|
+
return None
|
|
707
824
|
|
|
708
|
-
|
|
709
|
-
|
|
825
|
+
def find_all(self, predicate: Callable[[TItem], bool]) -> FlpList[TItem]:
|
|
826
|
+
if predicate is None:
|
|
827
|
+
raise PredicateNoneError()
|
|
828
|
+
|
|
829
|
+
return FlpList(item for item in self.__list.data if predicate(item))
|
|
830
|
+
|
|
831
|
+
def insert(self, index: int, item: TItem) -> None:
|
|
832
|
+
if index < 0 or index > len(self.__list.data):
|
|
833
|
+
raise IndexError(f"Index {index} is out of range.")
|
|
834
|
+
|
|
835
|
+
self.__list.data.insert(index, item)
|
|
836
|
+
|
|
837
|
+
def insert_range(self, index: int, collection: Iterable[TItem]) -> None:
|
|
838
|
+
if collection is None:
|
|
839
|
+
raise CollectionNoneError()
|
|
840
|
+
|
|
841
|
+
if index < 0 or index > len(self.__list.data):
|
|
842
|
+
raise IndexError(f"Index {index} is out of range.")
|
|
843
|
+
|
|
844
|
+
self.__list.data[index:index] = list(collection)
|
|
845
|
+
|
|
846
|
+
def remove(self, item: TItem) -> bool:
|
|
710
847
|
try:
|
|
711
|
-
|
|
712
|
-
|
|
848
|
+
self.__list.data.remove(item)
|
|
849
|
+
return True
|
|
850
|
+
except ValueError:
|
|
851
|
+
return False
|
|
852
|
+
|
|
853
|
+
def remove_all(self, predicate: Callable[[TItem], bool]) -> int:
|
|
854
|
+
if predicate is None:
|
|
855
|
+
raise PredicateNoneError()
|
|
856
|
+
|
|
857
|
+
original_length = len(self.__list.data)
|
|
858
|
+
|
|
859
|
+
self.__list.data[:] = [
|
|
860
|
+
item
|
|
861
|
+
for item in self.__list.data
|
|
862
|
+
if not predicate(item)
|
|
863
|
+
]
|
|
864
|
+
|
|
865
|
+
return original_length - len(self.__list.data)
|
|
866
|
+
|
|
867
|
+
def remove_at(self, index: int) -> None:
|
|
868
|
+
if index < 0 or index > len(self.__list.data):
|
|
869
|
+
raise IndexError(f"Index {index} is out of range.")
|
|
870
|
+
|
|
871
|
+
del self.__list.data[index]
|
|
872
|
+
|
|
873
|
+
def remove_range(self, index: int, count: int) -> None:
|
|
874
|
+
size = len(self.__list.data)
|
|
875
|
+
|
|
876
|
+
if index < 0 or index > size:
|
|
877
|
+
raise IndexError("index")
|
|
878
|
+
|
|
879
|
+
if count < 0:
|
|
880
|
+
raise IndexError("count")
|
|
881
|
+
|
|
882
|
+
if count > size - index:
|
|
883
|
+
raise IndexError("The range is invalid.")
|
|
884
|
+
|
|
885
|
+
del self.__list.data[index : index + count]
|
|
886
|
+
|
|
887
|
+
@overload
|
|
888
|
+
def reverse(self) -> None: ...
|
|
889
|
+
|
|
890
|
+
@overload
|
|
891
|
+
def reverse(self, index: int, count: int) -> None: ...
|
|
892
|
+
|
|
893
|
+
def reverse(self, index: int = 0, count: int | None = None) -> None:
|
|
894
|
+
if count is None:
|
|
895
|
+
self.__list.data.reverse()
|
|
713
896
|
return
|
|
714
897
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
898
|
+
size = len(self.__list.data)
|
|
899
|
+
|
|
900
|
+
# Apply your List<T>-compatible argument validation here.
|
|
901
|
+
if index < 0 or index > size:
|
|
902
|
+
raise IndexError("Non-negative number required.")
|
|
903
|
+
|
|
904
|
+
if count < 0:
|
|
905
|
+
raise IndexError("Non-negative number required.")
|
|
906
|
+
|
|
907
|
+
if index + count > size:
|
|
908
|
+
raise IndexError(
|
|
909
|
+
"Offset and length were out of bounds for the array or "
|
|
910
|
+
"count is greater than the number of elements from index "
|
|
911
|
+
"to the end of the source collection."
|
|
912
|
+
)
|
|
913
|
+
|
|
914
|
+
self.__list.data[index:index + count] = reversed(
|
|
915
|
+
self.__list.data[index:index + count]
|
|
916
|
+
)
|
|
917
|
+
|
|
918
|
+
# =====================================================================
|
|
919
|
+
# 4. IEnumerable add-ons
|
|
920
|
+
# =====================================================================
|
|
718
921
|
|
|
719
922
|
@overload
|
|
720
923
|
def any(self) -> bool: ...
|
|
@@ -727,55 +930,55 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
727
930
|
predicate: Callable[[TItem], bool] | _Sentinel = _SENTINEL,
|
|
728
931
|
) -> bool:
|
|
729
932
|
if predicate is _SENTINEL:
|
|
730
|
-
return len(self.data) > 0
|
|
933
|
+
return len(self.__list.data) > 0
|
|
731
934
|
|
|
732
|
-
return any(predicate(item) for item in self.data)
|
|
935
|
+
return any(predicate(item) for item in self.__list.data)
|
|
733
936
|
|
|
734
937
|
def all(
|
|
735
938
|
self,
|
|
736
939
|
predicate: Callable[[TItem], bool],
|
|
737
940
|
) -> bool:
|
|
738
|
-
return all(predicate(item) for item in self.data)
|
|
941
|
+
return all(predicate(item) for item in self.__list.data)
|
|
739
942
|
|
|
740
|
-
def
|
|
943
|
+
def append(self, element: TItem) -> FlpIt[TItem]:
|
|
741
944
|
"""Appends an element to the sequence lazily, returning a FlpIt without mutating this list."""
|
|
742
|
-
return FlpIt(self.data).append(element)
|
|
945
|
+
return FlpIt(self.__list.data).append(element)
|
|
743
946
|
|
|
744
947
|
def prepend(self, element: TItem) -> FlpIt[TItem]:
|
|
745
948
|
"""Prepends an element to the sequence lazily, returning a FlpIt without mutating this list."""
|
|
746
|
-
return FlpIt(self.data).prepend(element)
|
|
949
|
+
return FlpIt(self.__list.data).prepend(element)
|
|
747
950
|
|
|
748
951
|
# noinspection unused-parameter
|
|
749
952
|
def as_type(self, target_type: Type[TResult]) -> FlpList[TResult]:
|
|
750
953
|
return self # type: ignore[return-value]
|
|
751
954
|
|
|
752
955
|
def where(self, predicate: Callable[[TItem], bool]) -> FlpIt[TItem]:
|
|
753
|
-
return FlpIt(self.data).where(predicate)
|
|
956
|
+
return FlpIt(self.__list.data).where(predicate)
|
|
754
957
|
|
|
755
958
|
def select(self, selector: Callable[[TItem], TResult]) -> FlpIt[TResult]:
|
|
756
|
-
return FlpIt(self.data).select(selector)
|
|
959
|
+
return FlpIt(self.__list.data).select(selector)
|
|
757
960
|
|
|
758
961
|
def select_many(
|
|
759
962
|
self, selector: Callable[[TItem], Iterable[TResult]]
|
|
760
963
|
) -> FlpIt[TResult]:
|
|
761
|
-
return FlpIt(self.data).select_many(selector)
|
|
964
|
+
return FlpIt(self.__list.data).select_many(selector)
|
|
762
965
|
|
|
763
966
|
def take(self, count: int) -> FlpIt[TItem]:
|
|
764
|
-
return FlpIt(self.data).take(count)
|
|
967
|
+
return FlpIt(self.__list.data).take(count)
|
|
765
968
|
|
|
766
969
|
def cast(self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
767
|
-
return FlpIt(self.data).cast(target_type)
|
|
970
|
+
return FlpIt(self.__list.data).cast(target_type)
|
|
768
971
|
|
|
769
972
|
def of_type(self, target_type: Type[TResult]) -> FlpIt[TResult]:
|
|
770
|
-
return FlpIt(self.data).of_type(target_type)
|
|
973
|
+
return FlpIt(self.__list.data).of_type(target_type)
|
|
771
974
|
|
|
772
975
|
def distinct(self) -> FlpIt[TItem]:
|
|
773
|
-
return FlpIt(self.data).distinct()
|
|
976
|
+
return FlpIt(self.__list.data).distinct()
|
|
774
977
|
|
|
775
978
|
def distinct_by(
|
|
776
979
|
self, key_selector: Callable[[TItem], TKey]
|
|
777
980
|
) -> FlpIt[TItem]:
|
|
778
|
-
return FlpIt(self.data).distinct_by(key_selector)
|
|
981
|
+
return FlpIt(self.__list.data).distinct_by(key_selector)
|
|
779
982
|
|
|
780
983
|
@overload
|
|
781
984
|
def zip(self, second: Iterable[TOther]) -> FlpIt[tuple[TItem, TOther]]: ...
|
|
@@ -792,25 +995,25 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
792
995
|
second: Iterable[TOther],
|
|
793
996
|
result_selector: Callable[[TItem, TOther], Any] | _Sentinel = _SENTINEL,
|
|
794
997
|
) -> FlpIt[Any]:
|
|
795
|
-
return FlpIt(self.data).zip(second, result_selector)
|
|
998
|
+
return FlpIt(self.__list.data).zip(second, result_selector)
|
|
796
999
|
|
|
797
1000
|
def chunk(self, size: int) -> FlpIt[FlpList[TItem]]:
|
|
798
|
-
return FlpIt(self.data).chunk(size)
|
|
1001
|
+
return FlpIt(self.__list.data).chunk(size)
|
|
799
1002
|
|
|
800
1003
|
def order_by(
|
|
801
1004
|
self, key_selector: Callable[[TItem], Any]
|
|
802
1005
|
) -> OrderedIt[TItem]:
|
|
803
|
-
return FlpIt(self.data).order_by(key_selector)
|
|
1006
|
+
return FlpIt(self.__list.data).order_by(key_selector)
|
|
804
1007
|
|
|
805
1008
|
def order_by_descending(
|
|
806
1009
|
self, key_selector: Callable[[TItem], Any]
|
|
807
1010
|
) -> OrderedIt[TItem]:
|
|
808
|
-
return FlpIt(self.data).order_by_descending(key_selector)
|
|
1011
|
+
return FlpIt(self.__list.data).order_by_descending(key_selector)
|
|
809
1012
|
|
|
810
1013
|
def group_by(
|
|
811
1014
|
self, key_selector: Callable[[TItem], TKey]
|
|
812
1015
|
) -> FlpIt[Grouping[TKey, TItem]]:
|
|
813
|
-
return FlpIt(self.data).group_by(key_selector)
|
|
1016
|
+
return FlpIt(self.__list.data).group_by(key_selector)
|
|
814
1017
|
|
|
815
1018
|
@overload
|
|
816
1019
|
def aggregate(self, func: Callable[[TItem, TItem], TItem]) -> TItem: ...
|
|
@@ -825,41 +1028,37 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
825
1028
|
func: Callable[[Any, Any], Any],
|
|
826
1029
|
seed: Any = _SENTINEL,
|
|
827
1030
|
) -> TItem | TAccumulate:
|
|
828
|
-
return FlpIt(self.data).aggregate(func, seed=seed)
|
|
1031
|
+
return FlpIt(self.__list.data).aggregate(func, seed=seed)
|
|
829
1032
|
|
|
830
1033
|
@_guard_empty
|
|
831
1034
|
def min(self) -> TItem:
|
|
832
|
-
return builtins.min(self.data) # pyrefly: ignore [bad-specialization]
|
|
1035
|
+
return builtins.min(self.__list.data) # pyrefly: ignore [bad-specialization]
|
|
833
1036
|
|
|
834
1037
|
@_guard_empty
|
|
835
1038
|
def min_by(self, key_selector: Callable[[TItem], Any]) -> TItem:
|
|
836
|
-
return builtins.min(self.data, key=key_selector)
|
|
1039
|
+
return builtins.min(self.__list.data, key=key_selector)
|
|
837
1040
|
|
|
838
1041
|
@_guard_empty
|
|
839
1042
|
def max(self) -> TItem:
|
|
840
|
-
return builtins.max(self.data) # pyrefly: ignore [bad-specialization]
|
|
1043
|
+
return builtins.max(self.__list.data) # pyrefly: ignore [bad-specialization]
|
|
841
1044
|
|
|
842
1045
|
@_guard_empty
|
|
843
1046
|
def max_by(self, key_selector: Callable[[TItem], Any]) -> TItem:
|
|
844
|
-
return builtins.max(self.data, key=key_selector)
|
|
1047
|
+
return builtins.max(self.__list.data, key=key_selector)
|
|
845
1048
|
|
|
846
1049
|
def sum(
|
|
847
|
-
self, selector: Callable[[TItem], int | float] | _Sentinel = _SENTINEL
|
|
848
|
-
) -> int | float:
|
|
1050
|
+
self, selector: Callable[[TItem], int | float | Decimal] | _Sentinel = _SENTINEL
|
|
1051
|
+
) -> int | float | Decimal:
|
|
849
1052
|
"""Calculates the sum of elements, optionally applying a selector."""
|
|
850
|
-
return FlpIt(self.data).sum(selector)
|
|
1053
|
+
return FlpIt(self.__list.data).sum(selector)
|
|
851
1054
|
|
|
852
1055
|
def average(
|
|
853
|
-
self, selector: Callable[[TItem], int | float] | _Sentinel = _SENTINEL
|
|
854
|
-
) -> float:
|
|
1056
|
+
self, selector: Callable[[TItem], int | float | Decimal] | _Sentinel = _SENTINEL
|
|
1057
|
+
) -> float | int | Decimal | None:
|
|
855
1058
|
"""Calculates the arithmetic mean, optionally applying a selector."""
|
|
856
|
-
return FlpIt(self.data).average(selector)
|
|
857
|
-
|
|
858
|
-
def count_item(self, item: TItem) -> int:
|
|
859
|
-
return self.data.count(item) # just redirect how the count redirected before
|
|
1059
|
+
return FlpIt(self.__list.data).average(selector)
|
|
860
1060
|
|
|
861
|
-
|
|
862
|
-
def count( # type: ignore # pyrefly: ignore [bad-override-param-name] # pyright: ignore[reportIncompatibleMethodOverride]
|
|
1061
|
+
def count(
|
|
863
1062
|
self,
|
|
864
1063
|
predicate: Callable[[TItem], bool] | _Sentinel = _SENTINEL,
|
|
865
1064
|
) -> int:
|
|
@@ -867,22 +1066,21 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
867
1066
|
Counts elements using LINQ semantics.
|
|
868
1067
|
|
|
869
1068
|
Supported forms:
|
|
870
|
-
|
|
871
|
-
count()
|
|
872
|
-
count(predicate) -> number of elements satisfying the predicate
|
|
1069
|
+
- count() -> total number of elements
|
|
1070
|
+
- count(predicate) -> number of elements satisfying the predicate
|
|
873
1071
|
"""
|
|
874
1072
|
if predicate is _SENTINEL:
|
|
875
|
-
return len(self.data)
|
|
1073
|
+
return len(self.__list.data)
|
|
876
1074
|
|
|
877
|
-
return FlpIt(self.data).count(predicate)
|
|
1075
|
+
return FlpIt(self.__list.data).count(predicate)
|
|
878
1076
|
|
|
879
1077
|
def element_at(self, index: int) -> TItem:
|
|
880
|
-
if index < 0 or index >= len(self.data):
|
|
1078
|
+
if index < 0 or index >= len(self.__list.data):
|
|
881
1079
|
raise IndexError("Index out of range")
|
|
882
|
-
return self.data[index]
|
|
1080
|
+
return self.__list.data[index]
|
|
883
1081
|
|
|
884
1082
|
def first(self, predicate: Callable[[TItem], bool] | _Sentinel = _SENTINEL) -> TItem:
|
|
885
|
-
return FlpIt(self.data).first(predicate)
|
|
1083
|
+
return FlpIt(self.__list.data).first(predicate)
|
|
886
1084
|
|
|
887
1085
|
@overload
|
|
888
1086
|
def last(self) -> TItem: ...
|
|
@@ -909,11 +1107,11 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
909
1107
|
raise PredicateNoneError()
|
|
910
1108
|
|
|
911
1109
|
if predicate is _SENTINEL:
|
|
912
|
-
if not self.data:
|
|
1110
|
+
if not self.__list.data:
|
|
913
1111
|
raise EmptySequenceError()
|
|
914
|
-
return self.data[-1]
|
|
1112
|
+
return self.__list.data[-1]
|
|
915
1113
|
|
|
916
|
-
for item in reversed(self.data):
|
|
1114
|
+
for item in reversed(self.__list.data):
|
|
917
1115
|
if predicate(item):
|
|
918
1116
|
return item
|
|
919
1117
|
|
|
@@ -922,11 +1120,11 @@ class FlpList(UserList[TItem], Sequence[TItem], Generic[TItem]):
|
|
|
922
1120
|
def first_or_default(
|
|
923
1121
|
self, default: TResult, predicate: Callable[[TItem], bool] | _Sentinel = _SENTINEL
|
|
924
1122
|
) -> TItem | TResult:
|
|
925
|
-
return FlpIt(self.data).first_or_default(default, predicate)
|
|
1123
|
+
return FlpIt(self.__list.data).first_or_default(default, predicate)
|
|
926
1124
|
|
|
927
1125
|
def single(self, predicate: Callable[[TItem], bool] | _Sentinel = _SENTINEL) -> TItem:
|
|
928
|
-
return FlpIt(self.data).single(predicate)
|
|
1126
|
+
return FlpIt(self.__list.data).single(predicate)
|
|
929
1127
|
|
|
930
1128
|
def to_list(self) -> FlpList[TItem]:
|
|
931
1129
|
"""Explicitly returns a shallow copy instance to isolate mutations."""
|
|
932
|
-
return FlpList(self.data.copy())
|
|
1130
|
+
return FlpList(self.__list.data.copy())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|