Diskuze: Parser, psy a kočky... HELP
V předchozím kvízu, Test znalostí C# .NET online, jsme si ověřili nabyté zkušenosti z kurzu.


Tak už po 3 dnech dumání a myslení si, že je moje logika uplne v haji vím čím to je..** Je to tím, že mi pos009 = fr1.Position zobrazuje jen násobky 1024**, takže pozice nefungují, neví někdo jak to vyřešit?
pozice hned prvního řádku je 1024 a potom po 10ti kočkách je už 2048 a
tak dále... Jestli mi někdo pomůže vyřešit tohle tak si s tím parserem
už poradím Když jsem
vracel pozici, tak to samozřejmě vrátilo na úplnej nesmysl, kterej byl
někde v intervalu 1024.
To by bylo abych neuměl vyhledat kočku, potom příslušnýho psa, vrátit
pozici a znovu
Díky moc!!!
Ano
Pes je vždy po kočce, která je jen jedna
čísla jsou vždy jiná a jdou vzestupně
např.
kocka 1
kocka 2
pes 1
pes 1
pes 2
pes 2
pes 1
kocka 3
pes 3
pes 3
pes 3
kocka 4
pes 3
kocka 5
pes 4
pes 4
pes 5
pes 4
pes 5
apod. Vždy vyhledat kočku x a k ní přiřadit prvniho psa x, vrátit pozici za kocku x a hledat dalsi kocku y a hned prvního psa y a tak stále dokola, s tím, že se může stát, že v jednom z 100 případů bude úplně chybět kočce z pes z a tím je nutno počítat.
Jak říkám, myslím, že bych si už poradil s tím parserem, ale nechápu,
proč pozice vrací takový nesmysly jak jsem popsal v 4. příspěvku
I tak díky moc!
Ahoj nevim jestli to tak presne chces, ale pokud rikas
Takže potřebuji prostě, aby to vzalo všech 277 koček a k nim přiřadilo prvního psa se stejným číslem
Tak presne tohle mi vydal tenhle kod, je to tak nak uplne jiny nez to tvoje,
ale mozna pomuze
PS: ano je tam par veci co by se dali udelat lip a rad si poslechnu rady dekuji
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DogCatParser
{
class Program
{
static void Main(string[] args)
{
//nacteme soubor
string fileName = "inputFile.txt";
List<string> lines = new List<string>();
using (StreamReader sr = File.OpenText(fileName))
{
while (!sr.EndOfStream)
{
lines.Add(sr.ReadLine());
}
}
//pro lehkou praci s tema ID si pripravime dictionary s ID a vlastnim objektem Animal
//list jsem take premyslel, ale literace listu s Xtisic prvku je pomalejsi nez dictionart try get value
Dictionary<int, Animal> cats = new Dictionary<int, Animal>();
Dictionary<int, Animal> dogs = new Dictionary<int, Animal>();
//parsovani
foreach (var item in lines)
{
string[] nameAndId = item.Split(' ');
if (nameAndId.Length == 2)
{
//pripravime si jmeno a id; kocka 5, pes 2 atd
string name = nameAndId[0];
int ID;
int.TryParse(nameAndId[1], out ID);
Animal animal;
if (name == "kocka")
{
//pokud kocky NEOBSAHUJI toto ID tak pridame kocku s ID
if (!cats.TryGetValue(ID, out animal))
{
cats.Add(ID, new Animal(name, ID));
}
}
else if (name == "pes")
{
//pokud pejskove NEOBSAHUJI toto ID tak pridame pejska s ID
if (!dogs.TryGetValue(ID, out animal))
{
dogs.Add(ID, new Animal(name, ID));
}
}
//protoze jedeme postupne tak se zbavime duplikatu jako je pes 1, pes 1 atd.. vzdy ten PRVNI a to stejne u kocky
}
}
foreach (var item in cats)
{
int ID = item.Key;
Animal animal;
//zjistime zda existuje pejsek co ma stejne ID jako kocka, pokud ano pridame to kocce jako subanimal :)
if (dogs.TryGetValue(ID, out animal))
{
item.Value.SubAnimal = animal;
}
}
//test probehl na 200 tisic zaznamu absolutne bez problemu, pokud bys mel mozna slozitejsi objekty nebo miliony zaznamu mozna by byl rychlejsi Parallel.For
//priklad vysledku timhle parsovanim
//[3]
// Key 4
// Value
// ID 4
// Name kocka
// SubAnimal
// ID 4
// Name pes
// SubAnimal null
}
}
class Animal
{
public string Name { get; set; }
//ID by nemuselo byt protoze to mame ve slovniku co bude mit take stejny ID, mozna zbytecne duplikuju 2 inty
//ale je to pro pripad ze bys to chtel mit zachovany, at uz z jakehokoliv duvodu
public int ID { get; set; }
public Animal SubAnimal { get; set; }
public Animal(string Name, int ID)
{
this.Name = Name;
this.ID = ID;
}
}
}
Dobře, díky moc, problém je, že já vůbec neovládám kolekce apod. mám teď hodně učení takže se k tomu asi nedostanu.
Vůbec netuším odkud mám volat ten výslednej objekt. Jako kočky a
psy.
Ono je to ještě trošku složitější, ty kočky a psi jsem tam dal jen
kvůli přehlednosti, výsledek po projetí prvním parserem vypadá nějak
takto...
221 -> MSG_0x0009-T 2054131054 00A4DE5D ::: 10d ::: 13s
221 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'C' 287
221 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'C' 40 '' 'E' 80, 370,
20, 1
222 -> MSG_0x0009-T 2054131054 00A4DE5D ::: 13s ::: 7h
222 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'C' 15 '' 'E' 60, 634,
30, 1
222 -> MSG_0x0007-T 2054131054 00A4DE5D 'w' 0 'W' 65536
223 -> MSG_0x0009-T 2142451585 00A12320 ::: 5h ::: 2c
224 -> MSG_0x0009-T 2054131054 00A4DE5D ::: 4d ::: 8c
224 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'C' 15 '' 'E' 60, 370,
30, 1
224 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'C' 306
224 -> MSG_0x0007-T 2142451585 00A12320 'w' 0 'W' 65536
225 -> MSG_0x0009-T 2142451585 00A12320 ::: 12d ::: 10h
226 -> MSG_0x0009-T 2054131054 00A4DE5D ::: 2s ::: 6h
226 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'C' 15 '' 'E' 60, 638,
30, 1
226 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'C' 30 '' 'E' 90, 410,
30, 1
226 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'c' 0 '' 'B' 30, 340,
30, 1
226 -> MSG_0x0007-T 2054131054 00A4DE5D 'w' 0 'W' 65536
227 -> MSG_0x0009-T 2054131054 00A4DE5D ::: 11h ::: 11s
227 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'C' 90 '' 'E' 180, 340,
30, 1
227 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'c' 0 '' 'E' 60, 664,
30, 1
227 -> MSG_0x0007-T 2054131054 00A4DE5D 'w' 0 'W' 65536
228 -> MSG_0x0009-T 2054131054 00A4DE5D ::: 14h ::: 10c
228 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'C' 15 '' 'E' 60, 694,
30, 1
228 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'c' 0 '' 'B' 30, 250,
30, 1
228 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'c' 0 '' 'B' 30, 250,
30, 1
228 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'c' 0 '' 'B' 30, 664,
30, 1
228 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'c' 0 '' 'B' 30, 664,
30, 1
228 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'c' 0 '' 'B' 30, 664,
30, 1
229 -> MSG_0x0009-T 2142451585 00A12320 ::: 5h ::: 4h
229 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'C' 15 '' 'E' 60, 250,
30, 1
229 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'c' 0 '' 'E' 80, 664,
40, 1
229 -> MSG_0x0007-T 2142451585 00A12320 'w' 0 'W' 65536
230 -> MSG_0x0009-T 2142451585 00A12320 ::: 5h ::: 14d
230 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'C' 30 '*' 'E' 90, 280, 30,
1
230 -> MSG_0x0007-T 2054131054 00A4DE5D 'w' 0 'W' 65536
- z čehož číslo nazačátku je počet koček (MSG_0x0009-T) (to jsem tam doplnil já jen pro orientaci)
- MSG_0x0009-T je myšleno jako kočka, která je unikátní
- MSG_0x0007-T je myšleno jako pes, který je vždy vícekrát
- dvě čísla zatím jsou myšlena jako číslo u psa nebo kočky, je jedno jaké si vyberete, jsou vždy stejná u psa i kočky
např.
225 -> MSG_0x0009-T 2142451585 00A12320 ::: 12d :::
10h
by se mělo spárovat s:
226 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'C' 30 '*' 'E' 90,
410, 30, 1
a
229 -> MSG_0x0009-T 2142451585 00A12320 ::: 5h ::: 4h
by se mělo spárovat s:
229 -> MSG_0x0007-T 2142451585 00A12320 'F' 65536 'C' 15 '*' 'E' 60, 250, 30,
1
- data za temi dvěma čísly potřebuji odeslat do mé třídy, samozřejmě
v polích a osekány o uvozovky v intu apod... Ta se už postará o všechno
ostatní
konstruktor třídy vyžaduje data u prvního příkladu tučně (s '*'), když je první pes s 'W', tak nevyžaduje nic a když je první cokoli jiného tak, vyžaduje ještě číslo před těmi dvěma v prvním příkladu.
Z toho co si postavil ty bohužel moc nevím Netuším jak to volat apod...
Díky moc za tvoji pomoc, ale budu jí pravděpodobně potřebovat ještě
trošku
Ahoj
Predelal jsem to trosku a pocitam s tim ze plati
Jak říkám, myslím, že bych si už poradil s tím parserem
takze staci si uz nejak zpracovat RawData v kazde kocce neboli zprave 009-T
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DogCatParser
{
class Program
{
static void Main(string[] args)
{
//nacteme soubor
string fileName = "inputFile.txt";
List<string> lines = new List<string>();
using (StreamReader sr = File.OpenText(fileName))
{
while (!sr.EndOfStream)
{
lines.Add(sr.ReadLine());
}
}
//pro lehkou praci s tema ID si pripravime dictionary s ID a vlastnim objektem MessageLog
//list jsem take premyslel, ale literace listu s Xtisic prvku je pomalejsi nez dictionart try get value
//kocky
Dictionary<string, MessageLog> MSG_0x0009T = new Dictionary<string, MessageLog>();
//psy
Dictionary<string, MessageLog> MSG_0x0007T = new Dictionary<string, MessageLog>();
//parsovani
foreach (var item in lines)
{
//zde mame radek napr 222 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'C' 15 '' 'E' 60, 634, 30, 1
string lineToParse = item;
//zbaveni se sipek a mezer a jinych znaku za tim - vse oddeleno jednou mezerou snad / pokud data vypadaji jinak musis si to opravit
lineToParse = lineToParse.Replace("-> ", "");
lineToParse = lineToParse.Replace("::: ", "");
lineToParse = lineToParse.Replace("'' ", "");
lineToParse = lineToParse.Replace("'", "");
lineToParse = lineToParse.Replace(",", "");
string[] rawValues = lineToParse.Split(' ');
int countOfLog;
int.TryParse(rawValues[0], out countOfLog);
string messageType = rawValues[1];
string ID = rawValues[2]; //je prej teda jedno jake takze by slo i rawValues[3]
string rawDataForParsing = "";
for (int i = 3; i < rawValues.Length; i++)
{
//data jsou i s ID druheho typu, pokud nepotrbujes zmen startovni index z 3 na 4 :)
//podminka aby na konci nebyla mezera.. kdyby ti to nejak vadilo
if (i != rawValues.Length - 1)
{
rawDataForParsing += rawValues[i] + " ";
}
else
{
rawDataForParsing += rawValues[i];
}
}
MessageLog messageLog;
if (messageType == "MSG_0x0009-T")
{
//pokud jakoby kocky NEOBSAHUJI toto ID tak pridame kocku s ID
if (!MSG_0x0009T.TryGetValue(ID, out messageLog))
{
MSG_0x0009T.Add(ID, new MessageLog(ID, countOfLog, rawDataForParsing));
}
}
else if (messageType == "MSG_0x0007-T")
{
//pokud jakoby pejskove NEOBSAHUJI toto ID tak pridame pejska s ID
if (!MSG_0x0007T.TryGetValue(ID, out messageLog))
{
//vsiml jsem si ze prikladu co si prilozil vznikala jedna vec u ktere si nejsem jisty CTI POZORNE
//existuje napr ID psa co je stejne jako kocka, ale PES JE PRVNI..
//takze vzit hodnotu prvni v souboru -- to mi prislo divne
//takze takhle podminka dela ze musi vzdy existovat kocka nez se muze vytvorit zprava typu psa se stejnym ID
// neboli PES BUDE VZDY pod kockou
// pokud nechces staci smazat tu podminka..
MessageLog tempLog;
if (MSG_0x0009T.TryGetValue(ID, out tempLog))
{
MSG_0x0007T.Add(ID, new MessageLog(ID, countOfLog, rawDataForParsing));
}
}
}
//protoze jedeme postupne tak se zbavime duplikatu jako je pes 1, pes 1 atd.. vzdy ten PRVNI a to stejne u kocky
}
foreach (var message in MSG_0x0009T)
{
//klasicke prirazeni koccka k psovi..
string ID = message.Key;
MessageLog subLog;
if (MSG_0x0007T.TryGetValue(ID, out subLog))
{
message.Value.subMessageLog = subLog;
}
}
//Ted to mas prirazeny k sobe jak chces.. pokud existuje zprava pod kockou se stejnym ID tak je v subLogu, zadny se neopakuje atd
//Ted musis si udelat ten tvuj parsing, mas to pripraveny v objektu tak ze to zvladnes :)
//priklad
foreach (var messageLog in MSG_0x0009T)
{
Console.WriteLine("====================");
Console.WriteLine("KOCKA");
Console.WriteLine("___________________");
var message = messageLog.Value;
//to prvni ID :)
Console.WriteLine(message.ID);
//to cislo co bylo pred sipkou
Console.WriteLine(message.CountOfLog);
//DATA NA ZPRACOVANI -- KOCKA !!!!
Console.WriteLine(message.RawData);
Console.WriteLine("___________________");
//pokud vubec ma k sobe pejska
if (message.subMessageLog != null)
{
Console.WriteLine("PES");
Console.WriteLine("___________________");
//zase to ma ID, CountOfLog
//DATA NA ZPRACOVANI PES s kontrolou
Console.WriteLine(message.subMessageLog.RawData);
Console.WriteLine("___________________");
}
}
}
}
class MessageLog
{
//ID by nemuselo byt protoze to mame ve slovniku co bude mit take stejny ID, mozna zbytecne duplikuju 2 inty
//ale je to pro pripad ze bys to chtel mit zachovany, at uz z jakehokoliv duvodu
public string ID { get; set; }
public int CountOfLog { get; set; } //pocet? nevim muzes prejmenovat
public string RawData { get; set; }
public MessageLog subMessageLog { get; set; }
public MessageLog(string ID, int CountOfLog, string RawData)
{
this.ID = ID;
this.CountOfLog = CountOfLog;
this.RawData = RawData;
}
}
}
Aha.... Podělal jsem to v zadání Omlouvám se!!!
Myslel jsem, že tím unikátních myslíš jako že je vždy jen jedna kočka na několik psů a že ke každé kočce musí být přiřazen jeden pes.
17 je jich unikátních, jako ve smyslu kocka 1, kocka 2, kocka 3 (kocka a
jiné číslo) apod... A vytáhne to přesně 17 koček a 17 psů. což je 100%
zbytek jsou třeba kocka 1, kocka 1, kocka 1, ale ty jsou také důležité
dejme tomu, ze je to takhle
kocka 1
kocka 2
pes 1 //priradit kocce 1
pes 2 //priradit kocce 2
pes 1
pes 2
kocka 1
pes 2
kocka 2
pes 1 ////priradit druhe kocce 1
pes 1
pes 2 ///priradit druhe kocce 2
kocka 1
pes 2
pes 2
kocka 3
pes 1 //priradit treti kocce 1
kocka 2
pes 3 //priradit kocce 3
pes 3
pes 2
pes 2
ve smyslu kocka 1, kocka 2 jsou z totoho unikatni jen 3, ale vypsanych jich má být 6 ...
ten parser vytahuje jen unikátní kočky , což by vytáhlo jen strašně
málo dat, potřebuji vytáhnout právě všechny kočky a k nim přiřadit
prvního psa se stejným číslem kterej je pod nimi
mojee chyba v pochopení
Dobra uz jsem to musel trosinku zmenit a nemohl jsem pouzit slovnikovy
system..
takze testoval jsem to na 56000 zaznamu --- OBE KOLEKCE dohromady toto cislo
dali - to znamena ze kocky to projelo doopravdy vsechny
Jinak pes byl vzdy ten nejblizsi pod kockou..
Pri testu z toho co si pridal ty viz tech asi 35 zprav - tak 10 kocek - ODPOVIDA
a k tomu 25 psu z nich se ovsem priradili jen ty spravne takze 10 kocek a 10 psu
snad uz je to ono
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace DogCatParser
{
class Program
{
static void Main(string[] args)
{
//nacteme soubor
string fileName = "inputFile.txt";
List<string> lines = new List<string>();
using (StreamReader sr = File.OpenText(fileName))
{
while (!sr.EndOfStream)
{
lines.Add(sr.ReadLine());
}
}
//dictionary nemuze mit stejne klice, takze muj ID system nejde pokud ma platit to tvoje nove zadani :), proto pouzijem listy / slo by to do jednoho, orientace ve 2 je lehci
//kocky
List<MessageLog> MSG_0x0009T = new List<MessageLog>();
//psy
List<MessageLog> MSG_0x0007T = new List<MessageLog>();
//toto je nova promenna, dulezita
int lineID = 0;
//parsovani
foreach (var item in lines)
{
//zde vse zustalo stejne
//zde mame radek napr 222 -> MSG_0x0007-T 2054131054 00A4DE5D 'F' 65536 'C' 15 '' 'E' 60, 634, 30, 1
string lineToParse = item;
//zbaveni se sipek a mezer a jinych znaku za tim - vse oddeleno jednou mezerou snad / pokud data vypadaji jinak musis si to opravit
lineToParse = lineToParse.Replace("-> ", "");
lineToParse = lineToParse.Replace("::: ", "");
lineToParse = lineToParse.Replace("'' ", "");
lineToParse = lineToParse.Replace("'", "");
lineToParse = lineToParse.Replace(",", "");
string[] rawValues = lineToParse.Split(' ');
int countOfLog;
int.TryParse(rawValues[0], out countOfLog);
string messageType = rawValues[1];
string ID = rawValues[2]; //je prej teda jedno jake takze by slo i rawValues[3]
string rawDataForParsing = "";
for (int i = 3; i < rawValues.Length; i++)
{
//data jsou i s ID druheho typu, pokud nepotrbujes zmen startovni index z 3 na 4 :)
//podminka aby na konci nebyla mezera.. kdyby ti to nejak vadilo
if (i != rawValues.Length - 1)
{
rawDataForParsing += rawValues[i] + " ";
}
else
{
rawDataForParsing += rawValues[i];
}
}
//vzdy pridame do 1 ze 2 listu
if (messageType == "MSG_0x0009-T")
{
MSG_0x0009T.Add(new MessageLog(lineID, ID, countOfLog, rawDataForParsing));
}
else if (messageType == "MSG_0x0007-T")
{
MSG_0x0007T.Add(new MessageLog(lineID, ID, countOfLog, rawDataForParsing));
}
lineID++;
}
//prirazovani kocka-pes
for (int i = 0; i < MSG_0x0009T.Count; i++)
{
var message = MSG_0x0009T[i];
MessageLog messageLog;
//vlastni tryparse metoda
//funguje to jako tryparse :) zakladne metoda vraci true nebo false ale nez vrati BOOL tak nastavi OUT promenou na MessageLog ci null
if (TryGetMessageLogWithSameID(MSG_0x0007T, message.ID, message.LineID, out messageLog))
{
message.subMessageLog = messageLog;
}
}
//opet vypis pro ukazku
foreach (var messageLog in MSG_0x0009T)
{
Console.WriteLine("====================");
Console.WriteLine("KOCKA");
Console.WriteLine("___________________");
var message = messageLog;
//to prvni ID :)
Console.WriteLine(message.ID);
//to cislo co bylo pred sipkou
Console.WriteLine(message.CountOfLog);
//DATA NA ZPRACOVANI -- KOCKA !!!!
Console.WriteLine(message.RawData);
Console.WriteLine("___________________");
//pokud vubec ma k sobe pejska
if (message.subMessageLog != null)
{
Console.WriteLine("PES");
Console.WriteLine("___________________");
//zase to ma ID, CountOfLog
//DATA NA ZPRACOVANI PES s kontrolou
Console.WriteLine(message.subMessageLog.RawData);
Console.WriteLine("___________________");
}
}
Console.ReadKey();
}
public static bool TryGetMessageLogWithSameID(List<MessageLog> msg007, string ID, int lineID, out MessageLog log)
{
//pokud maji stejne ID tak super ale zaroven to musi byt PRVNI POD NIM
//proto si zachovavame kolikaty je to radek v LineID
//to znamena ze pes musi mit vzdy vetsi hodnotu radku -- to znamena neprida to kocce psa se stejnym id co uz mela jina kocka nad tim.. vzdy bude pod
for (int i = 0; i < msg007.Count; i++)
{
var message = msg007[i];
if (message.ID == ID && message.LineID > lineID)
{
log = message;
return true;
}
}
log = null;
return false;
}
}
class MessageLog
{
public string ID { get; set; }
public int CountOfLog { get; set; }
public string RawData { get; set; }
public MessageLog subMessageLog { get; set; }
public int LineID { get; set; } // kolikaty je to radek v souboru!
public MessageLog(int LineID, string ID, int CountOfLog, string RawData)
{
this.ID = ID;
this.CountOfLog = CountOfLog;
this.RawData = RawData;
this.LineID = LineID;
}
}
}
+20 Zkušeností
+2,50 Kč

Neví někdo jak nastavit ten kod tak, aby si pamatoval "přesnou" pozici kde skončil se četním v logu???
FileStream logs = new FileStream("log.txt", FileMode.Open, FileAccess.ReadWrite);
StreamReader reader1 = new StreamReader(logs);
logs.Seek(logpos1, SeekOrigin.Begin);
while (!reader1.EndOfStream)
{
lines.Add(reader1.ReadLine());
}
logpos1 = logs.Position;
Tohle nastavuje pozici někam na konec, ale určitě ne přesně, protože,
když zkopíruju ten log a dám ho ještě jednou za původní tak to nepřečte
vůbec první 4 kočky u toho vložení a je tam problém s tím, že když
dám, aby to ukazovalo pozici po každým přečteným řádku,tak opět první
řádek má už hodnotu 1024 a 8x za sebou, a pořád to jde po 1024, potom 2048
apod...
Někde jsem četl, že se to nemá vůbec používat , pokud nechci ztratit
data... seek a position
Zobrazeno 21 zpráv z 21.