Let's talk about performance. There is a threshold in the neighbourhood of ~30-100 input size (depending on method and density of odd numbers) before which overhead of Numpy makes it slower, and beyond which pure Python code will never be faster than vectorized code.
If you need to use pure Python as you are now, replace your approach with that of stefan. If you don't need to use pure Python, and especially if you expect long input, use Numpy. To compare,
from timeit import timeit from typing import Sequence import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns from numpy.random import default_rng def orig(source_array: Sequence[int]) -> list[int]: z,e = [],[] o = list(enumerate(source_array)) [z.append(i) if i[1] % 2 == 1 else e.append(i) for i in o] z.sort(key=lambda z: z[1]) [z.insert(i[0],i) for i in e] return [l[1] for l in z] def fmc(nums: Sequence[int]) -> list[int]: odds = [n for n in nums if n % 2] odds.sort(reverse=True) return [ odds.pop() if n % 2 else n for n in nums ] def stefan(source_array: Sequence[int]) -> list[int]: odds = sorted(n for n in source_array if n%2) iter_odd = iter(odds) return [next(iter_odd) if n%2 else n for n in source_array] def with_array_index(source_array: np.ndarray) -> np.ndarray: output = source_array.copy() odds_idx, = (source_array % 2).nonzero() output[odds_idx] = np.sort(output[odds_idx]) return output METHODS = orig, stefan, fmc, with_array_index def test() -> None: slybot_input = np.array([0, 1, 9, 5, 4, 8, 2, 4, 1, 2, 3, 2, 10, 11, 25, 25, 24, 35, 1, 2, 3, 4, 5]) expected = np.array([0, 1, 1, 1, 4, 8, 2, 4, 3, 2, 3, 2, 10, 5, 5, 9, 24, 11, 25, 2, 25, 4, 35]) for method in METHODS: orig_input = slybot_input.copy() output = method(slybot_input) assert np.all(output == expected) assert np.all(orig_input == slybot_input) def time() -> None: rand = default_rng(seed=0) sizes = (10**np.linspace(start=1, stop=4, num=30)).astype(int) measurements = [] for size in sizes: for odd_density in (0.1, 0.5, 0.9): np_inputs = 2*rand.integers(low=-1000, high=1000, size=size) np_inputs[rand.random(size) < odd_density] += 1 list_inputs = np_inputs.tolist() all_inputs = (list_inputs,) * (len(METHODS)-2) + (np_inputs,)*2 reps = 2 * sizes.max() // size for _ in range(reps): for method, inputs in zip(METHODS, all_inputs): def run(): return method(inputs) dur = timeit(stmt=run, number=1) measurements.append((size, odd_density, method.__name__, dur)) df = pd.DataFrame( data=measurements, columns=('size', 'odd_density', 'method', 'duration'), ) fig, ax = plt.subplots() sns.lineplot(ax=ax, data=df, x='size', y='duration', hue='method', style='odd_density') ax.set_xscale('log') ax.set_yscale('log') plt.show() if __name__ == '__main__': test() time()
