Skip to content

Remover duplicados de uma lista, preservando a ordem snippet

Deduplicar parece trivial — meter num set — e é exatamente aí que vivem dois bugs.

Deduplicar parece trivial — meter num set — e é exatamente aí que vivem dois bugs. Primeiro, os sets deduplicam por igualdade, mas listas de OBJETOS são comparadas por identidade/hash na maioria das linguagens, pelo que duas linhas visualmente iguais sobrevivem; dedupe por uma chave, não pelo objeto. Segundo, os sets baseados em hash destroem a ordem de inserção; o idiom que a preserva varia por linguagem (dict.fromkeys em Python, LinkedHashSet em Java, um map indexado por array em Go). O Postgres destaca-se com o DISTINCT ON — mantém a primeira linha por chave segundo qualquer ordenação que escolher.

Receita executável · 14 linguagens
Files & Datadedupeuniquesetordercollections

Every language

14 linguagens, copy-ready. One at a time with syntax highlighting, or all inline.

SQLSQLrunnable
-- Postgres DISTINCT ON: keep the FIRST row per key by any sort
SELECT DISTINCT ON (email) id, email, created_at
FROM subscribers
ORDER BY email, created_at DESC; -- newest per email survives

DISTINCT ON (key) + ORDER BY key, <tiebreaker> picks WHICH duplicate survives — a power none of the set idioms have. Run it in the playground with a duplicate email.

Run in the SQL playground →
JSJavaScript
// primitives, insertion order kept:
const unique = [...new Set(items)];

// objects, deduped BY KEY, order kept:
const seen = new Set();
const uniqueByKey = items.filter((item) => {
  if (seen.has(item.id)) return false;
  seen.add(item.id);
  return true;
});

Spread-into-Set dedupes primitives only — two {id: 1} objects are different references and both survive. The filter-with-Set variant is the by-key form.

TSTypeScript
function uniqueBy<T, K>(items: readonly T[], key: (item: T): K): T[] {
  const seen = new Set<K>();
  return items.filter((item) => {
    const k = key(item);
    if (seen.has(k)) return false;
    seen.add(k);
    return true;
  });
}

The generic uniqueBy composes with any key — dedupe users by id, events by timestamp+source. Keep the Set outside the map if you reuse it.

GoGo
seen := make(map[string]struct{}) // struct{}: zero bytes per key
unique := make([]Item, 0, len(items))

for _, item := range items {
	if _, dup := seen[item.ID]; dup {
		continue
	}
	seen[item.ID] = struct{}{}
	unique = append(unique, item)
}

map[string]struct{} instead of map[string]bool — the empty struct allocates nothing; the comma-ok idiom reads presence. Order preserved by the slice, not the map.

RsRust
use std::collections::HashSet;

let mut seen = HashSet::new();
let unique: Vec<&Item> = items
    .iter()
    .filter(|item| seen.insert(item.id))
    .collect();

HashSet::insert returns TRUE when the value is new — the whole filter collapses into one call. Vec::dedup()/dedup_by() only removes CONSECUTIVE duplicates; sort first or use the set.

PHPPHP
// primitives — but array_unique keeps ORIGINAL KEYS (gaps!):
$unique = array_values(array_unique($items));

// objects by key, order kept:
$seen = [];
$uniqueByKey = array_filter($items, function ($item) use (&$seen) {
    if (isset($seen[$item['id']])) return false;
    $seen[$item['id']] = true;
    return true;
});

array_unique compares as strings by default (8 vs '8' collide unless SORT_NUMERIC) and preserves keys — array_values reindexes or your json_encode output becomes an object.

PyPython
# primitives, order kept (dicts are insertion-ordered since 3.7):
unique = list(dict.fromkeys(items))

# objects by key:
seen = set()
unique_by_key = [x for x in items
                 if x['id'] not in seen and not seen.add(x['id'])]

set(items) loses order. dict.fromkeys(values) dedupes AND remembers first-seen order in one call — the idiomatic one-liner. The comprehension's seen.add trick relies on add returning None.

C++C++
#include <algorithm>
#include <unordered_set>
#include <vector>

// sort + unique + erase — output is SORTED, order not kept:
std::sort(v.begin(), v.end());
v.erase(std::unique(v.begin(), v.end()), v.end());

// first-seen order kept, dedupe by key:
std::unordered_set<std::string> seen;
std::vector<Item> unique;
for (const auto& item : items)
    if (seen.insert(item.id).second) unique.push_back(item);

std::unique only compacts CONSECUTIVE duplicates — the sort before it is not optional, it is what brings the equal elements together. Use that idiom when sorted output is fine; for first-seen order use the unordered_set seen-guard (insert().second is the was-new test).

C#C#
using System.Linq;

var unique = items.Distinct();            // by Equals/GetHashCode
var byKey = items.DistinctBy(i => i.Id);  // .NET 6+, order kept

DistinctBy arrived in .NET 6 (before that: GroupBy(i => i.Id).Select(g => g.First())). All LINQ operators preserve encounter order unless documented otherwise.

JvJava
import java.util.*;

// primitives/records by equals — ORDER kept by LinkedHashSet:
List<Item> unique = new ArrayList<>(
        new LinkedHashSet<>(items));

// by key:
Set<Object> seen = new HashSet<>();
List<Item> uniqueByKey = items.stream()
        .filter(i -> seen.add(i.id())) // add() = true when new
        .toList();

HashSet would scramble order — LinkedHashSet is the order-preserving set. Records get structural equals for free; classic classes need equals/hashCode implemented or dedupe silently fails.

SwSwift
// primitives — order NOT guaranteed after Set roundtrip:
let unique = Array(Set(items))

// order-preserving, by key:
var seen = Set<String>()
let byKey = items.filter { seen.insert($0.id).inserted }

Set(insert:) returns a tuple (inserted, memberAfterInsert) — .inserted is the one-call test. Sort the Set result or use the filter for stable output.

KtKotlin
val unique = items.distinct()              // by equals, order kept
val byKey = items.distinctBy { it.id }     // by key, first wins

distinctBy is stdlib and reads exactly like the intent. Both keep first-occurrence order.

RbRuby
unique = items.uniq                    # primitives, order kept
unique_by_key = items.uniq { |item| item[:id] } # by key, first wins

uniq compares with eql?/hash — for structs/arrays by value, for objects by identity. The block form dedupes by the computed key and keeps the first occurrence.

ZigZig
var seen = std.StringHashMap(void).init(alloc);
defer seen.deinit();

var unique = std.ArrayList(Item).init(alloc);
for (items) |item| {
    const gop = try seen.getOrPut(item.id);
    if (!gop.found_existing) try unique.append(item);
}

HashMap(void) is the zero-size value set. getOrPut does the single lookup; found_existing decides keep-or-skip, and the ArrayList keeps the order.