自定义类 SparseList<T> 怎么样:
- 一个列表,允许您通过将缺失值设置为 null来删除项目(或者对于 SparseValueList,一个特殊的值,如 -1(如 Dr ABT 的解决方案)、0(默认(T))或 int.MinValue 或 uint.MaxValue, ) 和
- 然后维护一个已删除索引的列表(一个 Stack<int>)。然后,当您需要添加到列表中时,它会从堆栈中弹出一个已删除的索引并在那里添加值。(对于多线程,也许 ConcurrentQueue<int> 将是另一个想法。)
- 枚举器可以跳过已删除的项目(以支持foreach)
- 枚举期间可以从集合中删除项目!(我必须经常这样做,所以这很好。)
- indexer 提供对包含空值的列表的原始访问。因此,如果您使用 for(;;) 循环,请准备好过滤掉空值。
- 如果/当您想删除所有空值时调用Compact()
- 如果在游戏中从不调用 compact,我担心会遍历大量空值。因此,对于紧凑的实验性替代方案,请参阅 SparseListCleaningEnumerator: auto-shrink the list each time it is enumerated, 至少对于单线程情况:当 MoveNext 离开一个项目时,它会查看堆栈以查看索引是否较低并且如果是这样,它将项目分配给较低的索引,将其从当前位置删除,这将缩小列表。平衡可能需要多次迭代并在优化之前涉及多次移动,除非堆栈被排序列表替换,或者堆栈偶尔排序。如果最后一个值为空,这将不起作用,因为索引将被埋在空闲索引堆栈中(用排序的东西替换堆栈可以避免这种情况)。
我实现了这个(尚未测试),但我存储了对我的游戏实体而不是 id 的实际引用,因此您需要以某种方式将其调整为 int 或 Nullable。(确定我回答您的问题的 int/uint 要求,我还添加了一个略有不同的 SparseValueList<T>,使用 default(T) 而不是 null。这意味着您不能在列表中使用 0。)您也许可以如果您认为不需要,请取出版本控制——大多数游戏可能不需要。
/// <summary>
/// Specifying null as value has unspecified results.
/// CopyTo may contain nulls.
/// </summary>
/// <typeparam name="T"></typeparam>
public class SparseList<T> : IList<T>
where T : class
{
int version = 0;
List<T> list = new List<T>();
Stack<int> freeIndices = new Stack<int>();
public int Capacity { get { return list.Capacity; } set { list.Capacity = value; } }
public void Compact()
{
var sortedIndices = freeIndices.ToList();
foreach (var i in sortedIndices.OrderBy(x => x).Reverse())
{
list.RemoveAt(i);
}
freeIndices.Clear();
list.Capacity = list.Count;
version++; // breaks open enumerators
}
public int IndexOf(T item)
{
return list.IndexOf(item);
}
/// <summary>
/// Slow (forces a compact), not recommended
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void Insert(int index, T item)
{
// One idea: remove index from freeIndices if it's in there. Stack doesn't support this though.
Compact(); // breaks the freeIndices list, so apply it before insert
list.Insert(index, item);
version++; // breaks open enumerators
}
public void RemoveAt(int index)
{
if (index == Count - 1) { list.RemoveAt(index); }
else { list[index] = null; freeIndices.Push(index); }
//version++; // Don't increment version for removals
}
public T this[int index]
{
get
{
return list[index];
}
set
{
if (value == null) throw new ArgumentNullException();
list[index] = value;
}
}
public void Add(T item)
{
if (item == null) throw new ArgumentNullException();
if (freeIndices.Count == 0) { list.Add(item); return; }
list[freeIndices.Pop()] = item;
//version++; // Don't increment version for additions? It could result in missing the new value, but shouldn't break open enumerators
}
public void Clear()
{
list.Clear();
freeIndices.Clear();
version++;
}
public bool Contains(T item)
{
if (item == null) return false;
return list.Contains(item);
}
/// <summary>
/// Result may contain nulls
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public void CopyTo(T[] array, int arrayIndex)
{
list.CopyTo(array, arrayIndex);
}
//public void CopyNonNullTo(T[] array, int arrayIndex)
//{
//}
/// <summary>
/// Use this for iterating via for loop.
/// </summary>
public int Count { get { return list.Count; } }
/// <summary>
/// Don't use this for for loops! Use Count.
/// </summary>
public int NonNullCount
{
get { return list.Count - freeIndices.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
int i = list.IndexOf(item);
if (i < 0) return false;
if (i == list.Count - 1)
{
// Could throw . Could add check in
list.RemoveAt(i);
}
else
{
list[i] = null;
freeIndices.Push(i);
}
//version++; // Don't increment version for removals
return true;
}
public IEnumerator<T> GetEnumerator()
{
return new SparseListEnumerator(this);
}
private class SparseListEnumerator : IEnumerator<T>, IRemovingEnumerator
{
SparseList<T> list;
int version;
int index = -1;
public SparseListEnumerator(SparseList<T> list)
{
this.list = list;
this.version = list.version;
//while (Current == null && MoveNext()) ;
}
public T Current
{
get {
if (index >= list.Count) return null; // Supports removing last items of collection without throwing on Enumerator access
return list[index];
}
}
public void Dispose()
{
list = null;
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
do
{
if (version != list.version) { throw new InvalidOperationException("Collection modified"); }
index++;
return index < list.Count;
} while (Current == null);
}
public void Reset()
{
index = -1;
version = list.version;
}
/// <summary>
/// Accessing Current after RemoveCurrent may throw a NullReferenceException or return null.
/// </summary>
public void RemoveCurrent()
{
list.RemoveAt(index);
}
}
private class SparseListCleaningEnumerator : IEnumerator<T>, IRemovingEnumerator
{
SparseList<T> list;
int version;
int index = -1;
public SparseListCleaningEnumerator(SparseList<T> list)
{
this.list = list;
this.version = list.version;
//while (Current == null && MoveNext()) ;
}
public T Current
{
get
{
if (index >= list.Count) return null; // Supports removing last items of collection without throwing on Enumerator access
return list[index];
}
}
public void Dispose()
{
list = null;
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
do
{
if (version != list.version) { throw new InvalidOperationException("Collection modified"); }
if (index > 0
&& Current != null // only works for values that are set, otherwise the index is buried in the free index stack somewhere
)
{
int freeIndex = list.freeIndices.Peek();
if (freeIndex < index)
{
list.freeIndices.Pop();
list[freeIndex] = list[index];
list.RemoveAt(index);
}
}
index++;
return index < list.Count;
} while (Current == null);
}
public void Reset()
{
index = -1;
version = list.version;
}
/// <summary>
/// Accessing Current after RemoveCurrent may throw a NullReferenceException or return null.
/// </summary>
public void RemoveCurrent()
{
list.RemoveAt(index);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
/// <summary>
/// Like SparseList but Supports value types, using default(T) in place of null.
/// This means values of default(T) are not permitted as values in the collection.
/// CopyTo may contain default(T).
/// TODO: Use EqualityComparer<T>.Default instead of default(T).Equals()
/// </summary>
/// <typeparam name="T"></typeparam>
public class SparseValueList<T> : IList<T>
{
int version = 0;
List<T> list = new List<T>();
Stack<int> freeIndices = new Stack<int>();
public int Capacity { get { return list.Capacity; } set { list.Capacity = value; } }
public void Compact()
{
var sortedIndices = freeIndices.ToList();
foreach (var i in sortedIndices.OrderBy(x => x).Reverse())
{
list.RemoveAt(i);
}
freeIndices.Clear();
list.Capacity = list.Count;
version++; // breaks open enumerators
}
public int IndexOf(T item)
{
return list.IndexOf(item);
}
/// <summary>
/// Slow (forces a compact), not recommended
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
public void Insert(int index, T item)
{
// One idea: remove index from freeIndices if it's in there. Stack doesn't support this though.
Compact(); // breaks the freeIndices list, so apply it before insert
list.Insert(index, item);
version++; // breaks open enumerators
}
public void RemoveAt(int index)
{
if (index == Count - 1) { list.RemoveAt(index); }
else { list[index] = default(T); freeIndices.Push(index); }
//version++; // Don't increment version for removals
}
public T this[int index]
{
get
{
return list[index];
}
set
{
if (default(T).Equals(value)) throw new ArgumentNullException();
list[index] = value;
}
}
public void Add(T item)
{
if (default(T).Equals(item)) throw new ArgumentNullException();
if (freeIndices.Count == 0) { list.Add(item); return; }
list[freeIndices.Pop()] = item;
//version++; // Don't increment version for additions? It could result in missing the new value, but shouldn't break open enumerators
}
public void Clear()
{
list.Clear();
freeIndices.Clear();
version++;
}
public bool Contains(T item)
{
if (default(T).Equals(item)) return false;
return list.Contains(item);
}
/// <summary>
/// Result may contain default(T)'s
/// </summary>
/// <param name="array"></param>
/// <param name="arrayIndex"></param>
public void CopyTo(T[] array, int arrayIndex)
{
list.CopyTo(array, arrayIndex);
}
//public void CopyNonNullTo(T[] array, int arrayIndex)
//{
//}
/// <summary>
/// Use this for iterating via for loop.
/// </summary>
public int Count { get { return list.Count; } }
/// <summary>
/// Don't use this for for loops! Use Count.
/// </summary>
public int NonNullCount
{
get { return list.Count - freeIndices.Count; }
}
public bool IsReadOnly
{
get { return false; }
}
public bool Remove(T item)
{
int i = list.IndexOf(item);
if (i < 0) return false;
if (i == list.Count - 1)
{
// Could throw . Could add check in
list.RemoveAt(i);
}
else
{
list[i] = default(T);
freeIndices.Push(i);
}
//version++; // Don't increment version for removals
return true;
}
public IEnumerator<T> GetEnumerator()
{
return new SparseValueListEnumerator(this);
}
private class SparseValueListEnumerator : IEnumerator<T>, IRemovingEnumerator
{
SparseValueList<T> list;
int version;
int index = -1;
public SparseValueListEnumerator(SparseValueList<T> list)
{
this.list = list;
this.version = list.version;
//while (Current == default(T) && MoveNext()) ;
}
public T Current
{
get
{
if (index >= list.Count) return default(T); // Supports removing last items of collection without throwing on Enumerator access
return list[index];
}
}
public void Dispose()
{
list = null;
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
do
{
if (version != list.version) { throw new InvalidOperationException("Collection modified"); }
index++;
return index < list.Count;
} while (default(T).Equals(Current));
}
public void Reset()
{
index = -1;
version = list.version;
}
/// <summary>
/// Accessing Current after RemoveCurrent may throw a NullReferenceException or return default(T).
/// </summary>
public void RemoveCurrent()
{
list.RemoveAt(index);
}
}
private class SparseValueListCleaningEnumerator : IEnumerator<T>, IRemovingEnumerator
{
SparseValueList<T> list;
int version;
int index = -1;
public SparseValueListCleaningEnumerator(SparseValueList<T> list)
{
this.list = list;
this.version = list.version;
while (default(T).Equals(Current) && MoveNext()) ;
}
public T Current
{
get
{
if (index >= list.Count) return default(T); // Supports removing last items of collection without throwing on Enumerator access
return list[index];
}
}
public void Dispose()
{
list = null;
}
object IEnumerator.Current
{
get { return Current; }
}
public bool MoveNext()
{
do
{
if (version != list.version) { throw new InvalidOperationException("Collection modified"); }
if (index > 0
&& (!default(T).Equals(Current)) // only works for values that are set, otherwise the index might be buried in the stack somewhere
)
{
int freeIndex = list.freeIndices.Peek();
if (freeIndex < index)
{
list.freeIndices.Pop();
list[freeIndex] = list[index];
list.RemoveAt(index);
}
}
index++;
return index < list.Count;
} while (default(T).Equals(Current));
}
public void Reset()
{
index = -1;
version = list.version;
}
/// <summary>
/// Accessing Current after RemoveCurrent may throw a NullReferenceException or return default(T).
/// </summary>
public void RemoveCurrent()
{
list.RemoveAt(index);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}