保持一系列序数的正确顺序

时间:2011-06-03 20:05:45

标签: c# algorithm data-structures

我有一个简单的域对象:

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

如果我将Pear的序数改为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不会被无限提升。

4 个答案:

答案 0 :(得分:3)

只需使用List<string>

List<string> foods = new List<string> { "Banana", "Orange", "Pear" };
int ordinalOfOrange = foods.IndexOf("Orange");

如果必须改变你描述的方式,那么“存储”那个序数并不是一个好主意。

答案 1 :(得分:0)

听起来你想要一个SortedList。使用它的序数作为关键字添加每个项目。

答案 2 :(得分:0)

我会做以下事情:

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 ;
    }
  }

}

答案 3 :(得分:0)

让我重申你的问题。

你有一组字符串。你有一系列的序数。

您希望能够快速查找字符串的序数。和一串序数。您还希望能够插入具有给定序数的字符串。并改变字符串的序数。

有两种方法可以去。第一个简单的方法是按顺序存储字符串的集合,并了解它们的序数。您可以及时扫描列表O(n)。您还可以按时O(n)查找,插入,移动和删除。如果你实际上并不关心性能,那么我强烈建议你这样做。

如果您关心性能,那么您需要构建自定义数据结构。最简单的想法是拥有两棵树。一棵树按字母顺序存储字符串,并告诉您字符串在另一棵树中的位置。另一棵树按顺序存储字符串,并存储各种子树中的东西数量。

现在这是您的基本操作。

  • 插入。在第二个树中插入正确的位置(如果您选择在过程中移动其他任何内容,在第一个树中更新这些内容),则将字符串插入第一个树中。
  • 按字符串查找。搜索第一棵树,找到第二棵树的位置,然后走回第二棵树找到它的序数。
  • 按顺序查找。搜索第二棵树,找到字符串。
  • 删除。从两棵树上删除。
  • 移动序数。从旧位置的第二棵树中删除。插入新位置的第二个树。更新第一个树中的所有适当节点。

对于简单版本,您可以使用树。如果你想要花哨,你可以查找B树,红黑树和其他类型的自平衡树,然后选择其中一种。

如果您正确编程,则可以保证所有操作都需要时间O(log(n))。然而,会有很多不断的开销,对于小型集合而言,相对于简单的方法而言,聪明的努力可能是一种损失。