Skip to content

Fix negative indexing bug and improve readability in wiggle_sort - #15371

Merged
cclauss merged 5 commits into
TheAlgorithms:masterfrom
Ewanjohndennis:patch-5
Sep 18, 2026
Merged

cclauss merged 5 commits into
TheAlgorithms:masterfrom
Ewanjohndennis:patch-5

Conversation

@Ewanjohndennis

@Ewanjohndennis Ewanjohndennis commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Issue:

  • The previous implementation used enumerate(nums), starting the loop at i = 0. This caused nums[i - 1] to evaluate to nums[-1], accidentally comparing (and potentially swapping) the first element with the last element of the array on the first iteration.
  • The conditional logic (i % 2 == 1) == (nums[i - 1] > nums[i]) was convoluted, hard to read, and triggered unnecessary swaps when adjacent numbers were equal.

Fix:

  • Changed the loop to use range(1, len(nums)) to ensure the index safely starts at 1, eliminating the negative indexing bug.
  • Replaced the confusing equality check with explicit if/elif statements that clearly define the peak (odd indices) and valley (even indices) requirements of a Wiggle Sort.
  • Preserved all original docstrings and the main block as-is.

Describe your change

  • Add an algorithm?
  • Fix a bug or typo in an existing algorithm?
  • Add or change doctests? -- Note: Please avoid changing both code and tests in a single pull request.
  • Documentation change?

Checklist

  • I have read CONTRIBUTING.md.
  • This pull request is all my own work -- I have not plagiarized.
  • I know that pull requests will not be merged if they fail the automated tests.
  • This PR only changes one algorithm file. To ease review, please open separate PRs for separate algorithms.
  • All new Python files are placed inside an existing directory.
  • All filenames are in all lowercase characters with no spaces or dashes.
  • All functions and variable names follow Python naming conventions.
  • All function parameters and return values are annotated with Python type hints.
  • All functions have doctests that pass the automated testing.
  • All new algorithms include at least one URL that points to Wikipedia or another similar explanation.
  • If this pull request resolves one or more open issues, then the description above includes the issue number(s) with a closing keyword: "Fixes #ISSUE-NUMBER".

Issue:
- The previous implementation used `enumerate(nums)`, starting the loop at `i = 0`. This caused `nums[i - 1]` to evaluate to `nums[-1]`, accidentally comparing (and potentially swapping) the first element with the last element of the array on the first iteration.
- The conditional logic `(i % 2 == 1) == (nums[i - 1] > nums[i])` was convoluted, hard to read, and triggered unnecessary swaps when adjacent numbers were equal.

Fix:
- Changed the loop to use `range(1, len(nums))` to ensure the index safely starts at 1, eliminating the negative indexing bug.
- Replaced the confusing equality check with explicit `if/elif` statements that clearly define the peak (odd indices) and valley (even indices) requirements of a Wiggle Sort. 
- Preserved all original docstrings and the __main__ block as-is.
@algorithms-keeper algorithms-keeper Bot added awaiting reviews This PR is ready to be reviewed enhancement This PR modified some existing files labels Sep 17, 2026
@algorithms-keeper algorithms-keeper Bot added the tests are failing Do not merge until tests pass label Sep 17, 2026
Ewanjohndennis and others added 3 commits September 17, 2026 20:51
- Updated `wiggle_sort` logic to merge the odd and even swap conditions using a single `or` expression.
- Fixes SIM114 ruff linter check failure (`Combine if branches using logical or operator`).
- Updated expected outputs for negative array doctests in `sorts/wiggle_sort.py`.
- The previous doctest expectations relied on the incorrect behavior caused by the index-0 negative lookup bug.
- Fixes pytest doctest mismatch failure in CI build job.
@algorithms-keeper algorithms-keeper Bot removed the tests are failing Do not merge until tests pass label Sep 17, 2026
@cclauss

cclauss commented Sep 17, 2026

Copy link
Copy Markdown
Member

@priya-sundaram-dev, please review. Does sorts/wiggle_sort.py really have a bug? If so, is this the right fix?

@cclauss

cclauss commented Sep 17, 2026

Copy link
Copy Markdown
Member

ON HOLD: Our focus is on merging or closing old pull requests before October 1st.

@priya-sundaram-dev

Copy link
Copy Markdown
Contributor

I dug into this. Short version: the new code is a genuine readability win and its logic is correct, but I'd frame the PR as clarity, not a correctness fix — because the old code doesn't actually produce wrong output.

Is there really a bug? The i - 1 at i = 0 does wrap around to nums[-1], so the first iteration can swap the first and last elements — that part of the description is true. But wiggle sort has many valid answers (any arrangement satisfying nums[0] <= nums[1] >= nums[2] <= …), and it turns out the old code always lands on a valid one. I checked exhaustively for all lists of length ≤ 8 over a small value set, plus 200k random cases with negatives/duplicates — zero cases where the old code returned a non-wiggle result or a non-permutation. So the wrap-around is a confusing no-op in practice, not a source of wrong answers.

That's also why the doctests had to change: e.g. wiggle_sort([-2, -5, -45]) went from [-45, -2, -5] to [-5, -2, -45]. Both satisfy the wiggle property, so the old doctest wasn't wrong either — the new code just picks a different valid arrangement.

Is the fix right? Yes. range(1, len(nums)) removes the wrap-around, and the expanded condition

(i % 2 == 1 and nums[i - 1] > nums[i]) or (i % 2 == 0 and nums[i - 1] < nums[i])

is exactly the correct swap rule (odd i: fix nums[i-1] > nums[i]; even i: fix nums[i-1] < nums[i]), and it avoids the needless swap-on-equal the old ==-of-booleans trick did. Much easier to read.

Suggestion: retitle to something like "Clarify wiggle_sort logic and start loop at index 1" and reword the description to say the old code did a spurious first/last swap (harmless but confusing) rather than "accidentally comparing … the first with the last" implying broken output. With that framing this is a clean 👍 from me.

@cclauss cclauss added awaiting changes A maintainer has requested changes to this PR and removed awaiting reviews This PR is ready to be reviewed labels Sep 17, 2026
@algorithms-keeper algorithms-keeper Bot removed the awaiting changes A maintainer has requested changes to this PR label Sep 18, 2026
@cclauss
cclauss merged commit b0e607a into TheAlgorithms:master Sep 18, 2026
6 checks passed
@priya-sundaram-dev

Copy link
Copy Markdown
Contributor

Reviewed. Recommendation: merge as a readability/robustness cleanup — but the "bug" framing overstates it.

The refactor itself is good and I'd take it:

  • for i in range(1, len(nums)) removes the accidental wraparound at i=0, where the old loop compared nums[-1] (the last element) with nums[0] and could do a spurious end-to-front swap. That negative-index access was never intentional, so dropping it is a real improvement.
  • The explicit (i % 2 == 1 and nums[i-1] > nums[i]) or (i % 2 == 0 and nums[i-1] < nums[i]) reads far more clearly than the old (i % 2 == 1) == (nums[i-1] > nums[i]), and the docstring now actually states the wiggle invariant.

One correction for the PR title/description, though: this does not fix incorrect sort output. I exhaustively checked the old implementation over every multiset of length ≤ 7 with values 0–4 (and 20k random cases up to length 7) — it always produced a valid wiggle (nums[0] <= nums[1] >= nums[2] <= ...). The single greedy adjacent pass is self-correcting for any starting order, so the stray i=0 swap was wasteful and confusing but not wrong. The updated doctest outputs ([-5, -2, -45], [-5.68, -2.1, -45.11]) are simply a different — equally valid — wiggle ordering the cleaner loop produces, and they pass.

So: happy to see it merged for clarity and to kill the negative-index wraparound; I'd just retitle it something like "wiggle_sort: drop accidental negative-index swap and clarify" rather than "fix bug", so the history is accurate.

@cclauss

cclauss commented Sep 18, 2026

Copy link
Copy Markdown
Member

Merged 4 hours ago. ;-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement This PR modified some existing files on hold

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants