Credence.Rule.NoEnumAtMidpointAccess (credence v0.3.2)

Copy Markdown

Performance rule: Flags Enum.at/2 with a midpoint index inside non-recursive functions.

Elixir lists are linked lists. Enum.at/2 is an O(n) operation. When the index is derived from midpoint arithmetic (e.g. div(low + high, 2)), the code almost certainly wants O(1) random access. Converting the list to a tuple with List.to_tuple/1 and using elem/2 achieves this.

Auto-fixable

The fix inserts a List.to_tuple/1 call at the top of the enclosing function body and replaces every flagged Enum.at/2 call with elem/2.

Pattern: direct call with midpoint variable

# Before
def find(list, low, high) do
  mid = low + div(high - low, 2)
  Enum.at(list, mid)
end

# After
def find(list, low, high) do
  list_tuple = List.to_tuple(list)
  mid = low + div(high - low, 2)
  elem(list_tuple, mid)
end

Pattern: piped call

# Before
def find(list, low, high) do
  mid = div(low + high, 2)
  list |> Enum.at(mid)
end

# After
def find(list, low, high) do
  list_tuple = List.to_tuple(list)
  mid = div(low + high, 2)
  elem(list_tuple, mid)
end

Pattern: inline midpoint expression

# Before
def find(list, low, high) do
  Enum.at(list, div(low + high, 2))
end

# After
def find(list, low, high) do
  list_tuple = List.to_tuple(list)
  elem(list_tuple, div(low + high, 2))
end

Pattern: multiple lists

# Before
def compare(keys, values, low, high) do
  mid = low + div(high - low, 2)
  k = Enum.at(keys, mid)
  v = Enum.at(values, mid)
  {k, v}
end

# After
def compare(keys, values, low, high) do
  keys_tuple = List.to_tuple(keys)
  values_tuple = List.to_tuple(values)
  mid = low + div(high - low, 2)
  k = elem(keys_tuple, mid)
  v = elem(values_tuple, mid)
  {k, v}
end

Pattern: inside anonymous function (e.g. Enum.reduce_while)

# Before
def search(list, target) do
  Enum.reduce_while(0..100, {0, length(list) - 1}, fn _, {low, high} ->
    mid = low + div(high - low, 2)
    mid_val = Enum.at(list, mid)
    ...
  end)
end

# After — conversion at top of enclosing def, NOT inside the fn
def search(list, target) do
  list_tuple = List.to_tuple(list)
  Enum.reduce_while(0..100, {0, length(list) - 1}, fn _, {low, high} ->
    mid = low + div(high - low, 2)
    mid_val = elem(list_tuple, mid)
    ...
  end)
end

See also Credence.Rule.NoEnumAtBinarySearch which catches the same anti-pattern in recursive functions (not auto-fixable).