mirror of
https://github.com/hanskokx/arcane_helper_utils.git
synced 2026-05-14 02:19:09 +02:00
v1.4.1
- Added List.equals extension
This commit is contained in:
@@ -1,3 +1,8 @@
|
|||||||
|
## 1.4.1
|
||||||
|
|
||||||
|
- Added a `List` equality extension, `equals`.
|
||||||
|
- Fixed an issue with the `List` extension `unique` that may have caused null-safety issues.
|
||||||
|
|
||||||
## 1.4.0
|
## 1.4.0
|
||||||
|
|
||||||
- [BREAKING] JWT-related extensions have been reworked.
|
- [BREAKING] JWT-related extensions have been reworked.
|
||||||
|
|||||||
@@ -17,7 +17,10 @@ providing utility functions and extensions that simplify common tasks.
|
|||||||
common transformations and checks.
|
common transformations and checks.
|
||||||
- **JWT Utilities**: Provides getters to parse a JWT token from a `String`, then
|
- **JWT Utilities**: Provides getters to parse a JWT token from a `String`, then
|
||||||
get common properties from it.
|
get common properties from it.
|
||||||
- **List Extensions**: Adds a new `unique` operator for filtering `List` items.
|
- **List Extensions**: Adds a new `unique` operator for filtering `List` items,
|
||||||
|
as well as getters for `isNullOrEmpty`, `isEmptyOrNull`, `isNotNullOrEmpty`,
|
||||||
|
and `isNotEmptyOrNull`. Furthermore, an `equals` extension has been introduced
|
||||||
|
which can be used to compare two lists.
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
@@ -80,7 +83,7 @@ converted from a `String?` to an `int?`:
|
|||||||
|
|
||||||
```dart
|
```dart
|
||||||
@freezed
|
@freezed
|
||||||
class MyFreezedClass with _$MyFreezedClass {
|
abstract class MyFreezedClass with _$MyFreezedClass {
|
||||||
const factory MyFreezedClass({
|
const factory MyFreezedClass({
|
||||||
@DecimalConverter() double? valueIsMaybeNull,
|
@DecimalConverter() double? valueIsMaybeNull,
|
||||||
@DecimalConverter() double? valueIsDouble,
|
@DecimalConverter() double? valueIsDouble,
|
||||||
@@ -411,6 +414,41 @@ The following extensions have been added to the `List` object:
|
|||||||
print(nullList.isNullOrEmpty); // Output: true
|
print(nullList.isNullOrEmpty); // Output: true
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- `equals`: Compares two lists to see if they are equal.
|
||||||
|
|
||||||
|
```dart
|
||||||
|
List<int?>? list1 = [1, 2, null, 4];
|
||||||
|
List<int?>? list2 = [1, 2, null, 4];
|
||||||
|
List<int?>? list3 = [1, 2, 3, 4];
|
||||||
|
List<int?>? list4 = null;
|
||||||
|
List<int?>? list5 = [1, 2, 3, null];
|
||||||
|
|
||||||
|
print(list1.equals(list2)); // Output: true
|
||||||
|
print(list1.equals(list3)); // Output: false
|
||||||
|
print(list1.equals(list4)); // Output: false
|
||||||
|
print(list4.equals(null)); // Output: true
|
||||||
|
print(list5.equals([1,2,3,null])); // Output: true
|
||||||
|
|
||||||
|
// Example with ignoreSorting:
|
||||||
|
List<int>? list6 = [1, 2, 3];
|
||||||
|
List<int>? list7 = [3, 1, 2];
|
||||||
|
|
||||||
|
// Output: true (order doesn't matter)
|
||||||
|
print(list6.equals(list7, ignoreSorting: true));
|
||||||
|
|
||||||
|
// Output: false (order matters)
|
||||||
|
print(list6.equals(list7, ignoreSorting: false));
|
||||||
|
|
||||||
|
List<String>? list8 = ["apple", "banana", "cherry"];
|
||||||
|
List<String>? list9 = ["cherry", "apple", "banana"];
|
||||||
|
|
||||||
|
// Output: true
|
||||||
|
print(list8.equals(list9, ignoreSorting: true));
|
||||||
|
|
||||||
|
// Output: false
|
||||||
|
print(list8.equals(list9, ignoreSorting: false));
|
||||||
|
```
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions are welcome! Feel free to fork the repository and submit pull
|
Contributions are welcome! Feel free to fork the repository and submit pull
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ extension Unique<E, Id> on List<E> {
|
|||||||
/// print(uniquePeople.map((p) => p.name)); // Output: ['Alice', 'Bob']
|
/// print(uniquePeople.map((p) => p.name)); // Output: ['Alice', 'Bob']
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
List<E> unique([Id Function(E element)? id, bool inplace = true]) {
|
List<E> unique([Object? Function(E element)? id, bool inplace = true]) {
|
||||||
final Set ids = {};
|
final Set<Object?> ids = {};
|
||||||
final List<E> list = inplace ? this : List<E>.from(this);
|
final List<E> list = inplace ? this : List<E>.from(this);
|
||||||
list.retainWhere((x) => ids.add(id != null ? id(x) : x as Id));
|
list.retainWhere((x) => ids.add(id != null ? id(x) : x));
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,3 +122,112 @@ extension ListNullability on List? {
|
|||||||
/// This is identical to [isEmptyOrNull].
|
/// This is identical to [isEmptyOrNull].
|
||||||
bool get isEmptyOrNull => isNullOrEmpty;
|
bool get isEmptyOrNull => isNullOrEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension on nullable lists of nullable elements to provide a custom equality check.
|
||||||
|
extension ListEquality<T> on List<T?>? {
|
||||||
|
/// Checks if this list is equal to another list.
|
||||||
|
///
|
||||||
|
/// Two lists are considered equal if:
|
||||||
|
/// - Both are null, or
|
||||||
|
/// - Both have the same length, and
|
||||||
|
/// - Elements at the same index are equal.
|
||||||
|
///
|
||||||
|
/// Nullable list elements are handled as follows:
|
||||||
|
/// - If both elements at a given index are null, they are considered equal.
|
||||||
|
/// - If one element is null and the other is not, they are considered unequal.
|
||||||
|
///
|
||||||
|
/// The type parameter `T` represents the type of elements in the list.
|
||||||
|
/// The elements can be nullable (`T?`).
|
||||||
|
///
|
||||||
|
/// Example:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// List<int?>? list1 = [1, 2, null, 4];
|
||||||
|
/// List<int?>? list2 = [1, 2, null, 4];
|
||||||
|
/// List<int?>? list3 = [1, 2, 3, 4];
|
||||||
|
/// List<int?>? list4 = null;
|
||||||
|
/// List<int?>? list5 = [1, 2, 3, null];
|
||||||
|
///
|
||||||
|
/// print(list1.equals(list2)); // Output: true
|
||||||
|
/// print(list1.equals(list3)); // Output: false
|
||||||
|
/// print(list1.equals(list4)); // Output: false
|
||||||
|
/// print(list4.equals(null)); // Output: true
|
||||||
|
/// print(list5.equals([1,2,3,null])); //Output: true
|
||||||
|
///
|
||||||
|
/// // Example with ignoreSorting:
|
||||||
|
/// List<int>? list6 = [1, 2, 3];
|
||||||
|
/// List<int>? list7 = [3, 1, 2];
|
||||||
|
/// print(list6.equals(list7, ignoreSorting: true)); // Output: true (order doesn't matter)
|
||||||
|
/// print(list6.equals(list7, ignoreSorting: false)); // Output: false (order matters)
|
||||||
|
///
|
||||||
|
/// List<String>? list8 = ["apple", "banana", "cherry"];
|
||||||
|
/// List<String>? list9 = ["cherry", "apple", "banana"];
|
||||||
|
/// print(list8.equals(list9, ignoreSorting: true)); // Output: true
|
||||||
|
/// print(list8.equals(list9, ignoreSorting: false)); // Output: false
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Returns `true` if the lists are equal, `false` otherwise.
|
||||||
|
///
|
||||||
|
/// The [ignoreSorting] parameter, if set to true, will compare the lists
|
||||||
|
/// after sorting them. This defaults to false.
|
||||||
|
bool equals(
|
||||||
|
List<T?>? a, {
|
||||||
|
bool ignoreSorting = false,
|
||||||
|
}) {
|
||||||
|
if (this == null) return a == null;
|
||||||
|
if (a == null || this?.length != a.length) return false;
|
||||||
|
if (this.runtimeType != a.runtimeType) return false;
|
||||||
|
|
||||||
|
if (ignoreSorting) {
|
||||||
|
// Create copies to avoid modifying the original lists.
|
||||||
|
final List<T?> sortedThis = this?.toList() ?? [];
|
||||||
|
final List<T?> sortedA = a.toList();
|
||||||
|
|
||||||
|
// If the lists contain non-comparable elements, we can't rely on sorting to determine equality.
|
||||||
|
if (T is! Comparable) {
|
||||||
|
// Instead, we check if the lists contain the same elements, regardless of order.
|
||||||
|
// This is an O(n^2) operation, but it's the only reliable way to compare non-comparable lists.
|
||||||
|
for (final itemThis in sortedThis) {
|
||||||
|
final int indexA = sortedA.indexOf(itemThis);
|
||||||
|
if (indexA == -1) {
|
||||||
|
// itemThis is not in sortedA, so the lists are not equal.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Remove the item from sortedA to avoid double-counting.
|
||||||
|
sortedA.removeAt(indexA);
|
||||||
|
}
|
||||||
|
// If sortedA is empty, all elements in sortedThis were found in sortedA.
|
||||||
|
return sortedA.isEmpty;
|
||||||
|
}
|
||||||
|
// Sort if comparable
|
||||||
|
sortedThis.sort((a, b) {
|
||||||
|
if (a == null && b == null) return 0;
|
||||||
|
if (a == null) return -1;
|
||||||
|
if (b == null) return 1;
|
||||||
|
return (a as Comparable).compareTo(b as Comparable);
|
||||||
|
});
|
||||||
|
sortedA.sort((a, b) {
|
||||||
|
if (a == null && b == null) return 0;
|
||||||
|
if (a == null) return -1;
|
||||||
|
if (b == null) return 1;
|
||||||
|
return (a as Comparable).compareTo(b as Comparable);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Compare sorted lists
|
||||||
|
for (int i = 0; i < sortedThis.length; i++) {
|
||||||
|
if (sortedThis[i] != sortedA[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// Original comparison
|
||||||
|
for (int i = 0; i < (this?.length ?? 0); i++) {
|
||||||
|
if (this?[i] != a[i]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
name: arcane_helper_utils
|
name: arcane_helper_utils
|
||||||
description: Provides a variety of helpful utilities and extensions for Flutter
|
description: Provides a variety of helpful utilities and extensions for Flutter
|
||||||
and Dart.
|
and Dart.
|
||||||
version: 1.4.0
|
version: 1.4.1
|
||||||
repository: https://github.com/hanskokx/arcane_helper_utils
|
repository: https://github.com/hanskokx/arcane_helper_utils
|
||||||
issue_tracker: https://github.com/hanskokx/arcane_helper_utils/issues
|
issue_tracker: https://github.com/hanskokx/arcane_helper_utils/issues
|
||||||
|
|
||||||
|
|||||||
@@ -63,4 +63,150 @@ void main() {
|
|||||||
expect(nonEmptyList.isNullOrEmpty, !nonEmptyList.isNotNullOrEmpty);
|
expect(nonEmptyList.isNullOrEmpty, !nonEmptyList.isNotNullOrEmpty);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group("ListEquality", () {
|
||||||
|
// Helper function to make tests more concise
|
||||||
|
void testEquality<T>(
|
||||||
|
List<T?>? list1,
|
||||||
|
List<T?>? list2,
|
||||||
|
bool expected, {
|
||||||
|
bool ignoreSorting = false,
|
||||||
|
}) {
|
||||||
|
expect(
|
||||||
|
list1.equals(list2, ignoreSorting: ignoreSorting),
|
||||||
|
expected,
|
||||||
|
reason:
|
||||||
|
'Expected ${list1?.toString()} and ${list2?.toString()} to be ${expected ? "equal" : "unequal"} when ignoreSorting is $ignoreSorting',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
test("Both lists are null", () {
|
||||||
|
testEquality(null, null, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("One list is null, the other is not", () {
|
||||||
|
testEquality([1, 2, 3], null, false);
|
||||||
|
testEquality(null, [1, 2, 3], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists have different lengths", () {
|
||||||
|
testEquality([1, 2, 3], [1, 2], false);
|
||||||
|
testEquality([1, 2], [1, 2, 3], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists have the same elements in the same order", () {
|
||||||
|
testEquality([1, 2, 3], [1, 2, 3], true);
|
||||||
|
testEquality(["a", "b", "c"], ["a", "b", "c"], true);
|
||||||
|
testEquality([true, false, true], [true, false, true], true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"Lists have the same elements in a different order - without ignoreSorting",
|
||||||
|
() {
|
||||||
|
testEquality([1, 2, 3], [3, 2, 1], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"Lists have the same elements in a different order - with ignoreSorting",
|
||||||
|
() {
|
||||||
|
testEquality([1, 2, 3], [3, 2, 1], true, ignoreSorting: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists have different elements", () {
|
||||||
|
testEquality([1, 2, 3], [1, 2, 4], false);
|
||||||
|
testEquality(["a", "b", "c"], ["a", "b", "d"], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists with null elements - both null at same index", () {
|
||||||
|
testEquality([1, null, 3], [1, null, 3], true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists with null elements - one null, one not null at same index", () {
|
||||||
|
testEquality([1, null, 3], [1, 2, 3], false);
|
||||||
|
testEquality([1, 2, 3], [1, null, 3], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists with multiple null elements", () {
|
||||||
|
testEquality([null, null, null], [null, null, null], true);
|
||||||
|
testEquality([null, 1, null], [null, 1, null], true);
|
||||||
|
testEquality([null, 1, null], [1, null, 1], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Empty lists", () {
|
||||||
|
testEquality([], [], true);
|
||||||
|
});
|
||||||
|
test("List of different types", () {
|
||||||
|
testEquality(<int?>[], <String?>[], false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists of comparable items with different order - ignoreSorting true",
|
||||||
|
() {
|
||||||
|
testEquality([3, 1, 2], [1, 2, 3], true, ignoreSorting: true);
|
||||||
|
testEquality(
|
||||||
|
["c", "a", "b"],
|
||||||
|
["b", "c", "a"],
|
||||||
|
true,
|
||||||
|
ignoreSorting: true,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists of comparable items with different order - ignoreSorting false",
|
||||||
|
() {
|
||||||
|
testEquality([3, 1, 2], [1, 2, 3], false, ignoreSorting: false);
|
||||||
|
testEquality(
|
||||||
|
["c", "a", "b"],
|
||||||
|
["b", "c", "a"],
|
||||||
|
false,
|
||||||
|
ignoreSorting: false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists with nulls and different order - ignoreSorting true", () {
|
||||||
|
testEquality([null, 3, 1, 2], [1, 2, 3, null], true, ignoreSorting: true);
|
||||||
|
testEquality([3, 1, 2, null], [null, 1, 2, 3], true, ignoreSorting: true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Lists with nulls and different order - ignoreSorting false", () {
|
||||||
|
testEquality(
|
||||||
|
[null, 3, 1, 2],
|
||||||
|
[1, 2, 3, null],
|
||||||
|
false,
|
||||||
|
ignoreSorting: false,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test(
|
||||||
|
"Lists of non-comparable items with different order - ignoreSorting true",
|
||||||
|
() {
|
||||||
|
final List<NonComparable?> list1 = [
|
||||||
|
NonComparable(1),
|
||||||
|
NonComparable(2),
|
||||||
|
NonComparable(3),
|
||||||
|
];
|
||||||
|
final List<NonComparable?> list2 = [
|
||||||
|
NonComparable(3),
|
||||||
|
NonComparable(1),
|
||||||
|
NonComparable(2),
|
||||||
|
];
|
||||||
|
testEquality(list1, list2, true, ignoreSorting: true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class NonComparable {
|
||||||
|
final int value;
|
||||||
|
NonComparable(this.value);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) =>
|
||||||
|
identical(this, other) ||
|
||||||
|
other is NonComparable &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
value == other.value;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => value.hashCode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => "NonComparable($value)";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user