c#用对象按值排序列表

本文关键字:排序 列表 对象 | 更新日期: 2023-09-27 18:14:20

我试图在c#中创建一个"有序"的对象缓存,其中顺序由被访问的次数决定。

我已经查看了Dictionary, SortedList和SortedDictionary,它们非常接近,但不完全有我想要的。

我想有一个列表,其中包含所有以前缓存的项目,这些项目可以有一个getHits()方法来确定缓存项目应该在什么顺序。

然后我可以通过名称访问缓存,并增加项目被查看的次数。

简化示例(在伪c# 中):
class Result {
  public int Hits = 0;
  public string Name = "";
  public void IncreaseHits() {
    this.hits++;
  }
  public Result(String name) {
    this.name = name;
  }
}
class Program {
  public MagicSortableType<string, Result> MyCache; //what structure to use?

  public main() {
    MyCache.Add(new Result("My result 1"));
    MyCache.Add(new Result("My result 2"));
    MyCache.Add(new Result("My result 3"));
    MyCache['My result 2'].IncreaseHits();
    MyCache['My result 2'].IncreaseHits();
    MyCache['My result 3'].IncreaseHits();
    MyCache.SortDesc(); //what is the real C# equivalent?
    foreach(Result result in MyCache) {
      Console.Write(result.Name + " - hits " + result.Hits);
    }
  }
}

输出:

My result 2 - hits 2
My result 3 - hits 1
My result 1 - hits 0

c#用对象按值排序列表

当我需要这样的东西时,我创建了一个我称之为MruDictionary的东西。它由一个LinkedList<T>和一个Dictionary<string, LinkedListNode<T>>组成(其中T为对象类型,对象键为类型string)。

通过字典访问。当一个项被访问时,它被移动到列表的头部。当添加一个项时,它被添加到列表的头部。如果列表的大小超过设置的最大值,则删除列表中的最后一个节点。

这工作得很好。这些物品不是按照使用次数排序的,而是按照严格的MRU顺序排列的。这个通常将最常用的项保存在缓存中,但是如果一个常用的项很长一段时间没有被使用,它将被刷新。对于我的目的,这工作得很好。

我写了一篇关于它的文章。完整的源代码和描述可在http://www.informit.com/guides/content.aspx?g=dotnet&seqNum=626找到。

如果你真的需要的话,添加点击数应该很容易。

基于您的伪代码,这似乎是有效的:

var MyCache = new Dictionary<string, Result>
{
    {"My result 1", new Result("My result 1")},
    {"My result 2", new Result("My result 2")},
    {"My result 3", new Result("My result 3")},
    {"My result 4", new Result("My result 4")}
};
MyCache["My result 2"].IncreaseHits();
MyCache["My result 2"].IncreaseHits();
MyCache["My result 3"].IncreaseHits();
foreach (var result in MyCache.OrderByDescending(x => x.Value.Hits))
{
    Console.WriteLine(result.Value.Name + " - hits " + result.Value.Hits);
}

我想你需要这样的东西:

SortedDictionary<string,int> MyCache = new SortedDictionary<string, int>();
string strKey = "NewResult";
if (MyCache.ContainsKey(strKey))
{
    MyCache[strKey] = MyCache[strKey] + 1;
}
else
{
    MyCache.Add(strKey, 1);
}

但是SortedDictionary是按键

排序的

SortedDictionary - MSDN

表示按键排序的键/值对的集合。

您可以将字典提取到List<KeyValuePair<string,int>>,然后根据值对它们进行排序,如:

List<KeyValuePair<string, int>> list = MyCache.ToList();
foreach (var item in list.OrderByDescending(r=> r.Value))
{
    Console.WriteLine(item.Key+ " - hits " + item.Value);
} 

所以你可以输入:

class Program
{
    public static SortedDictionary<string, int> MyCache = new SortedDictionary<string, int>();
    static void Main(string[] args)
    {
        AddToDictionary("Result1");
        AddToDictionary("Result1");
        AddToDictionary("Result2");
        AddToDictionary("Result2");
        AddToDictionary("Result2");
        AddToDictionary("Result3");
        List<KeyValuePair<string, int>> list = MyCache.ToList();
        foreach (var item in list.OrderByDescending(r=> r.Value))
        {
            Console.WriteLine(item.Key+ " - hits " + item.Value);
        } 

    }
    public static void AddToDictionary(string strKey)
    {
        if (MyCache.ContainsKey(strKey))
        {
            MyCache[strKey] = MyCache[strKey] + 1;
        }
        else
        {
            MyCache.Add(strKey, 1);
        }
    }
}

那么输出将是:

Result2 - hits 3
Result1 - hits 2
Result3 - hits 1

想知道你是否在追求这样的东西。

可以存储两组关系;所有的对象,按键检索,使检索速度快,所有的对象按Hits排序存储。这样做还有一个额外的好处,就是可以加快访问速度——你可以很快地得到ResultHits,从而得到当前和下一个索引。

当获取结果时,我们锁定访问以确保自动更改其顺序,然后返回对象。我们在记录点击次数时也会作弊;我们知道最受欢迎的是什么,然后我们可以倒着遍历这个集合——甚至可以提取List<Int32>的键,对其排序,然后遍历它。

public class PopularityContest{
    private Dictionary<int, List<Result>> PopularityContainer { get; set; }
    private Dictionary<String, Result> ResultContainer { get; set; }
    private int MaxPopularity = 0;
    public PopularityContest(){
        PopularityContainer = new Dictionary<int, List<Result>>();
        ResultContainer = new Dictionary<String, Result>();
    }
    private Object _SyncLock = new Object();
    public Result GetResult(string resultKey)
    {
      Result result = ResultContainer[resultKey];
      lock(_SyncLock)
      {
        int currentHits = result.Hits;
        if(PopularityContainer.ContainsKey(currentHits) && PopularityContainer[currentHits].Contains(result))
        {
           PopularityContainer[currentHits].Remove(result);
        }
        if(!PopularityContainer.ContainsKey(currentHits + 1))
        {
          PopularityContainer.Add(currentHits + 1, new List<Result>());
        }
        PopularityContainer[currentHits + 1].Add(Result);
        if((currentHits + 1) > MaxPopularity) { MaxPopularity = currentHits + 1;}
      }
      return result;
    }

    public void WritePopularity()
    {
      //Here could also extract the keys to a List<Int32>, sort it, and walk that.
      //Note, as this is a read operation, dependent upon ordering, you would also consider locking here.
      for(int i = MaxPopularity; i >= 0; i--)
      {
         if(PopularityContainer.Contains(i) && PopularityContainer[i].Count > 0)
         {
            //NB the order of items at key[i] is the order in which they achieved their popularity
            foreach(Result result in PopularityContainer[i])
            {
            Console.WriteLine(String.Format("{0} has had {1} hits", result.ToString(), i));
            }
         }
      }
    }
}

下面的Cache公开了一个简单的Add/Get接口,用于从缓存中添加和检索项,这显然可以得到改进。它实现了IEnumerable,它通过所需的行为枚举缓存。这里显然有线程问题需要解决。

public class Cache<T>: IEnumerable<T>
{
    //Dictionary to hold the values of the cache
    private Dictionary<string, T> m_cacheStore = new Dictionary<string, T>();
    //Dictionary to hold the number of times each key has been accessed
    private Dictionary<string, int> m_cacheAccessCount = new Dictionary<string, int>(); 
    public T Get(string cacheKey)
    {
        if (m_cacheStore.ContainsKey(cacheKey))
        {
            //Increment access counter
            if (!m_cacheAccessCount.ContainsKey(cacheKey))
                m_cacheAccessCount.Add(cacheKey, 0);
            m_cacheAccessCount[cacheKey] = m_cacheAccessCount[cacheKey] + 1;
            return m_cacheStore[cacheKey];
        }
        throw new KeyNotFoundException(cacheKey);
    }
    public int GetHits(string cacheKey)
    {
        return m_cacheAccessCount.ContainsKey(cacheKey) ? m_cacheAccessCount[cacheKey] : 0;
    }
    public void Add(string cacheKey, T cacheValue)
    {
        if(m_cacheStore.ContainsKey(cacheKey))
            throw new ArgumentException(string.Format("An element with the key {0} already exists in the cache", cacheKey));
        m_cacheStore.Add(cacheKey, cacheValue);
    }
    #region Implementation of IEnumerable
    public IEnumerator<T> GetEnumerator()
    {
        foreach (var source in m_cacheAccessCount.OrderBy(kvp => kvp.Value))
        {
            yield return m_cacheStore[source.Key];
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
    #endregion
}

正确的方法是在MyCache类中实现IComparable (http://msdn.microsoft.com/en-us/library/system.icomparable.aspx)接口。

这将暴露一个名为CompareTo的方法,您必须在代码中编写该方法。

你只需要创建那个方法并在里面放一些逻辑来说明这个对象是否大于,小于或等于传入的对象

然后在客户端代码中使用int result = MyCache1.ComparTo(MyCache2);

结果将是-1 0或1基于是否大于小于等于。

这个呢:

var MyCache = new SortedDictionary<string, int?>();
MyCache['My result 2'] = (MyCache['My result 2'] ?? 0) + 1;

你想要这样的东西吗?

public class Result {
  public int Hits = 0;
  public string Name = "";
  public void IncreaseHits() {
    this.hits++;
  }
  public Result(String name) {
    this.name = name;
  }
}
class Program {
   public Dictionary<string, Result> MyCache; //what structure to use?

   public main() {
    MyCache.Add("My result 1", new Result("My result 1"));
    MyCache.Add("My result 2", new Result("My result 2"));
    MyCache.Add("My result 3", new Result("My result 3"));
    MyCache["My result 2"].IncreaseHits();
    MyCache["My result 2"].IncreaseHits();
    MyCache["My result 3"].IncreaseHits();
   foreach(Result result in MyCache.Values.OrderByDesc(x => x.Hits)) {
      Console.Write(result.Name + " - hits " + result.Hits);
   }
  }
}

或者

public class MyCacheClass {
   private Dictionary<string,Result> cache = new Dictionary<string, Result>();
   public void IncreaseHits(string name) {
      Result cached;
      if (!cache.TryGetValue(name, out cached)) {
        cached = cache.Add(new Result(name));
      }
      cached.IncreaseHits();
   }
   public string Add(string name) {
      // Need to block duplicates....
      cache.Add(name, new Result(name));
   }
   public IEnumerable<Result> SortDesc {
      get { return cache.Values.OrderByDesc(x => x.Hits); }
   }
}

class Program {
   MyCacheClass MyCache = new MyCacheClass();
   MyCache.Add("result1");
   MyCache.IncreaseHits("My result 2");
   MyCache.IncreaseHits("My result 2");
   MyCache.IncreaseHits("My result 3");
   foreach(Result result in MyCache.SorDesc) {
      Console.WriteLine(string.Format("{0} - hits {1}",result.Name,result.Hits);
   }
}

为什么不使用经典的List并对其排序,使用sort方法并编写自己的比较委托?

MyCache.Sort(delegate(Result a, Result b)
   {
      if (a.hits > b.hits) return -1;
      if (a.hits < b.hits) return 1;
      return 0;
   });

如果需要按键访问,可以有2个结构。一个用于快速访问,另一个用于保存已排序的数据。

Dictionary<String, Result> accessMap;
List<Result> MyCache;
accessMap["Object 1"] = obj1;
MyCache.add(obj1);
accessMap[Object 1].Increase();
//sort MyCache    
foreach(Result result in MyCache) {
  Console.Write(result.Name + " - hits " + result.Hits);
}