So sánh, sắp xếp và tìm kiếm trong mảng
API MQL5 chứa một số hàm cho phép so sánh và sắp xếp mảng, cũng như tìm kiếm giá trị lớn nhất, nhỏ nhất hoặc bất kỳ giá trị cụ thể nào trong chúng.
int ArrayCompare(const void &array1[], const void &array2[], int start1 = 0, int start2 = 0, int count = WHOLE_ARRAY)
Hàm này trả về kết quả so sánh hai mảng của các loại tích hợp hoặc cấu trúc với các trường của loại tích hợp, ngoại trừ chuỗi. Không hỗ trợ mảng của các đối tượng lớp. Ngoài ra, bạn không thể so sánh mảng của các cấu trúc chứa mảng động, đối tượng lớp hoặc con trỏ.
Theo mặc định, việc so sánh được thực hiện cho toàn bộ mảng, nhưng nếu cần, bạn có thể chỉ định các phần của mảng bằng các tham số start1
(vị trí bắt đầu trong mảng đầu tiên), start2
(vị trí bắt đầu trong mảng thứ hai) và count
.
Mảng có thể là cố định hoặc động, cũng như đa chiều. Trong quá trình so sánh, các mảng đa chiều được biểu diễn dưới dạng mảng một chiều tương đương (ví dụ, đối với mảng hai chiều, các phần tử của hàng thứ hai theo sau hàng đầu tiên, các phần tử của hàng thứ ba theo sau hàng thứ hai, v.v.). Vì lý do này, các tham số start1
, start2
và count
cho mảng đa chiều được chỉ định thông qua số thứ tự phần tử, chứ không phải chỉ số dọc theo chiều đầu tiên.
Sử dụng các độ lệch start1
và start2
khác nhau, bạn có thể so sánh các phần khác nhau của cùng một mảng.
Mảng được so sánh từng phần tử cho đến khi tìm thấy sự khác biệt đầu tiên hoặc đến cuối một trong hai mảng. Mối quan hệ giữa hai phần tử (cùng vị trí trong cả hai mảng) phụ thuộc vào loại: đối với số, các toán tử >
, <
, ==
được sử dụng; đối với chuỗi, hàm StringCompare
được sử dụng. Các cấu trúc được so sánh theo từng byte, tương đương với việc thực thi đoạn mã sau cho mỗi cặp phần tử:
uchar bytes1[], bytes2[];
StructToCharArray(array1[i], bytes1);
StructToCharArray(array2[i], bytes2);
int cmp = ArrayCompare(bytes1, bytes2);
2
3
4
Dựa trên tỷ lệ của các phần tử khác biệt đầu tiên, kết quả so sánh tổng thể của các mảng array1
và array2
được xác định. Nếu không tìm thấy sự khác biệt nào và độ dài của các mảng bằng nhau, thì các mảng được coi là giống nhau. Nếu độ dài khác nhau, mảng dài hơn được coi là lớn hơn.
Hàm trả về -1 nếu array1
"nhỏ hơn" array2
, +1 nếu array1
"lớn hơn" array2
, và 0 nếu chúng "bằng nhau".
Trong trường hợp xảy ra lỗi, kết quả là -2.
Hãy xem một số ví dụ trong script ArrayCompare.mq5
.
Chúng ta sẽ tạo một cấu trúc đơn giản để điền dữ liệu vào các mảng cần so sánh.
struct Dummy
{
int x;
int y;
Dummy()
{
x = rand() / 10000;
y = rand() / 5000;
}
};
2
3
4
5
6
7
8
9
10
11
Các trường của lớp được điền bằng số ngẫu nhiên (mỗi lần chạy script, chúng ta sẽ nhận được các giá trị mới).
Trong hàm OnStart
, chúng ta mô tả một mảng nhỏ các cấu trúc và so sánh các phần tử liên tiếp với nhau (như việc di chuyển các đoạn lân cận của mảng với độ dài 1 phần tử).
#define LIMIT 10
void OnStart()
{
Dummy a1[LIMIT];
ArrayPrint(a1);
// so sánh cặp phần tử lân cận
// -1: [i] < [i + 1]
// +1: [i] > [i + 1]
for(int i = 0; i < LIMIT - 1; ++i)
{
PRT(ArrayCompare(a1, a1, i, i + 1, 1));
}
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Dưới đây là kết quả cho một trong các tùy chọn mảng (để tiện phân tích, cột với các dấu "lớn hơn" (+1) / "nhỏ hơn" (-1) được thêm trực tiếp bên phải nội dung mảng):
[x] [y] // kết quả
[0] 0 3 // -1
[1] 2 4 // +1
[2] 2 3 // +1
[3] 1 6 // +1
[4] 0 6 // -1
[5] 2 0 // +1
[6] 0 4 // -1
[7] 2 5 // +1
[8] 0 5 // -1
[9] 3 6
2
3
4
5
6
7
8
9
10
11
So sánh hai nửa của mảng với nhau cho kết quả -1:
// so sánh nửa đầu và nửa sau
PRT(ArrayCompare(a1, a1, 0, LIMIT / 2, LIMIT / 2)); // -1
2
Tiếp theo, chúng ta sẽ so sánh các mảng chuỗi với dữ liệu được định sẵn.
string s[] = {"abc", "456", "$"};
string s0[][3] = {{"abc", "456", "$"}};
string s1[][3] = {{"abc", "456", ""}};
string s2[][3] = {{"abc", "456"}}; // phần tử cuối bị bỏ qua: nó là null
string s3[][2] = {{"abc", "456"}};
string s4[][2] = {{"aBc", "456"}};
PRT(ArrayCompare(s0, s)); // s0 == s, mảng 1D và 2D chứa cùng dữ liệu
PRT(ArrayCompare(s0, s1)); // s0 > s1 vì "$" > ""
PRT(ArrayCompare(s1, s2)); // s1 > s2 vì "" > null
PRT(ArrayCompare(s2, s3)); // s2 > s3 do độ dài khác nhau: [3] > [2]
PRT(ArrayCompare(s3, s4)); // s3 < s4 vì "abc" < "aBc"
2
3
4
5
6
7
8
9
10
11
12
Cuối cùng, kiểm tra tỷ lệ của các đoạn mảng:
PRT(ArrayCompare(s0, s1, 1, 1, 1)); // phần tử thứ hai (chỉ số 1) bằng nhau
PRT(ArrayCompare(s1, s2, 0, 0, 2)); // hai phần tử đầu tiên bằng nhau
2
bool ArraySort(void &array[])
Hàm này sắp xếp một mảng số (có thể là mảng đa chiều) theo chiều đầu tiên. Thứ tự sắp xếp là tăng dần. Để sắp xếp mảng theo thứ tự giảm dần, áp dụng hàm ArrayReverse
cho mảng kết quả hoặc xử lý nó theo thứ tự ngược lại.
Hàm không hỗ trợ mảng chuỗi, cấu trúc hoặc lớp.
Hàm trả về true
nếu thành công hoặc false
nếu có lỗi.
Nếu thuộc tính "chuỗi thời gian" được đặt cho mảng, các phần tử trong đó được lập chỉ mục theo thứ tự ngược lại (xem chi tiết trong phần Hướng lập chỉ mục mảng như trong chuỗi thời gian), và điều này có hiệu ứng "đảo ngược bên ngoài" đối với thứ tự sắp xếp: khi bạn xử lý mảng này trực tiếp, bạn sẽ nhận được các giá trị giảm dần. Ở cấp độ vật lý, mảng luôn được sắp xếp theo thứ tự tăng dần và được lưu trữ như vậy.
Trong script ArraySort.mq5
, một mảng hai chiều 10 x 3 được tạo và sắp xếp bằng ArraySort
:
#define LIMIT 10
#define SUBLIMIT 3
void OnStart()
{
// tạo dữ liệu ngẫu nhiên
int array[][SUBLIMIT];
ArrayResize(array, LIMIT);
for(int i = 0; i < LIMIT; ++i)
{
for(int j = 0; j < SUBLIMIT; ++j)
{
array[i][j] = rand();
}
}
Print("Before sort");
ArrayPrint(array); // mảng nguồn
PRTS(ArraySort(array));
Print("After sort");
ArrayPrint(array); // mảng đã sắp xếp
...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Theo nhật ký, cột đầu tiên được sắp xếp theo thứ tự tăng dần (các số cụ thể sẽ thay đổi do tạo ngẫu nhiên):
Before sort
[,0] [,1] [,2]
[0,] 8955 2836 20011
[1,] 2860 6153 25032
[2,] 16314 4036 20406
[3,] 30366 10462 19364
[4,] 27506 5527 21671
[5,] 4207 7649 28701
[6,] 4838 638 32392
[7,] 29158 18824 13536
[8,] 17869 23835 12323
[9,] 18079 1310 29114
ArraySort(array)=true / status:0
After sort
[,0] [,1] [,2]
[0,] 2860 6153 25032
[1,] 4207 7649 28701
[2,] 4838 638 32392
[3,] 8955 2836 20011
[4,] 16314 4036 20406
[5,] 17869 23835 12323
[6,] 18079 1310 29114
[7,] 27506 5527 21671
[8,] 29158 18824 13536
[9,] 30366 10462 19364
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Các giá trị trong các cột tiếp theo di chuyển đồng bộ với các giá trị "dẫn đầu" trong cột đầu tiên. Nói cách khác, toàn bộ các hàng được hoán đổi, mặc dù chỉ cột đầu tiên là tiêu chí sắp xếp.
Nhưng nếu bạn muốn sắp xếp một mảng hai chiều theo cột khác ngoài cột đầu tiên thì sao? Bạn có thể viết một thuật toán đặc biệt cho việc đó. Một trong các tùy chọn được bao gồm trong tệp ArraySort.mq5
dưới dạng hàm mẫu:
template<typename T>
...(bị cắt bớt 78108 ký tự)...
}
2
3
Sử dụng nó, để sắp xếp một mảng cấu trúc theo một trường nhất định, chỉ cần viết một lệnh. Ví dụ:
MqlRates rates[];
CopyRates(_Symbol, _Period, 0, 10000, rates);
SORT_STRUCT(MqlRates, rates, high);
2
3
Ở đây, mảng rates
loại MqlRates
được sắp xếp theo giá high
.
int ArrayBsearch(const type &array[], type value)
Hàm này tìm kiếm một giá trị nhất định trong mảng số. Hỗ trợ mảng của tất cả các loại số tích hợp. Mảng phải được sắp xếp theo thứ tự tăng dần theo chiều đầu tiên, nếu không kết quả sẽ không chính xác.
Hàm trả về chỉ số của phần tử khớp (nếu có nhiều phần tử, thì chỉ số của phần tử đầu tiên trong số đó) hoặc chỉ số của phần tử gần nhất về giá trị (nếu không có khớp chính xác), tức là có thể là phần tử có giá trị lớn hơn hoặc nhỏ hơn giá trị đang tìm kiếm. Nếu giá trị mong muốn nhỏ hơn phần tử đầu tiên (tối thiểu), thì trả về 0. Nếu giá trị tìm kiếm lớn hơn phần tử cuối cùng (tối đa), thì trả về chỉ số của nó.
Chỉ số phụ thuộc vào hướng đánh số các phần tử trong mảng: trực tiếp (từ đầu đến cuối) hoặc ngược lại (từ cuối đến đầu). Nó có thể được nhận biết và thay đổi bằng các hàm được mô tả trong phần Hướng lập chỉ mục mảng như trong chuỗi thời gian.
Nếu xảy ra lỗi, trả về -1.
Đối với mảng đa chiều, tìm kiếm bị giới hạn ở chiều đầu tiên.
Trong script ArraySearch.mq5
, bạn có thể tìm thấy các ví dụ sử dụng hàm ArrayBsearch
.
void OnStart()
{
int array[] = {1, 5, 11, 17, 23, 23, 37};
// indexes 0 1 2 3 4 5 6
int data[][2] = {{1, 3}, {3, 2}, {5, 10}, {14, 10}, {21, 8}};
// indexes 0 1 2 3 4
int empty[];
...
}
2
3
4
5
6
7
8
9
Đối với ba mảng được định sẵn (một trong số đó rỗng), các câu lệnh sau được thực thi:
PRTS(ArrayBsearch(array, -1)); // 0
PRTS(ArrayBsearch(array, 11)); // 2
PRTS(ArrayBsearch(array, 12)); // 2
PRTS(ArrayBsearch(array, 15)); // 3
PRTS(ArrayBsearch(array, 23)); // 4
PRTS(ArrayBsearch(array, 50)); // 6
PRTS(ArrayBsearch(data, 7)); // 2
PRTS(ArrayBsearch(data, 9)); // 2
PRTS(ArrayBsearch(data, 10)); // 3
PRTS(ArrayBsearch(data, 11)); // 3
PRTS(ArrayBsearch(data, 14)); // 3
PRTS(ArrayBsearch(empty, 0)); // -1, 5053, ERR_ZEROSIZE_ARRAY
...
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Tiếp theo, trong hàm trợ giúp populateSortedArray
, mảng numbers
được điền bằng các giá trị ngẫu nhiên và mảng luôn được duy trì ở trạng thái sắp xếp bằng ArrayBsearch
.
void populateSortedArray(const int limit)
{
double numbers[]; // mảng để điền
double element[1]; // giá trị mới để chèn
ArrayResize(numbers, 0, limit); // cấp phát bộ nhớ trước
for(int i = 0; i < limit; ++i)
{
// tạo số ngẫu nhiên
element[0] = NormalizeDouble(rand() * 1.0 / 32767, 3);
// tìm vị trí của nó trong mảng
int cursor = ArrayBsearch(numbers, element[0]);
if(cursor == -1)
{
if(_LastError == 5053) // mảng rỗng
{
ArrayInsert(numbers, element, 0);
}
else break; // lỗi
}
else
if(numbers[cursor] > element[0]) // chèn tại vị trí 'cursor'
{
ArrayInsert(numbers, element, cursor);
}
else // (numbers[cursor] <= value) // chèn sau 'cursor'
{
ArrayInsert(numbers, element, cursor + 1);
}
}
ArrayPrint(numbers, 3);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Mỗi giá trị mới được đưa vào mảng một phần tử element
trước, vì cách này dễ chèn nó vào mảng kết quả numbers
bằng hàm ArrayInsert
hơn.
ArrayBsearch
cho phép xác định nơi giá trị mới nên được chèn.
Kết quả của hàm được hiển thị trong nhật ký:
void OnStart()
{
...
populateSortedArray(80);
/*
ví dụ (sẽ khác nhau mỗi lần chạy do ngẫu nhiên)
[ 0] 0.050 0.065 0.071 0.106 0.119 0.131 0.145 0.148 0.154 0.159
0.184 0.185 0.200 0.204 0.213 0.216 0.220 0.224 0.236 0.238
[20] 0.244 0.259 0.267 0.274 0.282 0.293 0.313 0.334 0.346 0.366
0.386 0.431 0.449 0.461 0.465 0.468 0.520 0.533 0.536 0.541
[40] 0.597 0.600 0.607 0.612 0.613 0.617 0.621 0.623 0.631 0.634
0.646 0.658 0.662 0.664 0.670 0.670 0.675 0.686 0.693 0.694
[60] 0.725 0.739 0.759 0.762 0.768 0.783 0.791 0.791 0.791 0.799
0.838 0.850 0.854 0.874 0.897 0.912 0.920 0.934 0.944 0.992
*/
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int ArrayMaximum(const type &array[], int start = 0, int count = WHOLE_ARRAY)
int ArrayMinimum(const type &array[], int start = 0, int count = WHOLE_ARRAY)
Các hàm ArrayMaximum
và ArrayMinimum
tìm kiếm trong mảng số các phần tử có giá trị lớn nhất và nhỏ nhất, tương ứng. Phạm vi chỉ số để tìm kiếm được đặt bởi các tham số start
và count
: với giá trị mặc định, toàn bộ mảng được tìm kiếm.
Hàm trả về vị trí của phần tử được tìm thấy.
Nếu thuộc tính "chuỗi" ("timeseries") được đặt cho mảng, việc lập chỉ mục các phần tử trong đó được thực hiện theo thứ tự ngược lại, và điều này ảnh hưởng đến kết quả của hàm này (xem ví dụ). Các hàm tích hợp để làm việc với thuộc tính "chuỗi" được thảo luận trong phần tiếp theo. Chi tiết hơn về mảng "chuỗi" sẽ được thảo luận trong các chương về chuỗi thời gian và chỉ báo.
Trong mảng đa chiều, tìm kiếm được thực hiện trên chiều đầu tiên.
Nếu có nhiều phần tử giống nhau trong mảng với giá trị tối đa hoặc tối thiểu, hàm sẽ trả về chỉ số của phần tử đầu tiên trong số đó.
Ví dụ về cách sử dụng các hàm được đưa ra trong tệp ArrayMaxMin.mq5
.
#define LIMIT 10
void OnStart()
{
// tạo dữ liệu ngẫu nhiên
int array[];
ArrayResize(array, LIMIT);
for(int i = 0; i < LIMIT; ++i)
{
array[i] = rand();
}
ArrayPrint(array);
// theo mặc định, mảng mới không phải là chuỗi thời gian
PRTS(ArrayMaximum(array));
PRTS(ArrayMinimum(array));
// bật thuộc tính "chuỗi"
PRTS(ArraySetAsSeries(array, true));
PRTS(ArrayMaximum(array));
PRTS(ArrayMinimum(array));
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Script sẽ ghi lại một tập hợp chuỗi tương tự như sau (do tạo dữ liệu ngẫu nhiên, mỗi lần chạy sẽ khác nhau):
22242 5909 21570 5850 18026 24740 10852 2631 24549 14635
ArrayMaximum(array)=5 / status:0
ArrayMinimum(array)=7 / status:0
ArraySetAsSeries(array,true)=true / status:0
ArrayMaximum(array)=4 / status:0
ArrayMinimum(array)=2 / status:0
2
3
4
5
6