Skip to content

Commit 9e3525d

Browse files
Add a new trick
1 parent 6ce7913 commit 9e3525d

2 files changed

Lines changed: 55 additions & 131 deletions

File tree

ExtendedSystemObjects/IntArray.cs

Lines changed: 31 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -126,89 +126,55 @@ public void RemoveAt(int index)
126126
/// Removes all specified indices from the array
127127
/// </summary>
128128
/// <param name="indicesToRemove">The indices to remove.</param>
129-
public void RemoveMultiple(int[] indicesToRemove)
129+
/// <summary>
130+
/// Removes multiple elements efficiently. If the indices are sequential and sorted,
131+
/// uses a fast bulk-removal path. Otherwise falls back to element-wise compaction.
132+
/// </summary>
133+
/// <param name="indices">Indices to remove. Must be sorted and within bounds.</param>
134+
public void RemoveMultiple(ReadOnlySpan<int> indices)
130135
{
131-
if (indicesToRemove == null || indicesToRemove.Length == 0)
132-
{
133-
return;
134-
}
136+
if (indices.Length == 0) return;
135137

136-
// Sort indices ascending (important!)
137-
Array.Sort(indicesToRemove);
138+
// === Fast-path: Trivial sequential sequence ===
139+
// If [3,4,5,6] then indices[^1] - indices[0] == indices.Length - 1
140+
if (indices.Length > 1 && indices[^1] - indices[0] == indices.Length - 1)
141+
{
142+
int start = indices[0];
143+
int count = indices.Length;
138144

139-
// Validate indices within bounds
140145
#if DEBUG
141-
foreach (var idx in indicesToRemove)
142-
{
143-
if (idx < 0 || idx >= Length)
144-
{
145-
throw new IndexOutOfRangeException($"Index {idx} is out of range.");
146-
}
147-
}
146+
if (start < 0 || start + count > Length)
147+
throw new IndexOutOfRangeException();
148148
#endif
149149

150-
if (IsSequential(indicesToRemove))
151-
{
152-
// Optimized path for continuous block removal
153-
var start = indicesToRemove[0];
154-
var count = indicesToRemove.Length;
155-
156-
// Move tail elements left by count positions
157-
var tailLength = Length - (start + count);
158-
if (tailLength > 0)
159-
{
160-
Buffer.MemoryCopy(
161-
_ptr + start + count,
162-
_ptr + start,
163-
tailLength * sizeof(int),
164-
tailLength * sizeof(int)
165-
);
166-
}
150+
int moveCount = Length - (start + count);
151+
if (moveCount > 0)
152+
Buffer.MemoryCopy(_ptr + start + count, _ptr + start, moveCount * sizeof(int), moveCount * sizeof(int));
167153

168154
Length -= count;
155+
return;
169156
}
170-
else
171-
{
172-
// General multi-block removal, no assumption on indices continuity
173157

174-
var srcIndex = 0;
175-
var dstIndex = 0;
158+
// === Fallback: Compact array by skipping indices ===
159+
int readIndex = 0, writeIndex = 0, removeIndex = 0;
176160

177-
foreach (var removeIndex in indicesToRemove)
161+
while (readIndex < Length)
162+
{
163+
if (removeIndex < indices.Length && readIndex == indices[removeIndex])
178164
{
179-
var lengthToCopy = removeIndex - srcIndex;
180-
181-
if (lengthToCopy > 0)
182-
{
183-
Buffer.MemoryCopy(
184-
_ptr + srcIndex,
185-
_ptr + dstIndex,
186-
(Length - dstIndex) * sizeof(int),
187-
lengthToCopy * sizeof(int)
188-
);
189-
dstIndex += lengthToCopy;
190-
}
191-
192-
srcIndex = removeIndex + 1;
165+
readIndex++;
166+
removeIndex++;
193167
}
194-
195-
// Copy remaining tail block after last removed index
196-
var tailLength = Length - srcIndex;
197-
if (tailLength > 0)
168+
else
198169
{
199-
Buffer.MemoryCopy(
200-
_ptr + srcIndex,
201-
_ptr + dstIndex,
202-
(Length - dstIndex) * sizeof(int),
203-
tailLength * sizeof(int)
204-
);
205-
dstIndex += tailLength;
170+
_ptr[writeIndex++] = _ptr[readIndex++];
206171
}
207-
208-
Length = dstIndex;
209172
}
173+
174+
Length = writeIndex;
210175
}
211176

177+
212178
/// <summary>
213179
/// Resizes the internal array to the specified new size.
214180
/// Contents will be preserved up to the minimum of old and new size.
@@ -242,31 +208,6 @@ public Span<int> AsSpan()
242208
return new((void*)_buffer, Length);
243209
}
244210

245-
/// <summary>
246-
/// Determines whether the specified sorted indices is sequential.
247-
/// </summary>
248-
/// <param name="sortedIndices">The sorted indices.</param>
249-
/// <returns>
250-
/// <c>true</c> if the specified sorted indices is sequential; otherwise, <c>false</c>.
251-
/// </returns>
252-
private static bool IsSequential(IReadOnlyList<int> sortedIndices)
253-
{
254-
if (sortedIndices is not { Count: > 1 })
255-
{
256-
return true;
257-
}
258-
259-
for (var i = 1; i < sortedIndices.Count; i++)
260-
{
261-
if (sortedIndices[i] != sortedIndices[i - 1] + 1)
262-
{
263-
return false;
264-
}
265-
}
266-
267-
return true;
268-
}
269-
270211
/// <summary>
271212
/// Finalizes an instance of the <see cref="IntArray" /> class.
272213
/// </summary>

ExtendedSystemObjects/Utility.cs

Lines changed: 24 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -255,58 +255,41 @@ public static List<KeyValuePair<int, int>> Sequencer(List<int> numbers, int sequ
255255
/// <returns>List of Sequences, with start and end index, null if none were found.</returns>
256256
public static List<KeyValuePair<int, int>> Sequencer(List<int> numbers, int stepWidth, int sequenceLength)
257257
{
258+
if (numbers == null || numbers.Count == 0 || sequenceLength <= 1)
259+
return null;
260+
258261
numbers.Sort();
259-
var max = numbers.Max();
262+
var numberSet = new HashSet<int>(numbers);
260263

261-
var sequenceGroups = new List<List<int>>();
262-
var currentSequence = new List<int>();
263-
var observer = new List<int>();
264+
var result = new List<KeyValuePair<int, int>>();
265+
var visited = new HashSet<int>();
264266

265-
foreach (var element in numbers)
267+
foreach (var num in numbers)
266268
{
267-
var cache = Math.Abs(element);
268-
var count = cache;
269-
270-
do
271-
{
272-
if (currentSequence.Contains(cache))
273-
{
274-
break;
275-
}
276-
277-
count += stepWidth;
278-
279-
if (observer.Contains(count))
280-
{
281-
continue;
282-
}
283-
284-
if (!numbers.Contains(count))
285-
{
286-
break;
287-
}
269+
if (visited.Contains(num))
270+
continue;
288271

289-
currentSequence.Add(count);
290-
observer.Add(count);
291-
} while (count < max);
272+
int current = num;
273+
int streak = 1;
292274

293-
if (currentSequence.Count == 0)
275+
// Try to build sequence by jumping stepWidth repeatedly
276+
while (numberSet.Contains(current + stepWidth))
294277
{
295-
continue;
278+
current += stepWidth;
279+
streak++;
296280
}
297281

298-
currentSequence.AddFirst(cache);
299-
sequenceGroups.Add(currentSequence);
300-
currentSequence = new List<int>();
282+
if (streak >= sequenceLength)
283+
{
284+
result.Add(new KeyValuePair<int, int>(num, current));
285+
286+
// Mark all in this sequence as visited to avoid duplicates
287+
for (int val = num; val <= current; val += stepWidth)
288+
visited.Add(val);
289+
}
301290
}
302291

303-
return sequenceGroups.Count == 0
304-
? null
305-
: (from stack in sequenceGroups
306-
where stack.Count >= sequenceLength
307-
let start = stack[0]
308-
let end = stack[^1]
309-
select new KeyValuePair<int, int>(start, end)).ToList();
292+
return result.Count == 0 ? null : result;
310293
}
311294

312295
/// <summary>

0 commit comments

Comments
 (0)