维持序数集合的适当顺序
本文关键字:顺序 集合 序数 | 更新日期: 2023-09-27 17:58:31
我有一个简单的域对象:
class FavoriteFood
{
public string Name;
public int Ordinal;
}
我想要一个这个域对象的集合来维护正确的序数。例如,给定4种最喜欢的食物:
Name: Banana, Ordinal: 1
Name: Orange, Ordinal: 2
Name: Pear, Ordinal: 3
Name: Watermelon, Ordinal: 4
如果我把梨的序数改为4,西瓜的序数应该会改为3。
如果我添加一种新的最喜欢的食物(草莓),序号为3,它应该会将梨增加到4,西瓜增加到5。
如果我把Pear的序数改为2,它应该把Orange改为3。
如果我把西瓜的序数改为1,香蕉会变成2,橘子会变成3,梨会变成4。
实现这一目标的最佳方法是什么?
UPDATE:域对象的name属性是动态的,并且基于用户输入。对象必须具有此Ordinal属性,因为用户可以更改他们喜爱的食物的显示顺序。这个序数值保存在数据库中,当填充结构时,我不能保证项目是按序数顺序添加的。
我遇到的问题是,当底层域对象发生更改时,没有一个好的方法来更新列表中的其余项。例如:
var favoriteFoods = new List<FavoriteFood>();
var banana = new FavoriteFood { Name = "Banana", Ordinal = 1};
favoriteFoods.Add(banana);
favoriteFoods.Add(new FavoriteFood { Name = "Orange", Ordinal = 2 });
banana.Ordinal = 2;
// at this point both Banana and Orange have the same ordinal in the list. How can we make sure that Orange's ordinal gets updated too?
到目前为止,我已经尝试过做以下工作:
class FavoriteFood : INotifyPropertyChanging
{
public string Name;
public int Ordinal
{
get { return this.ordinal; }
set
{
var oldValue = this.ordinal;
if (oldValue != value && this.PropertyChanging != null)
{
this.PropertyChanging(new FavoriteFoodChangingObject { NewOrdinal = value, OldOrdinal = oldValue }, new PropertyChangingEventArgs("Ordinal"));
}
this.ordinal = value;
}
}
internal struct FavoriteFoodChangingObject
{
internal int NewOrdinal;
internal int OldOrdinal;
}
// THIS IS A TEMPORARY WORKAROUND
internal int ordinal;
public event PropertyChangingEventHandler PropertyChanging;
}
public class FavoriteFoodCollection : IEnumerable<FavoriteFood>
{
private class FavoriteFoodOrdinalComparer : IComparer<FavoriteFood>
{
public int Compare(FavoriteFood x, FavoriteFood y)
{
return x.Ordinal.CompareTo(y.Ordinal);
}
}
private readonly SortedSet<FavoriteFood> underlyingList = new SortedSet<FavoriteFood>(new FavoriteFoodOrdinalComparer());
public IEnumerator<FavoriteFood> GetEnumerator()
{
return this.underlyingList.GetEnumerator();
}
public void AddRange(IEnumerable<FavoriteFood> items)
{
foreach (var i in items)
{
this.underlyingList.Add(i);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private void UpdateOrdinalsDueToRemoving(FavoriteFood item)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal > item.Ordinal))
{
i.ordinal--;
}
}
public void Remove(FavoriteFood item)
{
this.underlyingList.Remove(item);
this.UpdateOrdinalsDueToRemoving(item);
}
public void Add(FavoriteFood item)
{
this.UpdateOrdinalsDueToAdding(item);
this.underlyingList.Add(item);
item.PropertyChanging += this.item_PropertyChanging;
}
private void item_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (e.PropertyName.Equals("Ordinal"))
{
var ordinalsChanging = (FavoriteFood.FavoriteFoodChangingObject)sender;
this.UpdateOrdinalsDueToEditing(ordinalsChanging.NewOrdinal, ordinalsChanging.OldOrdinal);
}
}
private void UpdateOrdinalsDueToEditing(int newOrdinal, int oldOrdinal)
{
if (newOrdinal > oldOrdinal)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal <= newOrdinal && x.Ordinal > oldOrdinal))
{
//i.Ordinal = i.Ordinal - 1;
i.ordinal--;
}
}
else if (newOrdinal < oldOrdinal)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal >= newOrdinal && x.Ordinal < oldOrdinal))
{
//i.Ordinal = i.Ordinal + 1;
i.ordinal++;
}
}
}
private void UpdateOrdinalsDueToAdding(FavoriteFood item)
{
foreach (var i in this.underlyingList.Where(x => x.Ordinal >= item.Ordinal))
{
i.ordinal++;
}
}
}
这可以正常工作,但使用内部Ordinal字段是一个奇怪的解决方法。它是必需的,这样PropertyChangingEvent就不会被无限引发。
只需使用List<string>
:
List<string> foods = new List<string> { "Banana", "Orange", "Pear" };
int ordinalOfOrange = foods.IndexOf("Orange");
如果必须改变你描述的方式,那么"存储"这个序数不是一个好主意。
听起来你想要一个SortedList。使用序号作为关键字添加每个项目。
我会做如下操作:
public class FavoriteFoods
{
StringComparer comparer ;
List<string> list ;
public FavoriteFoods()
{
this.list = new List<string>() ;
this.comparer = StringComparer.InvariantCultureIgnoreCase ;
return ;
}
public void Add( string food , int rank )
{
if ( this.list.Contains(food,comparer ) ) throw new ArgumentException("food") ;
this.list.Insert(rank,food) ;
return ;
}
public void Remove( string food )
{
this.list.Remove( food ) ;
return ;
}
public void ChangeRank( string food , int newRank )
{
int currentRank = this.list.IndexOf(food) ;
if ( currentRank < 0 ) throw new ArgumentOutOfRangeException("food") ;
if ( newRank < 0 ) throw new ArgumentOutOfRangeException("newRank") ;
if ( newRank >= this.list.Count ) throw new ArgumentOutOfRangeException("newRank") ;
if ( newRank != currentRank )
{
this.Remove(food) ;
this.Add( food , newRank ) ;
}
return ;
}
public int GetRank( string food )
{
int rank = this.list.IndexOf(food) ;
if ( rank < 0 ) throw new ArgumentOutOfRangeException("food");
return rank ;
}
public IEnumerable<string> InRankOrder()
{
foreach ( string food in this.list )
{
yield return food ;
}
}
}
让我重述一下你的问题。
您有一组字符串。你有一组序数。
您希望能够快速查找字符串的序号。和序数的字符串。您还希望能够插入具有给定序数的字符串。并更改字符串的序号。
有两条路要走。第一种简单的方法是按顺序存储字符串集合,并了解它们的序数。您可以在时间O(n)
扫描列表。您还可以查找、插入、移动和删除每个O(n)
。如果你真的不在乎表现,那么我强烈建议你这样做。
如果您确实关心性能,那么您将需要构建一个自定义的数据结构。最简单的想法是有两棵树。一棵树按字母顺序存储字符串,告诉字符串在另一棵树中的位置。另一棵按序号顺序存储字符串并存储不同子树中的内容数量。
下面是您的基本操作。
- 插入。在第二棵树的正确位置插入(如果您选择在这个过程中移动任何其他东西,更新第一棵树中的那些东西),然后在第一棵树插入字符串
- 按字符串查找。搜索第一棵树,找到它在第二棵树中的位置,在第二颗树中往回走,找到它的序数
- 按序号查找。搜索第二棵树,找到字符串
- 删除。从两个树中删除
- 移动序号。从旧位置的第二棵树上移除。在新位置插入第二棵树。更新第一个树中的所有适当节点
对于简单的版本,您可以只使用树。如果你想变得新奇,你可以查找B树、红黑树和其他类型的自平衡树,然后从中选择一棵。
如果您对此进行了正确编程,则可以保证所有操作都需要时间O(log(n))
。然而,会有很多持续的开销,对于小的收集来说,与简单的方法相比,聪明的努力可能是一种损失。