我正在寻找一种排名算法,该算法将按使用量和最近的使用量对项目(文件、应用程序、访问过的网站...)进行排序。
例如,在应用程序启动器中,用户键入应用程序名称的一些短前缀,满足条件的应用程序将被排名。app A 是用户最喜欢的 app,使用频率很高,但现在他经常使用 app B,只是偶尔使用 app A。app A 的启动次数比 app B 多,但 B 上次的使用次数比 A 多。
所以应用 B 排在应用 A 之前。
此外,如果应用 C 想要超过应用 B,它必须被更频繁地使用(最近一段时间),但对于应用 A 来说,它不需要太多的使用,因为它是用户最喜欢的应用,并且过去比其他应用程序有更多的用途。
我不知道这是否是我想要的一个很好的解释,但我希望有些人会理解。
我认为您可以使用缓存替换策略的实现来实现这一点。
这些算法帮助计算机处理器 (CPU) 确定将主存储器 (RAM) 的哪些部分(“页面”)保留在缓存(L1、L2 等)中——CPU 可以比 RAM 更快地访问这些部分。但是它们可以很容易地适应您的问题。
这种种的最近使用的项目将类似于高速缓存策略的算法LRU其到期/替换大号东- [R ecently- ü SED页面时缓存满了。
这种种的最频繁的使用项的算法(这里是“频繁”的真正含义“使用次数”)将类似于缓存策略LFU其到期/替换大号朝东˚F requently- ü sed的页面时缓存满了。
有几种策略以您请求的方式显式或隐式地结合了这两个概念。有些还涉及“时间”(就实际计算机时钟时间而言,或者仅仅是页面请求的递增计数器等),以更好地了解页面访问的“年龄”或“频率”(而不是简单的使用次数) )。
这些变得稍微复杂和难以实现,特别是如果你需要一个非常有效的算法。但是对于大多数与用户界面相关的用途,即使是低效的算法也应该足够快,因为项目的数量很少,用户很少会修改列表。
一种可能对您有用的算法示例是LRFU(最近最少使用/最常使用)策略,该策略直接寻求结合 LRU 和 LFU 以根据结合了新近度和使用频率的公式来使页面过期。您可以在上面列出的同一页面上找到对此的参考。您还可以查看报道它的学术文章。
文章使用堆和链表数据结构的组合来实现它,并且它包含一些用于实现的伪代码。
对于您的用途,您可能可以很容易地编写一个更简单但效率较低的算法。
例如,您可以简单地存储具有 2 个属性的对象数组:
每当用户选择一个值时,您可以按如下方式修改列表:
USAGE_WEIGHT(0.5 和 1.0 之间的数字,下面将详细讨论)来更新列表中所有现有项目的分数。该USAGE_WEIGHT因素决定了“新近度”与“频率”(也就是使用次数)相比的重要性。值为 0.5 将导致列表具有完全 LRU 行为(仅新近度重要),而值为 1.0 将导致列表具有完全 LFU 行为(仅使用计数重要)。中间值(例如 0.9)将导致列表具有 LRU 和 LFU 行为的混合,如下面的示例输出所示。
在下面的每个场景中,“值”都是字母,它们按以下顺序添加:
A B C B A A D A C D A B D E C B A
Run Code Online (Sandbox Code Playgroud)
每次添加后,我都会在引号中列出与当前 MRU 列表一起添加的字母(例如“DBA”)。最大 MRU 大小为 3。我还列出了列表的更详细表示,显示了表单中每个项目的值(字母)和分数{ Letter, Score }。
1. (Added A) "A" [ { A, 1.0 } ]
2. (Added B) "AB" [ { A, 1.0 } { B, 1.0 } ]
3. (Added C) "ABC" [ { A, 1.0 } { B, 1.0 } { C, 1.0 } ]
4. (Added B) "BAC" [ { B, 2.0 } { A, 1.0 } { C, 1.0 } ]
5. (Added A) "BAC" [ { B, 2.0 } { A, 2.0 } { C, 1.0 } ]
6. (Added A) "ABC" [ { A, 3.0 } { B, 2.0 } { C, 1.0 } ]
7. (Added D) "ABD" [ { A, 3.0 } { B, 2.0 } { D, 1.0 } ]
8. (Added A) "ABD" [ { A, 4.0 } { B, 2.0 } { D, 1.0 } ]
9. (Added C) "ABC" [ { A, 4.0 } { B, 2.0 } { C, 1.0 } ]
10. (Added D) "ABD" [ { A, 4.0 } { B, 2.0 } { D, 1.0 } ]
11. (Added A) "ABD" [ { A, 5.0 } { B, 2.0 } { D, 1.0 } ]
12. (Added B) "ABD" [ { A, 5.0 } { B, 3.0 } { D, 1.0 } ]
13. (Added D) "ABD" [ { A, 5.0 } { B, 3.0 } { D, 2.0 } ]
14. (Added E) "ABE" [ { A, 5.0 } { B, 3.0 } { E, 1.0 } ]
15. (Added C) "ABC" [ { A, 5.0 } { B, 3.0 } { C, 1.0 } ]
16. (Added B) "ABC" [ { A, 5.0 } { B, 4.0 } { C, 1.0 } ]
17. (Added A) "ABC" [ { A, 6.0 } { B, 4.0 } { C, 1.0 } ]
Run Code Online (Sandbox Code Playgroud)
1. (Added A) "A" [ { A, 1.0 } ]
2. (Added B) "BA" [ { B, 1.0 } { A, 0.5 } ]
3. (Added C) "CBA" [ { C, 1.0 } { B, 0.5 } { A, 0.25 } ]
4. (Added B) "BCA" [ { B, 1.25 } { C, 0.5 } { A, 0.125 } ]
5. (Added A) "ABC" [ { A, 1.0625 } { B, 0.625 } { C, 0.25 } ]
6. (Added A) "ABC" [ { A, 1.5313 } { B, 0.3125 } { C, 0.125 } ]
7. (Added D) "DAB" [ { D, 1.0 } { A, 0.7656 } { B, 0.1563 } ]
8. (Added A) "ADB" [ { A, 1.3828 } { D, 0.5 } { B, 0.0781 } ]
9. (Added C) "CAD" [ { C, 1.0 } { A, 0.6914 } { D, 0.25 } ]
10. (Added D) "DCA" [ { D, 1.125 } { C, 0.5 } { A, 0.3457 } ]
11. (Added A) "ADC" [ { A, 1.1729 } { D, 0.5625 } { C, 0.25 } ]
12. (Added B) "BAD" [ { B, 1.0 } { A, 0.5864 } { D, 0.2813 } ]
13. (Added D) "DBA" [ { D, 1.1406 } { B, 0.5 } { A, 0.2932 } ]
14. (Added E) "EDB" [ { E, 1.0 } { D, 0.5703 } { B, 0.25 } ]
15. (Added C) "CED" [ { C, 1.0 } { E, 0.5 } { D, 0.2852 } ]
16. (Added B) "BCE" [ { B, 1.0 } { C, 0.5 } { E, 0.25 } ]
17. (Added A) "ABC" [ { A, 1.0 } { B, 0.5 } { C, 0.25 } ]
Run Code Online (Sandbox Code Playgroud)
1. (Added A) "A" [ { A, 1.0 } ]
2. (Added B) "BA" [ { B, 1.0 } { A, 0.9 } ]
3. (Added C) "CBA" [ { C, 1.0 } { B, 0.9 } { A, 0.81 } ]
4. (Added B) "BCA" [ { B, 1.81 } { C, 0.9 } { A, 0.729 } ]
5. (Added A) "ABC" [ { A, 1.6561 } { B, 1.629 } { C, 0.81 } ]
6. (Added A) "ABC" [ { A, 2.4905 } { B, 1.4661 } { C, 0.729 } ]
7. (Added D) "ABD" [ { A, 2.2414 } { B, 1.3195 } { D, 1.0 } ]
8. (Added A) "ABD" [ { A, 3.0173 } { B, 1.1875 } { D, 0.9 } ]
9. (Added C) "ABC" [ { A, 2.7156 } { B, 1.0688 } { C, 1.0 } ]
10. (Added D) "ADB" [ { A, 2.444 } { D, 1.0 } { B, 0.9619 } ]
11. (Added A) "ADB" [ { A, 3.1996 } { D, 0.9 } { B, 0.8657 } ]
12. (Added B) "ABD" [ { A, 2.8796 } { B, 1.7791 } { D, 0.81 } ]
13. (Added D) "ADB" [ { A, 2.5917 } { D, 1.729 } { B, 1.6012 } ]
14. (Added E) "ADE" [ { A, 2.3325 } { D, 1.5561 } { E, 1.0 } ]
15. (Added C) "ADC" [ { A, 2.0993 } { D, 1.4005 } { C, 1.0 } ]
16. (Added B) "ADB" [ { A, 1.8893 } { D, 1.2604 } { B, 1.0 } ]
17. (Added A) "ADB" [ { A, 2.7004 } { D, 1.1344 } { B, 0.9 } ]
Run Code Online (Sandbox Code Playgroud)
在第一个示例中 (USAGE_WEIGHT=1.0),当添加新项目时,现有项目的 Score 不会改变(这是因为我们在每一步都乘以 1.0)。这导致每次使用后将 Score 简单地增加 1.0,因此 Score 直接表示使用计数。请注意,项目始终按使用计数递减的顺序列出。
在第二个示例中 (USAGE_WEIGHT=0.5),每次添加一个项目时,现有项目的 Score 减半(这是因为我们在每一步都乘以 0.5)。这导致列表中的所有分数都低于最近添加的项目(获得 1.0 的分数)的属性,而且在步骤 N 添加的项目总是比添加的项目具有更高的分数。 N 之前的任何步骤,无论项目被重新添加多少次。这正是产生 LRU 策略的属性。
最后,当 (USAGE_WEIGHT=0.9) 我们可以看到这两个策略是如何混合的。第三个示例开始时看起来像 LRU(即“新近度”很重要)。但是随着某些项目的使用计数开始增加,它们开始产生影响并改变行为。这可以在第 7 步中看到,其中 LRU 列出了“DAB”,但由于 A 和 B 的使用率较高,示例 3 显示了“ABD”。然后示例 3 看起来更像 LFU 几个步骤,但在第 3 步发生了有趣的事情10. 这就是 LRFU 开始发光的地方。此时,A 已添加 4 次,而“B”、“C”和“D”均已添加两次。LRU 显示“DCA”,因为“D”和“C”是最近添加的,但它忽略了用户选择“A”的可能性是“D”的两倍的事实 或“C”。LFU 显示“ABD”是可以的,除了用户在选择“B”后选择了两次“D”,这表明“D”的使用正在“升温”,因此比“B”更有可能。LRFU 通过显示“ADB”得到正确的结果。当然,这都是有些主观的,其他读者可能不同意这是一个更好的选择。毕竟,我们试图根据之前的历史来预测用户未来的选择,所以没有完美的解决方案。但是使用 LRFU,您至少可以“调整” USAGE_WEIGHT 参数以在给定情况下找到 LRU 与 LFU 的正确平衡。正在“升温”,因此比“B”更有可能。LRFU 通过显示“ADB”得到正确的结果。当然,这都是有些主观的,其他读者可能不同意这是一个更好的选择。毕竟,我们试图根据之前的历史来预测用户未来的选择,所以没有完美的解决方案。但是使用 LRFU,您至少可以“调整” USAGE_WEIGHT 参数以在给定情况下找到 LRU 与 LFU 的正确平衡。正在“升温”,因此比“B”更有可能。LRFU 通过显示“ADB”得到正确的结果。当然,这都是有些主观的,其他读者可能不同意这是一个更好的选择。毕竟,我们试图根据之前的历史来预测用户未来的选择,所以没有完美的解决方案。但是使用 LRFU,您至少可以“调整” USAGE_WEIGHT 参数以在给定情况下找到 LRU 与 LFU 的正确平衡。
事实上,对于某些应用程序,随着程序的进行,动态更改 USAGE_WEIGHT甚至可能更可取,以改进基于历史数据的预测。这对于用户界面 MRU 列表可能没有意义,但更适用于预测高容量或高频事件。
仅供参考,在论文中讨论的LRFU算法中,Score被称为“CRF”。他们讨论的算法还存储了计算每个 Score 的“时间”(或步数)。通过存储分数和时间,可以只更新正在添加的项目的分数以及项目的一小部分——而不是整个列表。此外,排序顺序由堆和链表数据结构组合维护,因此该算法比我在此处描述的使用简单数组并在每次添加后重新计算和重新排序列表的算法高效得多。但是这个实现很简单,易于理解,并且适用于用户界面 MRU 列表。
这是 Java 中一个非常基本的 LRFU 列表的实现。在性能方面可以做很多事情来改进它,但它足以证明 LRFU 的结果:
A B C B A A D A C D A B D E C B A
Run Code Online (Sandbox Code Playgroud)