Dart/Flutter – How to find the elements appearing on both lists

In this post, let’s go through a few simple examples to find the elements that appear on both lists (of the same type). After this article, you may understand some more fundamentals in Dart, like equality operator or how contains() method works.

1. Implement the method

Let’s get started with how to implement the method to find the “same” elements in both lists first:

List? getElementsAppearInBothList(List l1, List l2) {
  return l1.where((e) {
    // Pick any element in l1 and be contained in l2
    return l2.contains(e);
  }).toList();
}

As you can see, in this method, I declared the List without providing any type. This is because we may need to pass any kind of list later (String, int, or any custom object).

2. Test the method with integers

void main() {
  List<int> l1 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
  List<int> l2 = [2, 4, 6, 8, 10];

  print(getElementsAppearInBothList(l1, l2));
}

// Output: [2, 4, 6, 8, 10]

It works as expected! Okay so let’s move to another test with object

3. Test the method with 2 lists of Book objects

First, let’s create a Book class and execute the method again

class Book {
  final int id;
  final String name;

  Book(this.id, this.name);
}

void main() {
  List<Book> books1 = [
    Book(1, 'Book 1'),
    Book(2, 'Book 2'),
    Book(3, 'Book 3'),
    Book(4, 'Book 4')
  ];
  List<Book> books2 = [
    Book(1, 'Book 1'),
    Book(3, 'Book 3'),
    Book(5, 'Book 4'),
    Book(6, 'Book 6')
  ];

  print(getElementsAppearInBothList(books1, books2));
}

// Output: [] -> Empty list

Whoops! It’s an empty list. Yes, it is.

If we look at books1 and book2, we expect that the output should contain ‘Book 1’ and ‘Book 3‘ because they both appear in books1 and books2. What’s wrong with the method we implemented?

4. Find the bug

Let’s look at our implementation, one more time

List? getElementsAppearInBothList(List l1, List l2) {
  return l1.where((e) {
    // Pick any element in l1 and be contained in l2
    return l2.contains(e);
  }).toList();
}

In our implementation here, we use .contains( ) method to check if the element e (in l1 list) appears in l2 list. Let’s look at the documentation of this method, it says (I made some highlights):


Whether the collection contains an element equal to element.

The equality used to determine whether element is equal to an element of the iterable defaults to the Object.== of the element.


Hmm, the .contains( ) method will compare the elements in the list with the one we want to check. To do that, it uses the equality operator. Unfortunately, we forgot to override the equality operator under our Book class. Then even when 2 books have the same attributes (id and name), they are never equal to each other.

To solve this bug, let’s update our Book class by adding the equality operator:

class Book {
  final int id;
  final String name;

  Book(this.id, this.name);


  @override
  bool operator ==(Object other) =>
      // 2 books are equal if and only if they are both Book and have exactly
      // the same id AND name.
      identical(this, other) ||
          other is Book &&
              runtimeType == other.runtimeType &&
              id == other.id &&
              name == other.name;
}

Note: when you override the equality operator, it’s recommended to override the hashCode method too. If you want to know the reason, you can check this article for more details. In this example, I will ignore the hashCode method.

Now, let’s run the test again, and see the output:

void main() {
  List<Book> books1 = [
    Book(1, 'Book 1'),
    Book(2, 'Book 2'),
    Book(3, 'Book 3'),
    Book(4, 'Book 4')
  ];
  List<Book> books2 = [
    Book(1, 'Book 1'),
    Book(3, 'Book 3'),
    Book(5, 'Book 4'),
    Book(6, 'Book 6')
  ];

  print(getElementsAppearInBothList(books1, books2));
  
  // Output: [Instance of 'Book', Instance of 'Book']
}

We can see 2 books in the output. However, it does not show us the information of each book, so we guess it works but we are not sure if it works correctly (If you want to know why the output is not nice like that, please check How to print an object ). Let’s modify our Book class one more time to have a beautiful output:

class Book {
  final int id;
  final String name;

  Book(this.id, this.name);

  @override
  bool operator ==(Object other) =>
      // 2 books are equal if and only if they are both Book and have exactly
  // the same id AND name.
  identical(this, other) ||
      other is Book &&
          runtimeType == other.runtimeType &&
          id == other.id &&
          name == other.name;

  // New method
  @override
  String toString() {
    return 'Book{id: $id, name: $name}';
  }
}

Run the code again and that’s it:

[Book{id: 1, name: Book 1}, Book{id: 3, name: Book 3}]

Happy coding!

Tagged : / / / /
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x