176

How do I delete duplicates from a list without fooling around with a set? Is there something like list.distinct()? or list.unique()?

void main() {
  print("Hello, World!");

  List<String> list = ['abc',"abc",'def'];
  list.forEach((f) => print("this is list $f"));

  Set<String> set = new Set<String>.from(list);
  print("this is #0 ${list[0]}");
  set.forEach((f) => print("set: $f"));

  List<String> l2= new List<String>.from(set);
  l2.forEach((f) => print("This is new $f"));
}
Hello, World!
this is list abc
this is list abc
this is list def
this is #0 abc
set: abc
set: def
This is new abc
This is new def

Set seems to be way faster!! But it loses the order of the items :/

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Gero
  • 12,993
  • 25
  • 65
  • 106

23 Answers23

458

Use toSet and then toList

  var ids = [1, 4, 4, 4, 5, 6, 6];
  var distinctIds = ids.toSet().toList();

Result: [1, 4, 5, 6]

Or with spread operators:

var distinctIds = [...{...ids}];
atreeon
  • 21,799
  • 13
  • 85
  • 104
  • 23
    Now this is the Dart way (short and powerful)! THank you. – xpeldev Feb 15 '19 at 20:19
  • 4
    this is beautiful code-efficiency wise. Is it performance efficient as well? I will use this in a long for loop to avoid adding same elements. – aytunch Apr 10 '19 at 21:43
  • 12
    Just keep in mind tho from the original docs 'The order of the elements in the set is not guaranteed to be the same' – Mertcan Diken Jul 08 '19 at 15:11
  • This does not work for strings list `2019 - 01`. And... from docs: `The set may contain fewer elements than the iterable, if the iterable contains an element more than once, or it contains one or more elements that are equal`. – BambinoUA Jul 15 '19 at 09:39
  • 2
    What doesn't work? var ids = ["abc", "def", "def"]; var distinctIds = ids.toSet().toList(); seems to remove the duplicate "def" – atreeon Jul 15 '19 at 11:45
  • 5
    @AleksTi it does work for strings too and regarding the second point: well this is the point of the whole operation. –  Apr 16 '20 at 03:05
  • Would this work if the elements in the set are objects? or are they considered different bcause evaluated by reference? Edit: tried, it does not work – user3808307 Jan 31 '21 at 22:17
  • @user3808307 For anyone that want to use this for `Object`. Check this example https://stackoverflow.com/questions/12030613/how-to-delete-duplicates-in-a-dart-list-list-distinct/66137459#66137459 – mahdi shahbazi Feb 10 '21 at 13:04
  • 1
    @MertcanDiken that is not true, currently when using `.toSet()` a `LinkedHashSet` is created, which keeps the order of insertion. This means that doing `ids.toSet().toList()` will keep the order. – Limbou Dec 01 '21 at 19:03
  • This does not remove duplicate enums for me. Does anyone have a good solution for this? – Joel Broström Dec 15 '21 at 12:01
  • I assume `set()` relies on the equality operator, so this does not necessarily work for complex objects where you would expect two objects to be duplicates when they are considered to be distinct by dart. – masus04 Jul 20 '23 at 08:15
172

I didn't find any of the provided answers very helpful. Here is what I generally do:

final ids = Set();
myList.retainWhere((x) => ids.add(x.id));

Of course you can use any attribute which uniquely identifies your objects. It doesn't have to be an id field.

Benefits over other approaches:

  • Preserves the original order of the list
  • Works for rich objects not just primitives/hashable types
  • Doesn't have to copy the entire list to a set and back to a list

Update 09/12/21
You can also declare an extension method once for lists:

extension Unique<E, Id> on List<E> {
  List<E> unique([Id Function(E element)? id, bool inplace = true]) {
    final ids = Set();
    var list = inplace ? this : List<E>.from(this);
    list.retainWhere((x) => ids.add(id != null ? id(x) : x as Id));
    return list;
  }
}

This extension method does the same as my original answer. Usage:

// Use a lambda to map an object to its unique identifier.
myRichObjectList.unique((x) => x.id);
// Don't use a lambda for primitive/hashable types.
hashableValueList.unique();
Eugene Kalinin
  • 208
  • 4
  • 5
Basic Coder
  • 10,882
  • 6
  • 42
  • 75
  • 8
    That's only answer that works fine for List of Objects Thanks man – Ahmad Darwesh Sep 03 '20 at 20:38
  • Works for me when I needed to remove Map in List which has a specific value. – shinyatk Oct 02 '20 at 09:48
  • Brilliant! Didn't know `remove` has that kind of utility. Thanks! – Moses Aprico Oct 06 '20 at 08:59
  • 1
    Brilliant solution! I knew there had to be a way to use Sets to achieve this effect. – Ray Li Oct 12 '20 at 22:59
  • Best solution, needs more upvotes. Thanks!! It's the most generic, normally we have lists with objects not simple strings or numbers. – n13 Mar 03 '21 at 08:51
  • 3
    This is the kind of solution I was looking for, thanks. However, I'm a bit puzzled: why use [`.remove()`](https://api.dart.dev/stable/2.10.5/dart-core/Set/remove.html) instead of [`.add()`](https://api.dart.dev/stable/2.10.5/dart-core/Set/add.html)? It seems it would avoid to iterate the list twice: `final ids = Set(); myList.retainWhere((x) => ids.add(x.id))`. – Delgan Mar 20 '21 at 12:57
  • @Delgan yes, using `.add()` is even better. Thanks for the advice. – Alex Semeniuk May 28 '21 at 15:33
46

Set works okay, but it doesn't preserve the order. Here's another way using LinkedHashSet:

import "dart:collection";

void main() {
  List<String> arr = ["a", "a", "b", "c", "b", "d"];
  List<String> result = LinkedHashSet<String>.from(arr).toList();
  print(result); // => ["a", "b", "c", "d"]
}

https://api.dart.dev/stable/2.4.0/dart-collection/LinkedHashSet/LinkedHashSet.from.html

naffetS
  • 3,087
  • 2
  • 14
  • 24
  • 5
    `Set`s default implementation is a `LinkedHashSet`, so this changes nothing – PixelToast Nov 23 '20 at 11:12
  • Per the [docs](https://api.dart.dev/stable/2.4.0/dart-collection/LinkedHashSet-class.html), "The LinkedHashSet also keep track of the order that elements were inserted in, and iteration happens in first-to-last insertion order." If order preservation is important, it may make sense to use the specific type offering this guarantee, rather than rely on what happens to (today) be the default implementation. While I have not seen the docs explicitly guarantee that default `Set()` will always preserve order, it seems unlikely to change since that could potentially break quite a bit. – Chuck Batson Feb 17 '23 at 21:38
33

Try the following:

List<String> duplicates = ["a", "c", "a"];

duplicates = duplicates.toSet().toList();

Check this code on Dartpad.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Anandu YD
  • 331
  • 3
  • 5
  • this is pure solution with 3rd support – TeeTracker Jul 13 '20 at 10:26
  • 3
    @TeeTracker: What do you mean by "3rd support"? – Peter Mortensen Apr 22 '21 at 23:40
  • doesn't work if the elements of the array are objects! – Mohammad Desouky May 01 '23 at 09:48
  • @MohammadDesouky try the following example ` class Person { String name; Person(this.name); @override bool operator ==(Object other) => identical(this, other) || other is Person && runtimeType == other.runtimeType && name == other.name; @override int get hashCode => name.hashCode; } void main() { List duplicates = [Person("a"), Person("c"), Person("a")]; List uniqueItems = duplicates.toSet().toList(); print(uniqueItems); // Output: [Instance of 'Person', Instance of 'Person'] } ` – Anandu YD Jul 26 '23 at 10:11
22

If you want to keep ordering or are dealing with more complex objects than primitive types, store seen ids to the Set and filter away those ones that are already in the set.

final list = ['a', 'a', 'b'];
final seen = Set<String>();
final unique = list.where((str) => seen.add(str)).toList();

print(unique); // => ['a', 'b']
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Niko Ruotsalainen
  • 49,514
  • 4
  • 24
  • 31
  • Very nice uniqueness maker if one deals with more complex lists. Thank you very much ! – iKK Jan 06 '20 at 07:48
  • This is the only one that worked when having a list of objects and needing unique by id. Thank you – user3808307 Jan 31 '21 at 23:20
  • Wouldn't be 'forEach' better than 'where'? 'where' is used for filtering (and should return bool): – Piotr Temp Mar 26 '21 at 11:18
  • This is the simplest, cleanest looking to me. Also, from the documentation, where returns a new lazy iterable... : Iterable where(bool Function(String) test) Returns a new lazy Iterable with all elements that satisfy the predicate test. – RobbB Dec 29 '21 at 02:43
21

//This easy way works fine

List<String> myArray = [];
myArray = ['x', 'w', 'x', 'y', 'o', 'x', 'y', 'y', 'r', 'a'];

myArray = myArray.toSet().toList();

print(myArray);

// result => myArray =['x','w','y','o','r', 'a']

Ravindra S. Patil
  • 11,757
  • 3
  • 13
  • 40
Confiance
  • 706
  • 5
  • 7
19

Remove duplicates from a list of objects:

class Stock {
  String? documentID; //key
  Make? make;
  Model? model;
  String? year;

  Stock({
    this.documentID,
    this.make,
    this.model,
    this.year,
  });
}

List of stock, from where we want to remove duplicate stocks

List<Stock> stockList = [stock1, stock2, stock3];

Remove duplicates

final ids = stockList.map((e) => e.documentID).toSet();
stockList.retainWhere((x) => ids.remove(x.documentID));
Baig
  • 4,737
  • 1
  • 32
  • 47
15

I am adding this to atreeon's answer. For anyone that want use this with Object:

class MyObject{
  int id;

  MyObject(this.id);


  @override
  bool operator ==(Object other) {
    return other != null && other is MyObject && hashCode == other.hashCode;
  }


  @override
  int get hashCode => id;
}

main(){
   List<MyObject> list = [MyObject(1),MyObject(2),MyObject(1)];

   // The new list will be [MyObject(1),MyObject(2)]
   List<MyObject> newList = list.toSet().toList();
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
mahdi shahbazi
  • 1,882
  • 10
  • 19
  • Beautiful!! I'd like to say the `Set` uses `hashCode` to uniquely identify the object, That's why it works, if we don't override the `hashCode` and `operator` to our custom object, then `toSet()` won't work here! – Balaji Apr 02 '23 at 18:56
11

Using Dart 2.3+, you can use the spread operators to do this:

final ids = [1, 4, 4, 4, 5, 6, 6];
final distinctIds = [...{...ids}];

Whether this is more or less readable than ids.toSet().toList() I'll let the reader decide :)

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
jtlim
  • 3,861
  • 1
  • 16
  • 14
11

For distinct list of objects you can use Equatable package.

Example:

// ignore: must_be_immutable
class User extends Equatable {
  int id;
  String name;

  User({this.id, this.name});

  @override
  List<Object> get props => [id];
}

List<User> items = [
  User(
    id: 1,
    name: "Omid",
  ),
  User(
    id: 2,
    name: "Raha",
  ),
  User(
    id: 1,
    name: "Omid",
  ),
  User(
    id: 2,
    name: "Raha",
  ),
];

print(items.toSet().toList());

Output:

[User(1), User(2)]
Omid Raha
  • 9,862
  • 1
  • 60
  • 64
9

Here it is, a working solution:

var sampleList = ['1', '2', '3', '3', '4', '4'];
//print('original: $sampleList');
sampleList = Set.of(sampleList).toList();
//print('processed: $sampleList');

Output:

original: [1, 2, 3, 3, 4, 4]
processed: [1, 2, 3, 4]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
BambinoUA
  • 6,126
  • 5
  • 35
  • 51
6

Using the fast_immutable_collections package:

[1, 2, 3, 2].distinct();

Or

[1, 2, 3, 2].removeDuplicates().toList();

Note: While distinct() returns a new list, removeDuplicates() does it lazily by returning an Iterable. This means it is much more efficient when you are doing some extra processing. For example, suppose you have a list with a million items, and you want to remove duplicates and get the first five:

// This will process five items:
List<String> newList = list.removeDuplicates().take(5).toList();

// This will process a million items:
List<String> newList = list.distinct().sublist(0, 5);

// This will also process a million items:
List<String> newList = [...{...list}].sublist(0, 5);

Both methods also accept a by parameter. For example:

// Returns ["a", "yk", "xyz"]
["a", "yk", "xyz", "b", "xm"].removeDuplicates(by: (item) => item.length);

If you don't want to include a package into your project but needs the lazy code, here it is a simplified removeDuplicates():

Iterable<T> removeDuplicates<T>(Iterable<T> iterable) sync* {
  Set<T> items = {};
  for (T item in iterable) {
    if (!items.contains(item)) yield item;
    items.add(item);
  }
}

Note: I am one of the authors of the fast_immutable_collections package.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
5
void uniqifyList(List<Dynamic> list) {
  for (int i = 0; i < list.length; i++) {
    Dynamic o = list[i];
    int index;
    // Remove duplicates
    do {
      index = list.indexOf(o, i+1);
      if (index != -1) {
        list.removeRange(index, 1);
      }
    } while (index != -1);
  }
}

void main() {
  List<String> list = ['abc', "abc", 'def'];
  print('$list');
  uniqifyList(list);
  print('$list');
}

Gives output:

[abc, abc, def]
[abc, def]
Cutch
  • 3,511
  • 1
  • 18
  • 12
1

As for me, one of the best practices is sort the array, and then deduplicate it. The idea is stolen from low-level languages. So, first make the sort by your own, and then deduplicate equal values that are going after each other.

// Easy example
void dedup<T>(List<T> list, {removeLast: true}) {
  int shift = removeLast ? 1 : 0;
  T compareItem;
  for (int i = list.length - 1; i >= 0; i--) {
    if (compareItem == (compareItem = list[i])) {
      list.removeAt(i + shift);
    }
  }
}

// Harder example
void dedupBy<T, I>(List<T> list, I Function(T) compare, {removeLast: true}) {
  int shift = removeLast ? 1 : 0;
  I compareItem;
  for (int i = list.length - 1; i >= 0; i--) {
    if (compareItem == (compareItem = compare(list[i]))) {
      list.removeAt(i + shift);
    }
  }
}


void main() {
  List<List<int>> list = [[1], [1], [2, 1], [2, 2]];
  print('$list');
  dedupBy(list, (innerList) => innerList[0]);
  print('$list');

  print('\n removeLast: false');

  List<List<int>> list2 = [[1], [1], [2, 1], [2, 2]];
  print('$list2');
  dedupBy(list2, (innerList) => innerList[0], removeLast: false);
  print('$list2');
}

Output:

[[1], [1], [2, 1], [2, 2]]
[[1], [2, 1]]

removeLast: false
[[1], [1], [2, 1], [2, 2]]
[[1], [2, 2]]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Stanislav Sagan
  • 362
  • 2
  • 3
1

This is another way...

final reducedList = [];

list.reduce((value, element) {
    if (value != element) 
        reducedList.add(value);
    return element;
});

reducedList.add(list.last);

print(reducedList);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

Mind this extension

extension IterableExtension<T> on Iterable<T> {
  List<T> distinct<U>({required U Function(T t) by}) {
    final unique = <U, T>{};

    for (final item in this) {
      unique.putIfAbsent(by(item), () => item);
    }

    return unique.values.toList();
  }
}

class Item {
  Item(this.name, this.id);

  final String name;
  final int id;

  @override
  String toString() {
    return 'Item{name: $name, id: $id}';
  }
}

void main() {
  final list = [1, 1, 1, 2, 3, 4, 4, 5, 6, 6, 7, 7];
  print(list.distinct(by: (item) => item));
  // [1, 2, 3, 4, 5, 6, 7]

  final items = [Item('foo', 1), Item('bar', 2), Item('foo', 3), Item('bar', 4), Item('foo', 4), Item('bar', 3)];
  print(items.distinct(by: (item) => item.name));
  // [Item{name: foo, id: 1}, Item{name: bar, id: 2}]

  print(items.distinct(by: (item) => item.id));
  // [Item{name: foo, id: 1}, Item{name: bar, id: 2}, Item{name: foo, id: 3}, Item{name: bar, id: 4}]
}
0

It works for me.

var list = [
 {"id": 1, "name": "Joshua"},
 {"id": 2, "name": "Joshua"},
 {"id": 3, "name": "Shinta"},
 {"id": 4, "name": "Shinta"},
 {"id": 5, "name": "Zaidan"}
];
list.removeWhere((element) => element.name == element.name.codeUnitAt(1));
list.sort((a, b) => a.name.compareTo(b.name));

Output:

[{"id": 1, "name": "Joshua"}, 
{"id": 3, "name": "Shinta"}, 
{"id": 5, "name": "Zaidan"}]
Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Dec 09 '21 at 01:47
0
List<Model> bigList = [];
List<ModelNew> newList = [];  

for (var element in bigList) {
      var list = newList.where((i) => i.type == element.type).toList();
      if(list.isEmpty){
       newList.add(element);
      }
    }
  • 1
    There are **17 existing answers** to this question, including a top-voted, accepted answer with over **three hundred votes**. Are you _certain_ your solution hasn't already been given? If not, why do you believe your approach improves upon the existing proposals, which have been validated by the community? Offering an explanation is _always_ useful on Stack Overflow, but it's _especially_ important where the question has been resolved to the satisfaction of both the OP and the community. Help readers out by explaining what your answer does different and when it might be preferred. – Jeremy Caney Mar 07 '22 at 00:15
  • Please read "[answer]". It helps more if you supply an explanation why this is the preferred solution and explain how it works. We want to educate, not just provide code. – the Tin Man Mar 11 '22 at 05:54
0

Create method to remove duplicates from Array and return Array of unique elements.

class Utilities {
  static List<String> uniqueArray(List<String> arr) {
    List<String> newArr = [];
    for (var obj in arr) {
      if (newArr.contains(obj)) {
        continue;
      }
      newArr.add(obj);
    }
    return newArr;
  }
}
Mohd Danish Khan
  • 1,044
  • 11
  • 12
0

You can use the following way:

void main(List <String> args){
    List<int> nums = [1, 2, 2, 2, 3, 4, 5, 5];
    List<int> nums2 = nums.toSet().toList();
}

NOTE: This will not work if the items in the list are objects of class and have the same attributes. So, to solve this, you can use the following way:

void main() {
  List<Medicine> objets = [Medicine("Paracetamol"),Medicine("Paracetamol"), Medicine("Benylin")];
  
  List <String> atributs = [];
  objets.forEach((element){
    atributs.add(element.name);
  });
  
  List<String> noDuplicates = atributs.toSet().toList();
  print(noDuplicates);
}

class Medicine{
  final String name;
  Medicine(this.name);
}
0

Remove duplicate data from any type of map or list.

 var list = [
     {"id": 1, "name": "piyush"},
     {"id": 2, "name": "jay"},
     {"id": 3, "name": "premal"},
     {"id": 4, "name": "piyush"},
     {"id": 5, "name": "nishant"}
    ];
    
    final ids = Set();
    randomProgramsList.retainWhere((x) => ids.add(x["name"]));
P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
-1

This is my solution

    List<T> removeDuplicates<T>(List<T> list, IsEqual isEqual) {
      List<T> output = [];
      for(var i = 0; i < list.length; i++) {
        bool found = false;
        for(var j = 0; j < output.length; j++) {
          if (isEqual(list[i], output[j])) {
            found = true;
          }
        }
        if (found) {
          output.add(list[i]);
        }
      }

      return output;
    }

Use it like this:

  var theList = removeDuplicates(myOriginalList, (item1, item2) => item1.documentID == item2.documentID);

or...

  var theList = removeDuplicates(myOriginalList, (item1, item2) => item1.equals(item2));

or...

-12

I have a library called Reactive-Dart that contains many composable operators for terminating and non-terminating sequences. For your scenario it would look something like this:

final newList = [];
Observable
   .fromList(['abc', 'abc', 'def'])
   .distinct()
   .observe((next) => newList.add(next), () => print(newList));

Yielding:

[abc, def]

I should add that there are other libraries out there with similar features. Check around on GitHub and I'm sure you'll find something suitable.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
John Evans
  • 6,858
  • 4
  • 28
  • 26