Dart
How can I compare Lists for equality in Dart
In the world of Dart programming, particularly when developing Flutter applications, managing and comparing data structures is a fundamental task. One common challenge developers encounter is determining how to compare Lists for equality in Dart. Unlike primitive types such as integers or strings, which are compared by their value using the == operator, lists in Dart behave differently. Directly applying == to two lists only checks if they are the exact same object in memory, not if their contents are identical. This nuance often leads to unexpected behavior if not properly understood, making it crucial to delve into the various methods for accurate list comparison. This article will guide you through the intricacies of shallow versus deep equality, explore built-in solutions, and provide practical examples to ensure your Dart applications handle list comparisons robustly and efficiently.
Understanding List Equality in Dart
When you work with lists in Dart, it’s essential to grasp how the language defines equality. By default, the == operator for objects, including lists, performs a reference equality check. This means it evaluates whether two variables point to the exact same object in memory. For instance, if you create two distinct list instances, even if they contain the same elements in the same order, == will return false because they are different objects. This behavior is a common source of confusion for developers migrating from languages where list or array comparison might default to content equality.
Consider this simple Dart example:
List<int> listA = [1, 2, 3]; List<int> listB = [1, 2, 3]; List<int> listC = listA; print(listA == listB); // Output: false (different objects) print(listA == listC); // Output: true (same object reference)
As illustrated, listA == listB yields false, emphasizing that Dart’s default equality check isn’t about the elements within the lists. This characteristic necessitates specific approaches when your goal is to ascertain if two lists have identical content. Understanding this foundational difference is the first step toward correctly implementing Dart list comparison logic in your applications.
Shallow Equality vs. Deep Equality
To effectively compare lists, we must differentiate between shallow and deep equality. Each serves a distinct purpose and is applicable in different scenarios, depending on the complexity of the list elements.
Shallow Equality
Shallow equality refers to the comparison of elements within two lists based on their direct equality. For lists containing primitive types like integers, doubles, strings, or booleans, shallow equality effectively means checking if each corresponding element is equal by value. If the lists contain objects, shallow equality checks if the references to those objects are the same, or if the objects themselves are considered equal via their overridden == operator. This method is straightforward and efficient for simple data types, as it doesn’t delve into the internal structure of complex objects.
For example, comparing [1, 2, 3] and [1, 2, 3] for shallow equality would involve checking if 1 == 1, 2 == 2, and 3 == 3. If all these individual element comparisons return true, and the lists have the same length, then they are shallowly equal. However, for lists of custom objects, like [User('Alice'), User('Bob')], a shallow comparison would only check if user1 == user1, not if User('Alice') (from list A) has the same name and ID as User('Alice') (from list B) if they are different instances.
Deep Equality
Deep equality, also known as structural equality, takes the comparison a step further. It’s crucial when your lists contain complex objects (e.g., custom classes, other lists, or maps) and you need to verify if their actual contents are identical, not just their references. Deep equality recursively compares the elements and their nested structures. This means if a list contains another list, the deep equality check would compare the elements of the inner list, and so on, until all nested structures are fully evaluated.
For instance, if you have [[1, 2], [3, 4]] and [[1, 2], [3, 4]], a deep equality check would confirm that [1, 2] from the first list is deeply equal to [1, 2] from the second, and similarly for [3, 4]. This level of scrutiny is indispensable for ensuring data integrity when dealing with complex data models, such as comparing two sets of configuration data or validating deserialized JSON structures. Without deep equality, subtle differences in nested objects could go unnoticed, leading to bugs or inconsistent application states.
Methods to Compare Lists for Deep Equality
When you need to accurately determine if two lists have identical content, especially when they contain complex objects or nested structures, Dart offers several robust approaches. The most straightforward way to compare Lists for equality in Dart, particularly for deep equality, is to leverage the package:collection. This package provides highly optimized and reliable utilities specifically designed for collection comparisons.
One common scenario where deep equality is essential is comparing two lists of custom objects, for which the default == operator might not suffice. For instance, if you have a Product class and two lists of Product objects, you’d want to ensure that each product in one list matches a corresponding product in the other, based on their properties (e.g., ID, name, price), not just their memory addresses. According to the Dart documentation on equality, objects are equal if they represent the same value, which for custom classes often requires overriding the == operator and hashCode.
Manual Element-Wise Comparison
Before diving into external packages, it’s useful to understand how to implement a manual deep comparison. This approach gives you granular control and is suitable for simpler cases or when you want to avoid adding external dependencies. Here’s how you can implement a custom function for element-wise comparison:
- First, check if both lists are null. If both are null, they are equal. If one is null and the other isn’t, they are not equal.
- Check if the lengths of the two lists are equal. If they differ, the lists cannot be equal.
- Iterate through the lists, comparing each element at the corresponding index.
- For each element, use the element’s own
==operator. If any pair of elements at the same index is not equal, the lists are not equal. - If the loop completes without finding any unequal elements, the lists are deeply equal.
bool areListsEqual(List? list1, List? list2) { if (identical(list1, list2)) return true; // Same object reference or both null if (list1 == null || list2 == null) return false; // One is null, other isn't if (list1.length != list2.length) return false; for (int i = 0; i < list1.length; i++) { if (list1[i] is List && list2[i] is List) { if (!areListsEqual(list1[i], list2[i])) { // Recursive call for nested lists return false; } } else if (list1[i] != list2[i]) { // Use element's == operator return false; } } return true; } // Example usage: List<int> l1 = [1, 2, 3]; List<int> l2 = [1, 2, 3]; List<List<int>&
<b>Question & Answer : </b><br></br><p>I'm comparing two lists in Dart like this:</p> main() { if ([1,2,3] == [1,2,3]) { print("Equal"); } else { print("Not equal"); } } <p>But they are never equal. There doesn't seem to be an equal() method in the Dart API to compare Lists or Collections. Is there a proper way to do this?</p>
<br></br><p>To complete Gunter's answer: the recommended way to compare lists for equality (rather than identity) is by using the Equality classes from the following package</p> import 'package:collection/collection.dart'; <p><strong>Edit</strong>: prior to 1.13, it was <del>import 'package:collection/equality.dart';</del></p> <p>E.g.:</p> Function eq = const ListEquality().equals; print(eq([1,'two',3], [1,'two',3])); // => true <p>The above prints true because the corresponding list elements are identical(). If you want to (deeply) compare lists that might contain other collections then instead use:</p> Function deepEq = const DeepCollectionEquality().equals; List list1 = [1, ['a',[]], 3]; List list2 = [1, ['a',[]], 3]; print( eq(list1, list2)); // => false print(deepEq(list1, list2)); // => true <p>There are other Equality classes that can be combined in many ways, including equality for Maps. You can even perform an <strong>unordered</strong> (deep) comparison of collections:</p> Function unOrdDeepEq = const DeepCollectionEquality.unordered().equals; List list3 = [3, [[],'a'], 1]; print(unOrdDeepEq(list2, list3)); // => true <p>For details see the <a href="http://pub.dartlang.org/packages/collection" rel="noreferrer">package API documentation</a>. As usual, to use such a package you must list it in your pubspec.yaml:</p> dependencies: collection: any