Credence.Pattern.NoUniqThenCount (credence v0.7.1)

Copy Markdown

Detects Enum.uniq/1 piped into length/1 or Enum.count/1, which creates an unnecessary intermediate list only to count its size.

Why this matters

LLMs frequently produce Enum.uniq |> length() pipelines to count distinct elements. This allocates an intermediate list that is immediately consumed by a linear scan:

# Flagged — intermediate list from uniq
items |> Enum.uniq() |> length()
items |> Enum.uniq() |> Enum.count()

# Better — MapSet deduplicates on insertion, no intermediate list
items |> MapSet.new() |> MapSet.size()

MapSet.new/1 builds the set in a single pass, and MapSet.size/1 returns the count in O(1).

Why the rewrite is exact

Enum.uniq/1 and MapSet both deduplicate using map-key (exact, ===) equality — 1 and 1.0 stay distinct under both, +0.0 and -0.0 stay distinct under both. The number of distinct elements is therefore identical, and length/1/Enum.count/1 and MapSet.size/1 all return that cardinality. The source enumerable is evaluated exactly once on the left of the pipe in both forms.

Flagged patterns

Enum.uniq() piped into length/1 or Enum.count/1, in two forms:

  • piped uniq: enum |> Enum.uniq() |> length()
  • head uniq: Enum.uniq(enum) |> Enum.count()

Not flagged

  • Enum.uniq() alone (without count/length following)
  • Enum.uniq |> Enum.map(...) (uniq used for further processing)
  • Enum.uniq |> Enum.count(predicate) (a filtered count, different op)