Журнал LinuxFormat - перейти на главную

LXF134:OLAP

Материал из Linuxformat
Перейти к: навигация, поиск
Мно­го­мер­ный ана­лиз Раз­бо­р ре­аль­ной про­грам­мы, вы­являю­щей за­ко­но­мер­но­сти

Содержание

OLAP: Ана­лиз данных и СПО

Вы­ло­жить бе­лый хлеб по со­сед­ст­ву с мо­ло­ком или кол­ба­сой? От­вет на этот во­прос по­мо­жет най­ти мно­го­мер­ный ана­лиз дан­ных, а Вя­че­слав Оло­ни­чев и Ан­д­рей Се­нов реа­ли­зу­ют его на Qt и C++.

Мно­го­мер­ный ана­лиз дан­ных или OLAP – это тех­но­ло­гия, ин­туи­тив­но по­нят­ная лю­бо­му, кто когда-ли­бо занимал­ся обоб­щением дан­ных по про­да­жам, вы­пуску про­дук­ции и то­му по­доб­ным. Эта тех­но­ло­гия востре­бо­ва­на прак­ти­че­ски по­все­ме­ст­но, на­чи­ная от ма­лень­кой мастер­ской или фер­мер­ско­го хо­зяй­ства и за­кан­чи­вая министер­ством или цен­траль­ным офи­сом транс­на­цио­наль­ной кор­по­ра­ции. Со­от­вет­ствен­но, и ре­шений для нее су­ще­ству­ет доста­точ­но мно­го. Здесь мож­но най­ти и нестан­дарт­ные рас­ши­рения SQL ти­па pivot в Access, под­клю­чае­мые мо­ду­ли для элек­трон­ных таб­лиц или той же «», пре­ве­ли­кое мно­же­ство про­грамм на Delphi с его Decision Cube или VBA, и, на­конец, ин­ст­ру­мен­та­рий для мно­го­мер­но­го ана­ли­за на сто­роне сер­ве­ра от Microsoft или Oracle. Наи­бо­лее удоб­ные и про­дви­ну­тые ре­шения в дан­ной об­ласти пред­на­зна­че­ны в пер­вую оче­редь для боль­ших кор­по­ра­ций и име­ют со­от­вет­ствую­щую стои­мость. Ис­поль­зо­вание же элек­трон­ных таб­лиц крайне тру­до­ем­ко и неэф­фек­тив­но. И кро­ме то­го, все ука­зан­ные ре­шения яв­ля­ют­ся про­прие­тар­ны­ми.

Опи­сы­вае­мая здесь про­грам­ма (вы най­де­те ее пол­ные ис­ход­ные тек­сты на LXFDVD) от­ли­ча­ет­ся от имею­щих­ся в пер­вую оче­редь тем, что ис­поль­зу­ет толь­ко сво­бод­ный код и са­ма яв­ля­ет­ся от­кры­той. Пред­на­зна­че­на она для мно­го­мер­но­го ана­ли­за дан­ных на сто­роне кли­ен­та, что и оп­ре­де­ля­ет об­ласть ее по­тен­ци­аль­но­го при­менения: неболь­шие и средние пред­при­ятия, для ко­то­рых SQL-за­прос, от­би­раю­щий дан­ные за пять-шесть лет, бу­дет вы­полнен на сер­ве­ре и пе­ре­дан по локаль­ной се­ти кли­ен­ту за при­ем­ле­мое вре­мя.

Про­грам­ма осно­ва­на на вы­полнении всех опе­ра­ций по мно­го­мер­но­му ана­ли­зу с ис­поль­зо­ванием ти­по­вых струк­тур дан­ных в ОЗУ, что долж­но обес­пе­чить доста­точ­но вы­со­кую эф­фек­тив­ность при уме­рен­ных за­тра­тах па­мя­ти. Наи­бо­лее пол­ная, удоб­ная и эф­фек­тив­ная реа­ли­за­ция ти­по­вых струк­тур дан­ных – это па­ра­мет­ри­зо­ван­ные кон­тейнеры на язы­ке С++, та­кие как стан­дарт­ная биб­лио­те­ка шаб­ло­нов STL и биб­лио­те­ка шаб­ло­нов Qt.

Как кон­тейнеры STL, так и кон­тейнеры Qt – это сво­бод­ное ПО. Разница ме­ж­ду ними на­столь­ко ма­ла, что фраг­мен­ты ис­ход­но­го ко­да, со­дер­жа­щие об­ра­щение к STL, мож­но пе­ре­де­лать в код, ра­бо­таю­щий с шаб­ло­на­ми Qt (и на­обо­рот), бу­к­валь­но за па­ру ми­нут.

В на­шем слу­чае ис­поль­зу­ют­ся шаб­ло­ны Qt, по сле­дую­щей при­чине: по­сколь­ку гра­фи­че­ский ин­тер­фейс про­грам­мы вы­полнен с ис­поль­зо­ванием биб­лио­те­ки Qt и доступ к ба­зе дан­ных про­из­во­дит­ся так­же сред­ства­ми Qt, то шаб­ло­ны Qt сле­ду­ет при­ме­нять не толь­ко из прин­ци­па еди­но­об­ра­зия, но и ради эф­фек­тив­но­сти, так как они тес­но взаи­мо­дей­ству­ют с основ­ной биб­лио­те­кой. Мо­дуль мно­го­мер­но­го ана­ли­за в про­грам­ме вы­де­лен в от­дель­ный класс, по­это­му его лег­ко адап­ти­ро­вать к дру­гой плат­фор­ме или биб­лио­те­ке. Кста­ти го­во­ря, пер­вая вер­сия про­грам­мы бы­ла вы­полнена на C++ Builder с ис­поль­зо­ванием STL, по­том ин­тер­фейс был пе­репи­сан на Qt3 в KDevelop, но STL со­хранена. А те­ку­щая реа­ли­за­ция вы­полнена на Qt4 в сре­де Qt Creator.

Для мно­го­мер­но­го ана­ли­за дан­ных на сто­роне кли­ен­та тре­бу­ет­ся ре­шить сле­дую­щие за­да­чи:

  1. Вы­бор струк­ту­ры дан­ных для со­хранения в ОЗУ ре­зуль­та­тов SQL-за­про­са и тре­бо­вания к тек­сту SQL-за­про­са.
  2. Фор­мат пред­став­ления вы­бран­ных осей ги­пер­ку­ба для фор­ми­ро­вания его про­ек­ции на плоскость.
  3. Фор­ми­ро­вание са­мой про­ек­ции.
  4. Фор­ми­ро­вание ито­гов.

При ре­шении дан­ных за­дач бу­дем по­сле­до­ва­тель­но при­дер­жи­вать­ся прин­ци­па по­все­ме­ст­но ис­поль­зо­вать па­ра­мет­ри­зо­ван­ные кон­тейнеры. Это стрем­ление не столь­ко к со­блю­дению еди­но­го сти­ля, сколь­ко к эф­фек­тив­но­сти, так как по­зво­ля­ет све­сти к миниму­му все if-else’ы и switch-case’ы и мак­си­маль­но ис­поль­зо­вать оп­ти­ми­зи­ро­ван­ный код па­ра­мет­ри­зо­ван­ных кон­тейнеров.

STL и OLAP

Пре­ж­де чем уг­луб­лять­ся в де­та­ли, разъ­яс­ним не­сколь­ко тер­ми­нов. Стан­дарт­ная биб­лио­те­ка шаб­ло­нов (Standard Template Library, STL) – на­бор со­гла­со­ван­ных обоб­щен­ных ал­го­рит­мов, кон­тей­не­ров, средств дос­ту­па к их со­дер­жи­мо­му и раз­лич­ных вспо­мо­га­тель­ных функ­ций (http://ru.wikipedia.org/wiki/Стан­дарт­ная_би­би­ло­те­ка_шаб­ло­нов).

OLAP (On Line Analytical Processing – ин­те­рак­тив­ная ана­ли­ти­че­ская об­ра­бот­ка дан­ных) – один из спо­со­бов пред­став­ле­ния и ана­ли­за дан­ных. При этом ин­фор­ма­ция пред­став­ля­ет­ся в ви­де мно­го­мер­но­го ку­ба с воз­мож­но­стью про­из­воль­но­го ма­ни­пу­ли­ро­ва­ния ею. Мно­го­мер­ные мо­де­ли рас­смат­ри­ва­ют дан­ные ли­бо как фак­ты с со­от­вет­ст­вую­щи­ми чис­лен­ны­ми па­ра­мет­ра­ми, ли­бо как тек­сто­вые из­ме­ре­ния, ко­то­рые ха­рак­те­ри­зу­ют эти фак­ты. (Ка­ши­рин И.Ю., Сем­чен­ков С.Ю. Ин­те­рак­тив­ная ана­ли­ти­че­ская об­ра­бот­ка дан­ных в со­вре­мен­ных OLAP сис­те­мах // Биз­нес-ин­фор­ма­ти­ка – 2009. – № 2. С.12–19. См. так­же http://ru.wikipedia.org/wiki/OLAP).

Вы­бор струк­ту­ры дан­ных

SQL-за­прос, по­став­ляю­щий дан­ные для мно­го­мер­но­го ана­ли­за, дол­жен иметь вид:

 SELECT ось0, ось1, ... осьN, val
 FROM ... INNER JOIN ... ON ...
 ...
 WHERE some_date BETWEEN :d1 AND :d2

где ось0, ось1, …, осьN – ре­зуль­та­ты из­ме­рений на осях ги­пер­ку­ба; val – ана­ли­зи­руе­мое зна­чение, со­дер­жа­щее­ся в ячей­ке ги­пер­ку­ба; :d1,:d2 – па­ра­мет­ры, за­даю­щие ин­тер­вал вре­мени, за ко­то­рый про­из­во­дит­ся ана­лиз.

За­прос воз­вра­ща­ет ре­ля­ци­он­ную таб­ли­цу, в ко­то­рой ат­ри­бу­ты, со­от­вет­ствую­щие ре­зуль­та­там из­ме­рений, мо­гут иметь са­мые раз­но­об­раз­ные ти­пы, а по­следний ат­ри­бут – val – прак­ти­че­ски все­гда яв­ля­ет­ся чис­лом с пла­ваю­щей точ­кой.

На­ша за­да­ча – реа­ли­зо­вать не толь­ко универ­саль­ный спо­соб со­хранения ре­зуль­та­тов в ОЗУ, но и обес­пе­чить воз­мож­ность по­строения как про­стых, так и со­став­ных клю­чей по ре­зуль­та­там из­ме­рений на осях ги­пер­ку­ба, ко­то­рые бу­дут необ­хо­ди­мы как для упо­ря­до­чи­вания, так и для по­иска ин­фор­ма­ции. По­это­му все ре­зуль­та­ты из­ме­рений сле­ду­ет при­вести к од­но­му ти­пу в са­мом на­ча­ле, еще до раз­ме­щения в ОЗУ. В ка­че­стве та­ко­го ти­па был вы­бран стро­ко­вый. Это объ­яс­ня­ет­ся тем, что боль­шин­ство ат­ри­бу­тов, со­от­вет­ствую­щих ре­зуль­та­там из­ме­рений на осях ги­пер­ку­ба, из­на­чаль­но име­ют стро­ко­вый тип – это на­име­но­вания то­ва­ров, пред­при­ятий, фа­ми­лии ра­ботников и то­му по­доб­ное. Все дру­гие ти­пы все­гда мож­но пре­об­ра­зо­вать к стро­ко­во­му. Что ка­са­ет­ся дат, то ес­ли их пре­об­ра­зо­вать в стро­ко­вый вид при по­мо­щи SQL-функ­ции cast в так на­зы­вае­мом ар­мей­ском фор­ма­те – «гггг.мм.дд» – то упо­ря­до­чи­вание строк в ал­фа­вит­ном по­ряд­ке бу­дет со­от­вет­ство­вать упо­ря­до­чи­ванию дат в по­ряд­ке воз­растания.

Та­ким об­ра­зом по­лу­ча­ем:

 typedef QVector<QString> s_keys;
 struct l_cube{
  s_keys dims;
 double r;
 };
 typedef QVector<l_cube> data_cube;

где s_keys – век­тор, в ко­то­рый по­ме­ща­ют­ся ре­зуль­та­ты из­ме­ре­ний; l_cube – струк­ту­ра для со­хра­не­ния од­ной стро­ки, воз­вра­щае­мой за­про­сом; data_cube – век­тор, для со­хра­не­ния в ОЗУ таб­ли­цы, воз­вра­щае­мой за­про­сом.

За­кач­ка ин­фор­ма­ции в па­мять, ес­ли смот­реть из­нут­ри, осу­ще­ст­в­ля­ет­ся сле­дую­щим об­ра­зом:

 class hyper_cube{
 protected:
     data_cube dc;
     l_cube line;
 ...
 public:
     void add(s_keys sk, double res){
       line.r = res;
       for(int i=0; i<N; i++) line.dims[i]=sk[i];
       dc.push_back(line);
     }
 ...
 }

А снару жи это выглядит так:

 hyper_cube HC;
 s_keys VK;
 QSqlQuery* psql;
 QDate d1,d2;
 psql­>bindValue(:d1”,QVariant(d1));
 psql­>bindValue(:d2”,QVariant(d2));
 if(psql­>exec()){
     while ( psql­>next() ) {
       for(int i=0; i<Nax; i++){
          VK[i] = psql­>value(i).toString() + '\n';
       }
       HC.add(VK, psql­>value(Nax).toDouble());
     }
 }

Здесь Nax – ко­ли­че­ство осей ги­пер­ку­ба. Ес­ли ис­поль­зо­вать од­но­на­прав­лен­ный за­прос, то при­ве­ден­ный код бу­дет доста­точ­но эф­фек­тив­ным.

Ба­за дан­ных

В ка­че­стве при­ме­ра ис­поль­зу­ем фраг­мент ба­зы дан­ных пред­приятия, занимаю­ще­го­ся мел­ко­оп­то­вой про­да­жей сто­ло­вой по­су­ды. Ба­за дан­ных реа­ли­зо­ва­на на SQLite (ка­та­лог primer в ис­ход­ных тек­стах про­грам­мы), а ее ER-диа­грам­ма пред­став­ле­на на рис. 1.

В ука­зан­ной ба­зе дан­ных име­ют­ся сле­дую­щие таб­ли­цы: kat – ка­те­го­рии то­ва­ра, tov – то­ва­ры, prod – про­дав­цы, grup – груп­пы по­ку­па­те­лей, pok – по­ку­па­те­ли; cen – це­ны на то­ва­ры, sales – про­да­жи.

Сле­дую­щий за­прос воз­вра­тит ги­пер­куб с дан­ны­ми о про­да­жах в руб­лях, со­дер­жа­щий 8 осей:

 SELECT k.nam, t.nam, pr.nam, g.nam, pk.nam,
 substr(cast(s.dat AS char(32)),1,4) AS ye,
 substr(cast(s.dat AS char(32)),1,7) AS mo,
 substr(cast(s.dat AS char(32)),1,10) AS da,
 s.kvo*c.cena AS sum
 FROM sales s INNER JOIN cen c ON c.id_tov=s.id_tov
 INNER JOIN tov t ON t.id=s.id_tov
 INNER JOIN prod pr ON pr.id=s.id_prod
 INNER JOIN pok pk ON pk.id=s.id_pok
 INNER JOIN grup g ON g.id=pk.id_gr
 INNER JOIN kat k ON k.id=t.id_kat
 WHERE c.dat=(SELECT max(dat) FROM cen WHERE
 cen.id_tov=s.id_tov AND dat<=s.dat)
 AND s.dat BETWEEN :d1 AND :d2


Ося­ми ги­пер­ку­ба в дан­ном слу­чае яв­ля­ют­ся: 0 – ка­те­го­рия, 1 – то­вар, 2 – про­да­вец, 3 – груп­па, 4 – по­ку­па­тель, 5 – год, 6 – ме­сяц, 7 – день. Фраг­мент ги­пер­ку­ба или таб­ли­цы, по­лу­чен­ной в ре­зуль­та­те вы­полнения дан­но­го за­про­са, мож­но ви­деть на рис. 2.


Фор­мат пред­став­ления

Сле­дую­щая про­бле­ма – вы­бор поль­зо­ва­те­лем осей ги­пер­ку­ба для фор­ми­ро­вания про­ек­ции и пе­ре­да­ча этой ин­фор­ма­ции ко­ду, ко­то­рый и бу­дет ее фор­ми­ро­вать. Для поль­зо­ва­те­ля есте­ствен­ным пред­став­лением осей ги­пер­ку­ба яв­ля­ют­ся два спи­ска, один для оси X про­ек­ции и вто­рой – для оси Y, до­пускаю­щих мно­же­ствен­ный вы­бор и пе­ре­ме­щение эле­мен­тов спи­ска от­но­си­тель­но друг дру­га. Эти два тре­бо­вания яв­ля­ют­ся обя­за­тель­ны­ми; про­грам­ма, не от­ве­чаю­щая им, не пред­став­ля­ет боль­шо­го ин­те­ре­са для прак­ти­ки.

Для при­ло­жения ис­поль­зо­вание на­име­но­ваний осей не со­всем удоб­но – пред­поч­ти­тельней ис­поль­зо­вать но­ме­ра: ведь дан­ные из­ме­рений хра­нят­ся в век­то­рах, и ин­декс в век­то­ре од­но­знач­но со­от­вет­ству­ет но­ме­ру оси. По­сколь­ку эле­мен­ты спи­ска мо­гут из­ме­нять вза­им­ное рас­по­ло­жение, то их те­ку­щие но­ме­ра уже не бу­дут со­от­вет­ство­вать но­ме­рам осей ги­пер­ку­ба. Для ре­шения дан­ной за­да­чи восполь­зу­ем­ся сло­ва­рем QMap<QString, int>, ко­то­рый по­зво­лит по вы­бран­ной поль­зо­ва­те­лем стро­ке с на­званием оси оп­ре­де­лить ее но­мер. До­пустим, что, как пред­став­ле­но на рис. 3, поль­зо­ва­тель ре­шил проана­ли­зи­ро­вать, как рас­пре­де­ля­ют­ся про­да­жи по го­дам и груп­пам по­ку­па­те­лей, с од­ной сто­ро­ны, и по ка­те­го­ри­ям то­ва­ров и про­дав­цам, с дру­гой. Для это­го ему при­шлось пе­ре­местить ось ги­пер­ку­ба «Год» вверх по спи­ску для про­ек­ции на ось X – ведь его ин­те­ре­су­ет рас­пре­де­ление про­даж по груп­пам по­ку­па­те­лей в пре­де­лах ка­ж­до­го го­да; ина­че у него по­лу­чи­лось бы рас­пре­де­ление про­даж по го­дам в пре­де­лах ка­ж­дой груп­пы. Оп­ре­де­лим сле­дую­щие пе­ре­мен­ные: myAxes – мас­сив QVector<QString>, со­дер­жа­щий на­име­но­вания осей ги­пер­ку­ба; lstX и lstY – спи­ски QListWidget, пред-на­зна­чен­ные для ото­бра­жения и вы­бо­ра осей ги­пер­ку­ба для осей X и Y про­ек­ции; m_axe – сло­варь QMap<QString,int>, по­зво­ляю­щий по­лу­чить по­ряд­ко­вые но­ме­ра по на­име­но­ваниям осей ги­пер­ку­ба; xJ, yI – мас­си­вы QVector<int>, со­дер­жа­щие но­ме­ра осей ги­пер­ку­ба, ото­бран­ные для ото­бра­жения на оси X и Y про­екции. В ре­зуль­та­те, в при­ме­ре на рис. 3, мас­сив xJ бу­дет со­дер­жать чис­ла 5 и 3, а мас­сив yI – чис­ла 0 и 2.

Фор­ми­ро­вание про­ек­ции

При фор­ми­ро­вании от­че­та бу­дем ис­поль­зо­вать сле­дую­щие пра­ви­ла: в ячей­ки про­ек­ции по­ме­ща­ют­ся толь­ко сум­мы, а ито­ги мо­гут со­дер­жать или сум­мы, или средние, или миниму­мы/мак­си­му­мы зна­чений, со­дер­жа­щих­ся в клет­ках про­ек­ции, в за­ви­си­мо­сти от вы­бо­ра поль­зо­ва­те­ля. Та­ким об­ра­зом, при вы­бо­ре мак­си­му­ма мы бу­дем по­лу­чать мак­си­маль­ную сум­му про­даж ка­те­го­рии то­ва­ров про­дав­цом ка­ж­дой груп­пе по­тре­би­те­лей за год, а не мак­си­маль­ную единич­ную про­да­жу.

Как про­ек­цию, так и ито­ги бу­дем по­ме­щать в дву­мер­ный мас­сив чи­сел с пла­ваю­щей точ­кой. Зна­чения из­ме­рения по осям «при­ри­со­вы­ва­ют­ся» к от­че­ту на по­следнем эта­пе, когда он ото­бра­жа­ет­ся в QTableWidget в окне диа­ло­га. Раз­мер дан­но­го дву­мер­но­го мас­си­ва из­вестен на мо­мент фор­ми­ро­вания, и по­это­му мож­но ис­поль­зо­вать обыч­ный ди­на­ми­че­ский мас­сив. Од­на­ко, про­сто из прин­ци­па еди­но­об­ра­зия, бу­дем ис­поль­зо­вать тип дан­ных Qvector<QVector<double>>.

На рис. 4 пред­став­лен фраг­мент про­ек­ции и ито­гов; за­го­лов­ки ко­ло­нок таб­ли­цы со­дер­жат ре­зуль­та­ты из­ме­рений по осям ги­пер­ку­ба – «Год» и «Груп­па пред­при­ятий», ко­то­рые от­ра­же­ны на ось X про­ек­ции. В клет­ках про­ек­ции пред­став­ле­ны сум­мы про­даж, рас­пре­де­лен­ные по го­дам и груп­пам пред­при­ятий, с од­ной сто­ро­ны, и ка­те­го­ри­ям то­ва­ров и про­дав­цам, с дру­гой сто­ро­ны. Ито­ги вни­зу пред­став­ля­ют сум­мы по го­дам и груп­пам пред­при­ятий. Строч­ка ниже со­дер­жит сум­мы про­даж по го­дам, а са­мая пра­вая ко­лон­ка – сум­мы про­даж по ка­те­го­ри­ям то­ва­ров.


За­да­ча за­клю­ча­ет­ся в том, что­бы вы­брать из таб­ли­цы с резуль­та­та­ми за­про­са уникаль­ные со­че­тания из­ме­рен­ных зна­чений на вы­бран­ных осях ги­пер­ку­ба и упо­ря­до­чить их в ал­фа­вит­ном по­ряд­ке. А за­тем сле­ду­ет ото­брать зна­чения ана­ли­зи­руе­мой ве­ли­чи­ны, в дан­ном слу­чае – сум­мы про­даж, из со­от­вет­ствую­щих стро­чек таб­ли­цы и про­сум­ми­ро­вать их в нуж­ных клет­ках про­ек­ции. Для ре­шения за­да­чи бу­дем ис­поль­зо­вать сло­варь QMap<QString, int>.

В дан­ном фраг­мен­те ко­да сканиру­ет­ся таб­ли­ца dc, со­дер­жа­щая ре­зуль­та­ты за­про­са к ба­зе дан­ных, как по­ка­за­но на рис. 2. Век­то­ры xJ и yI, как уже бы­ло ска­за­но, со­дер­жат но­ме­ра вы­бран­ных осей, {5, 3} и {0, 2} со­от­вет­ствен­но. В хо­де вы­полнения цик­ла стро­ка xs бу­дет по­сле­до­ва­тель­но принимать зна­чения «2007\nВосток\n», «2007\nЮг\n», «2007\nЗапад\n», «2007\nСеверг\n» и т. д., а стро­ка ys – «се­реб­ро\nКсюша\n», «се­реб­ро\nМаша\n», «се­реб­ро\nДаша\n», «се­реб­ро\nМаша\n» и т. д. И все эти стро­ки, яв­ляю­щие­ся пол­ны­ми со­став­ны­ми клю­ча­ми для осей X и Y про­ек­ции ги­пер­ку­ба, бу­дут по­ме­щать­ся как клю­чи в сло­ва­ри pX и pY. Зна­чения, по­ме­щае­мые в сло­ва­ри, на дан­ном эта­пе не важ­ны, и по­это­му для них ис­поль­зу­ет­ся -1. Сло­варь мо­жет со­дер­жать толь­ко непо­вто­ряю­щие­ся клю­чи, по­это­му по за­вер­шении цик­ла сло­ва­ри pX и pY бу­дут со­дер­жать все уникаль­ные зна­чения пол­ных со­став­ных клю­чей, со­дер­жа­щих­ся в таб­ли­це с дан­ны­ми.

 QString xs, ys;
 Qmap<QString,int> pX,pY;
 int Hx=xJ.size();
 int Hy=yI.size();
 for(int i=0; i<dc.size(); i++){
   xs=””; ys=””;
   for(int x=0; x<Hx; x++) xs=xs+dc[i].dims[xJ[x]];
   for(int y=0; y<Hy; y++) ys=ys+dc[i].dims[yI[y]];
   (*pY)[ys]=­1; (*pX)[xs]=­1;
 }

Сле­дую­щий фраг­мент ко­да пе­ре­би­ра­ет все клю­чи, со­дер­жа­щие­ся в сло­ва­рях pX и pY, при по­мо­щи ите­ра­то­ра, и при­сваи­ва­ет им мо­но­тон­но воз­растаю­щие це­ло­чис­лен­ные зна­чения. В ре­зуль­та­те на­ши сло­ва­ри со­дер­жат в ка­че­стве клю­чей на­име­но­вания строк и ко­ло­нок про­ек­ции, а в ка­че­стве зна­чений – но­ме­ра со­от­вет­ствую­щих строк и ко­ло­нок. Это как раз то, что и тре­бу­ет­ся для за­полнения яче­ек про­ек­ции.

 Qmap<QString,int>::iterator it;
 int m,n;
 m=0;
 for(it=(*pY).begin(); it!=(*pY).end(); it++)
  {it.value()=m++;}
 n=0;
 for(it=(*pX).begin(); it!=(*pX).end(); it++)
  {it.value()=n++;}

В ниже­сле­дую­щем цик­ле зна­чения пол­ных со­став­ных клю­чей опять восста­нав­ли­ва­ют­ся из зна­чений из­ме­рений, а по ним оп­ре­де­ля­ют­ся ко­ор­ди­на­ты ячей­ки про­ек­ции, к со­дер­жи­мо­му ко­то­рой до­бав­ля­ет­ся зна­чение ана­ли­зи­руе­мой ве­ли­чи­ны из оче­ред­ной строч­ки сканируе­мой таб­ли­цы с ре­зуль­та­та­ми за­про­са.

 int I,J;
 for(int i=0; i<dc.size(); i++){
  xs=””; ys=””;
  for(int x=0; x<Hx; x++) xs=xs+dc[i].dims[xJ[x]];
  for(int y=0; y<Hy; y++) ys=ys+dc[i].dims[yI[y]];
  I = (*pY)[ys];
  J = (*pX)[xs];
  dp[I][J]+=dc[i].r;
 }

Фор­ми­ро­ва­ние ито­гов

Оста­лась по­след­няя про­бле­ма – фор­ми­ро­вание ито­гов по пол­ным и частич­ным клю­чам. Как уже бы­ло ска­за­но вы­ше, на­до по­лу­чить сум­мы про­даж, рас­пре­де­лен­ные по груп­пам по­тре­би­те­лей в пре­де­лах го­да, а так­же сум­мы про­даж по го­дам. Так вот, «Год\nГруп­па_ком­паний\n» – это пол­ный ключ, имею­щий уникаль­ное зна­чение для ка­ж­дой ко­лон­ки про­ек­ции, а «Год\n» – это частич­ный ключ. Ес­ли по­стро­ить сло­ва­ри с частич­ны­ми клю­ча­ми, то мож­но оп­ре­де­лить как ко­ор­ди­на­ты яче­ек в ито­гах, «при­стро­ен­ных» к про­ек­ции спра­ва и снизу, так и рас­стояние ме­ж­ду эти­ми ко­ор­ди­на­та­ми – то есть ко­ли­че­ство яче­ек в ито­гах, оп­ре­де­лен­ных по пол­ным клю­чам, ко­то­рые необ­хо­ди­мо ис­поль­зо­вать для фор­ми­ро­вания ито­гов по частич­ным клю­чам. По­сколь­ку ко­ли­че­ство осей ги­пер­ку­ба, на­кла­ды­вае­мых на ось про­ек­ции, мо­жет быть про­из­воль­ным, то для фор­ми­ро­вания частич­ных клю­чей бу­дем ис­поль­зо­вать век­тор сло­ва­рей: QVector<QMap<QString,int>>.

Фор­ми­ро­вание ито­гов бу­дем осу­ще­ств­лять при по­мо­щи цик­лов, пе­ре­би­раю­щих ячей­ки мас­си­ва с про­ек­ци­ей ги­пер­ку­ба. А про­бле­му ви­да ито­гов, а имен­но: сум­ма, среднее, мак­си­мум или минимум – бу­дем ре­шать при по­мо­щи вир­ту­аль­ных функ­ций вспо­мо­га­тель­ных клас­сов.

 class Ttl{
   protected:
    double r;
   public:
    void setR(double R);
    virtual void updR(double E)=0;
    virtual double getR(int N)=0;
 };
 class Sum: public Ttl{
   public:
    virtual void updR(double E);
    virtual double getR(int N);
 };
 class Avg: public Sum{
   public:
    virtual double getR(int N);
 };
 class Max: public Sum{
   public:
    virtual void updR(double E);
 };
 class Min: public Sum{
   public:
    virtual void updR(double E);
 };
 void Ttl::setR(double R){r=R;}
 void Sum::updR(double E){r+=E;}
 void Max::updR(double E){if(E>r)r=E;}
 void Min::updR(double E){if(E<r)r=E;}
 double Sum::getR(int N){return r;}
 double Avg::getR(int N){return r/N;}

Име­на у клас­сов го­во­ря­щие, а пред­на­зна­чение функ­ций оче­вид­но и не тре­бу­ет разъ­яснений. Един­ствен­ное, что вы­зы­ва­ет во­прос, так это вир­ту­аль­ные функ­ции. Стои­ло ли бо­роть­ся за эф­фек­тив­ность ко­да, ис­поль­зуя па­ра­мет­ри­зо­ван­ные кон­тейнеры, что­бы по­том все, сэ­ко­ном­лен­ное бу­к­валь­но по мил­ли­се­кун­дам, так без­дар­но спустить в по­следний мо­мент? По­ясним: па­ра­мет­ри­зо­ван­ные кон­тейнеры ис­поль­зу­ют­ся внут­ри цик­лов, сканирую­щих таб­ли­цу с ис­ход­ны­ми дан­ны­ми или ги­пер­куб. Дан­ная таб­ли­ца в ре­аль­ных за­да­чах мо­жет иметь несколь­ко де­сят­ков, а то и со­тен ты­сяч строк. А для мас­си­ва, в ко­то­ром фор­ми­ру­ет­ся про­ек­ция и ито­ги, счет идет на де­сят­ки. Тео­ре­ти­че­ски, конеч­но, мож­но сфор­ми­ро­вать от­чет и на несколь­ко ты­сяч стро­чек и ко­ло­нок, толь­ко кто же его смот­реть бу­дет?

Фи­наль­ные штри­хи

Вы­вод про­ек­ции и ито­гов на фор­му и до­бав­ление к ним за­го­лов­ков строк и столб­цов – са­мо­стоя­тель­ная за­да­ча, и в дан­ной ста­тье не рас­смат­ри­ва­ет­ся, на­ря­ду с осталь­ны­ми во­про­са­ми про­ек­ти­ро­вания ин­тер­фей­са с поль­зо­ва­те­лем, а так­же фор­ма­та фай­ла, в ко­то­рый по­ме­ща­ет­ся от­чет, сгенери­ро­ван­ный про­грам­мой. Но в про­грам­ме, ко­то­рую (на­помним) вы най­де­те на LXFDVD, все это сде­ла­но. По­это­му она го­то­ва к ис­поль­зо­ванию «из ко­роб­ки».

Пред­ла­гае­мая ме­то­ди­ка мно­го­мер­но­го ана­ли­за дан­ных, конеч­но, не ог­раничи­ва­ет­ся толь­ко язы­ком C++ и биб­лио­те­кой Qt. Впол­не воз­мож­на реа­ли­за­ция на wxWidgets с ис­поль­зо­ванием STL, а па­ра­мет­ри­зи­ро­ван­ные шаб­ло­ны име­ют­ся и в язы­ках Java и C#.

В по­следний мо­мент, про­смат­ри­вая де­мон­ст­ра­ци­он­ную ба­зу дан­ных, мы за­ме­ти­ли, что де­вуш­ки-про­дав­щи­цы про­ра­бо­та­ли у нас по три го­да без от­пус­ков. Да... Луч­ше бы мы взло­ма­ли ба­зу дан­ных си­рот­ско­го при­юта.

Персональные инструменты
купить
подписаться
Яндекс.Метрика