【问题标题】:C# Sorted List by Value with ObjectC# 使用对象按值排序列表
【发布时间】:2013-05-20 12:24:04
【问题描述】:

我正在尝试在 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

【问题讨论】:

  • 您尝试过的解决方案在哪些方面不符合您的要求?
  • 对不起,我不明白你的评论。你能解释一下吗?
  • 什么是MyCache.SortDesc()?你怎么在没有MyCache.Add(new Result("My result 1")); 键的情况下添加SortedDictionary
  • 我上面概述的示例不是真正的 C#,它是我“希望”能够做的,以帮助解释我的问题。我添加了更多细节。
  • MRU(因此访问最多的结果)位于列表顶部。

标签: c# sortedlist


【解决方案1】:

当我需要这样的东西时,我创建了我称之为MruDictionary 的东西。它由LinkedList&lt;T&gt;Dictionary&lt;string, LinkedListNode&lt;T&gt;&gt; 组成(其中T 是对象类型,对象键是string 类型)。

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

这非常有效。这些物品没有按使用次数排列,而是按照严格的 MRU 顺序排列。这通常将最常用的项目保留在缓存中,但如果有很长一段时间没有使用热门项目,它将被刷新。就我的目的而言,这非常有效。

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

如果您确实需要,添加点击计数应该很容易。

【讨论】:

  • 这听起来像是一个非常强大的解决方案,实际上正是我想要构建的。将通过文章看一下!
  • 我实际上实现了建议的组合,但这与我放在一起的非常接近。
【解决方案2】:

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

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

【讨论】:

  • 如果我想维护缓存(最多几个小时),我是否只需要用 LINQ 查询的输出完全替换“MyCache”?
  • 你当然可以用 -> 替换你的MyCache (如果你需要我会编辑并包含),但是我对解决方案感到不安。
  • 理想情况下我想要一种就地排序解决方案
  • 这里你肯定有排序,你甚至可以用排序的替换MyCache。
  • 或者就地排序是指,每次增加点击次数时,Result 都会重新排列它的位置并“自行排序”?
【解决方案3】:

我猜你需要类似的东西:

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是按key排序的

SortedDictionary - MSDN

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

您可以将字典提取到List&lt;KeyValuePair&lt;string,int&gt;&gt;,然后根据以下值对它们进行排序:

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

【讨论】:

  • 但是我完全失去了对 Result 对象的访问权限,我想我可以使用相同的键创建一个哈希图。
  • @PezCuckow,您可以有一个List&lt;KeyValuePair&lt;string,int&gt;&gt;,您可以根据键访问项目并根据值对其进行排序。
  • 如果是List,根据key来访问item并不容易。你必须循环。抱歉状态。
【解决方案4】:

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

你可以存储两组关系;所有对象,通过key来快速检索,所有对象通过Hits来存储排序。这具有加快访问速度的额外优势 - 您可以获得ResultHits,因此它很快就会成为当前索引和下一个索引。

当获取结果时,我们锁定访问以确保我们以原子方式更改它的顺序,然后返回对象。我们在写出点击次数时也会作弊;我们知道最受欢迎的是什么,然后我们可以向后遍历该集合 - 甚至可以提取 List&lt;Int32&gt; 的键,对其进行排序,然后对其进行迭代。

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

      }
    }

}

【讨论】:

    【解决方案5】:

    下面的 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
    }
    

    【讨论】:

    • 值得一提的是,这个实现不需要显式的“Increase Hits”调用,而是在访问所需的缓存项时自动递增。如果您希望从类的使用者显式增加计数器,则可以将适当的方法添加到公共接口。
    【解决方案6】:

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

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

    您只需创建该方法并在其中放入一些逻辑,说明该对象是否大于、小于或等于传入的对象。

    然后你通过说int result = MyCache1.ComparTo(MyCache2);在你的客户端代码中使用它

    结果将是 -1 0 或 1 取决于它是否大于小于或等于。

    【讨论】:

    • 我已经为结果类组合了一个 IComparable,我可以将这些结果存储在什么结构中以允许我 Sort() 他们?
    【解决方案7】:

    这个呢:

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

    【讨论】:

      【解决方案8】:

      你想要这样的东西吗?

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

      【讨论】:

      • 是的,但不必调用 foreach,希望能够对结构进行 Sort() 以供以后使用
      • @Pez var forLater = MyCache.Values.OrderByDesc(x=&gt;x.Hits).ToList()
      【解决方案9】:

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

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

      【讨论】:

      • 因为我无法按键访问经典列表。
      • 您可以使用 List 进行排序,使用 Dictionary 来访问对象。字典不会被排序,但没关系,因为你只是用它来访问。列表将被排序打印(其他操作)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-18
      • 1970-01-01
      • 2012-06-26
      • 1970-01-01
      • 1970-01-01
      • 2021-09-16
      相关资源
      最近更新 更多