Skip to content

Make OrderedCollection.IndexOf and Remove find the first occurrence - #64

Merged
matt-edmondson merged 1 commit into
mainfrom
claude/containers-62-indexof-first-occurrence
Sep 26, 2026
Merged

matt-edmondson merged 1 commit into
mainfrom
claude/containers-62-indexof-first-occurrence

Conversation

@matt-edmondson

Copy link
Copy Markdown
Contributor

Fixes #62

Problem

OrderedCollection<T>.IndexOf and Remove are documented to act on the first occurrence. Both used BinarySearch, which returns whichever equal element the midpoint hits. So [1,1,1].IndexOf(1) returned 1, [0,2,2,2,2,3].IndexOf(2) returned 2, and under a key-based comparer Remove could drop a different record with the same key.

Fix

  • A new private FindFirst does a lower-bound binary search: on a match it records mid and keeps searching left.
  • IndexOf uses FindFirst.
  • Remove starts at FindFirst, removes the first element in the equal-key run that also Equals the argument, and falls back to the first element in the run when none does.
  • BinarySearch keeps its existing "any match" behaviour, which Add relies on for insertion. Its XML doc now says that.

Tests

New tests in OrderedCollectionTests:

  • IndexOf_OddLengthDuplicateRun_ReturnsFirstOccurrence ([1,1,1] gives 0)
  • IndexOf_EvenLengthDuplicateRun_ReturnsFirstOccurrence ([0,2,2,2,2,3] gives 1)
  • Remove_KeyComparerWithDuplicateKeys_RemovesTheElementPassedIn
  • Remove_KeyComparerWithNoExactMatch_RemovesFirstOccurrence

With the fix reverted, all 4 fail. With it, the full suite passes (377/377).

This PR is independent of #63; both branch from main.

🤖 Generated with Claude Code

https://claude.ai/code/session_01UHW69XTeuuLbfVdQdJ8uQh


Generated by Claude Code

…nce [patch]

IndexOf and Remove were built on BinarySearch, which returns whichever
equal element the midpoint lands on. With duplicates, IndexOf(1) on [1,1,1]
returned 1 rather than the documented first occurrence, and Remove could
drop a different element with the same key under a key-based comparer.

Both now use a lower-bound search. Remove prefers the element in the
equal-key run that also Equals the argument, falling back to the first.
BinarySearch keeps its any-match contract for insertion, and its doc now
says so.

Fixes #62

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHW69XTeuuLbfVdQdJ8uQh
@sonarqubecloud

Copy link
Copy Markdown

@matt-edmondson
matt-edmondson merged commit 40a7ca1 into main Sep 26, 2026
12 checks passed
@matt-edmondson
matt-edmondson deleted the claude/containers-62-indexof-first-occurrence branch September 26, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

OrderedCollection<T>.IndexOf returns an arbitrary duplicate instead of the documented first occurrence

2 participants