文章 "MQL5秘笈之:采用关联数组或字典实现快速数据访问"

 

新文章 MQL5秘笈之:采用关联数组或字典实现快速数据访问已发布:

本文介绍一种能够通过key来访问元素的特殊算法。任何基本数据类型都可以被当作key。例如它可以是一个字符串或一个整型值。这样的数据容器通常被称为字典或这关联数组。这为解决问题提供了便捷。

本文介绍一个便捷的存储信息的类,称为关联属于或者字典。这个类允许通过密钥来访问其中的信息。

关联数组是一个常规数组。但是它使用某些具备唯一性的key来取代索引,如ENUM_TIMEFRAMES枚举值或者一些文本。用什么作为key并不关键。只要key是唯一的。这种数据存储算法能显著的简化许多编程工作。

例如,一个能够打印错误信息的函数,就能够如下实现:

//+------------------------------------------------------------------+
//| 在终端中显示错误描述                                                |
//| 如果错误代码未知则显示“未知错误”                                     |
//+------------------------------------------------------------------+
void PrintError(int error)
 {
   Dictionary dict;
   CStringNode* node = dict.GetObjectByKey(error);
   if(node != NULL)
      printf(node.Value());
   else
      printf("Unknown error");
 }

我们后面会仔细分析这段代码。

在介绍关联数组内部逻辑之前,我们来看看数据存储的两个主要方式,即数组和列表。我们的字典将基于这两个数据类型,因此我们要对他们的特性有深入的理解。第一张重点介绍数据类型。第二章介绍关联数组及其方法。

作者:Vasiliy Sokolov

 

很酷的工作,为作者点赞!这是 MQ 很久以前就应该包含在 \MQL5\Include\Arrays 中的东西,我希望它能在下一个版本中包含在库中。顺便说一下,在 MQL4 中一切运行正常,这是第一次测试的测量结果。我的理解是,由于缺乏完整的指针,不可能包含简单的数据类型,而不是 *CObject?或者有什么方法可以解决这个问题?

2015.03.23 13:25:54.617 TestDict EURUSD,M1: 1000000 elements. Add: 1373; Get: 218
2015.03.23 13:25:52.644 TestDict EURUSD,M1: 950000 elements. Add: 1216; Get: 219
2015.03.23 13:25:50.833 TestDict EURUSD,M1: 900000 elements. Add: 1217; Get: 218
2015.03.23 13:25:49.069 TestDict EURUSD,M1: 850000 elements. Add: 1154; Get: 187
2015.03.23 13:25:47.424 TestDict EURUSD,M1: 800000 elements. Add: 1092; Get: 187
2015.03.23 13:25:45.844 TestDict EURUSD,M1: 750000 elements. Add: 1061; Get: 171
2015.03.23 13:25:44.320 TestDict EURUSD,M1: 700000 elements. Add: 1107; Get: 156
2015.03.23 13:25:42.761 TestDict EURUSD,M1: 650000 elements. Add: 1045; Get: 140
2015.03.23 13:25:41.304 TestDict EURUSD,M1: 600000 elements. Add: 1014; Get: 156
2015.03.23 13:25:39.915 TestDict EURUSD,M1: 550000 elements. Add: 920; Get: 125
2015.03.23 13:25:38.665 TestDict EURUSD,M1: 500000 elements. Add: 702; Get: 109
2015.03.23 13:25:37.693 TestDict EURUSD,M1: 450000 elements. Add: 593; Get: 93
2015.03.23 13:25:36.836 TestDict EURUSD,M1: 400000 elements. Add: 577; Get: 78
2015.03.23 13:25:36.025 TestDict EURUSD,M1: 350000 elements. Add: 561; Get: 78
2015.03.23 13:25:35.247 TestDict EURUSD,M1: 300000 elements. Add: 515; Get: 78
2015.03.23 13:25:34.557 TestDict EURUSD,M1: 250000 elements. Add: 343; Get: 63
2015.03.23 13:25:34.063 TestDict EURUSD,M1: 200000 elements. Add: 312; Get: 47
2015.03.23 13:25:33.632 TestDict EURUSD,M1: 150000 elements. Add: 281; Get: 31
2015.03.23 13:25:33.264 TestDict EURUSD,M1: 100000 elements. Add: 171; Get: 16
2015.03.23 13:25:33.038 TestDict EURUSD,M1: 50000 elements. Add: 47; Get: 16
 
VDev:

干得漂亮,为作者点赞!MQ 早就应该在 MQL5/Include/ Arrays 中包含这个功能,我希望它能在下一个版本的库中包含。顺便说一下,在 MQL4 中也一切正常,这是第一次测试的测量结果。我的理解是,由于缺乏完整的指针,不可能包含简单的数据类型,而不是 *CObject?或者有什么方法可以让它工作?

可以的。有了装箱/解箱机制和模板的帮助。我们的想法是将每个基本类型打包到一个 KeyValuePairBase 容器中。通过 GetObjectByKey 类型的内部函数进行解包并返回相应的类型:

template<typename Type, typename T>
Type GetObjectByKey(T key);

需要强调的是,使用基础类型不会带来任何性能优势,但会更加方便。

 

现在,我试图在模板的基础上创建一个 CDictionaryBase,存储基本 MQL 类型之一,而不是CObject。不幸的是,这行不通,因为函数不允许返回模板类型。真遗憾:

//+------------------------------------------------------------------+
//|TestDictBase.mq5
//|版权所有 2015 年,瓦西里-索科洛夫。|
//|http://www.mql5.com | |
//+------------------------------------------------------------------+
#property copyright "Copyright 2015, Vasiliy Sokolov."
#property link      "http://www.mql5.com"
#property version   "1.00"
#include <Dictionary.mqh>
#include <DictionaryBase.mqh>
//+------------------------------------------------------------------+
//| 脚本程序启动功能|
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   CDictionaryBase base;
   base.AddValue("Pi", 3.14159);
   double pi = (double)base.GetValueByKey("Pi");
   printf(DoubleToString(pi, 5));
   //base.AddObject(
  }
//+------------------------------------------------------------------+
could not deduce template argument #1    TestDictBase.mq5        19      29
could not deduce template argument #0    DictionaryBase.mqh      404     25
possible loss of data due to type conversion    DictionaryBase.mqh      133     10
possible loss of data due to type conversion    DictionaryBase.mqh      135     10
possible loss of data due to type conversion    DictionaryBase.mqh      137     10
...

//+------------------------------------------------------------------+
//| 按键返回对象。|
//+------------------------------------------------------------------+
template<typename T, typename C>
C CDictionaryBase::GetValueByKey(T key)
  {
   if(!ContainsKey(key))
      return NULL;
   return m_current_kvp.GetValue();
  }

太遗憾了。

因此,我们必须为每个基础类型创建一个基础容器,或者直接创建基础类型的容器:CDouble、CLong、CInt 等。

 
C-4:

现在,我试图在模板的基础上创建一个 CDictionaryBase,存储基本 MQL 类型之一,而不是 CObject。不幸的是,我失败了,因为函数不允许返回模板类型。

它们可以。但返回值的类型无法自动推断,实际上是由编译器编写的。

您可以使用伪参数形式的小拐杖。

template<typename T, typename C>
C CDictionaryBase::GetValueByKey(T key, C)
{
   if(!ContainsKey(key))
      return NULL;
   return m_current_kvp.GetValue();
}
 
TheXpert:

确实如此。但返回值的类型无法自动推断,而这正是编译器实际写入的内容。

你可以使用伪参数形式的小拐杖。

真正的拐杖在哪里?
 
C-4:
真正的拐杖在哪里?
增加了第二个参数
 
TheXpert:
添加了第二个参数
我现在看到了。我明天再检查。
 
您是否尝试过比较性能?与二进制搜索 相比,排序字符串数组的优势从多大的数据量开始?
 
Integer:
你有没有试着比较过性能?与二进制搜索相比,排序字符串数组的优势从多大的数据量开始?

我没有做过精确的测试,但根据我的观察,从数万个元素开始,速度优势就开始显现了。也就是说,在处理 100-10 000 个元素的日常任务时,无法获得性能提升。

还有一点也很重要,那就是使用容器的便利性。你不需要编写额外的方法来搜索元素。使用字典执行许多日常任务都会变得容易得多。你不需要创建一个元素来搜索索引,然后通过相应的索引检索所需的元素,等等等等。

s.s. 虽然我认为性能应该用插入元素和搜索元素的总时间来衡量。如果在CArrayObj 中搜索排序项是一个相当快的操作,那么插入就是一个真正的麻烦。由于快速搜索需要有序性,因此我们无法摆脱可能的插入,这将大大降低性能。

 

非常有趣,很明显,字典是非常有用且易于使用的数据整理工具。


感谢您的分享。