[C#] Домашно Strings and Text Processing - 19 задача


6

Условие: Write a program that extracts from a given text all dates that match the format DD.MM.YYYY. Display them in the standard date format for Canada.

Решениеsource.

Обяснение: Отново с регулярен израз избираме 2 цифри, следвани от точка, следвана от 2 цифри и нова точка и 4 цифри. След това с DateTime.TryParse() проверяваме дали е валидна датата. Накрая форматираме датата в канадска култура.




Отговори



9

/* В процеса на описване на регулярния израз успях да го подобря. В края на поста са разяснени промените, но описанието все още важи. */

Следният regex 

((([1-9]|[0-2][0-9]|3[01])\.((0[13578])|[13578]|(1[02])))|(([1-9]|[0-2][0-9]|30)\.((0[469])|[469]|11))|(([1-9]|[0-2][0-9])\.(2|02)))\.\d{4}

решава задачата. Или по- скоро върши това, което ми трябва. А то е следното. Трябва ми дата, която да може да се въвежда по различни начини и да се валидира от регулярния израз. Възможностите са DD.MM.YYYY (както по условие), но също така и D.MM.YYYY, D.M.YYYY, DD.M.YYYY, 0D.MM.YYYY, 0D.0M.YYYY и т.н.

Какво се случва в regexa?

Можете да се опитате да го разучите през следния линк http://pastebin.com/qVqQe5n8, където съм го разбил логически. Тук ще се опитам да го разясня по- сбито.

([1-9]|[0-2][0-9]|3[01])

Тук избираме ден от месеца. Т.е. можем да изберем от 1 до 9, или от 01 до 29 или 30 или 31. Очевидно това важи за месеци, които могат да имат 31 дена, затова логично следва

(0[13578])|[13578]|(1[02]))

където избираме между месеците с по 31 дни: януари (1 или 01), март (3 или 03), ..., август (8 или 08), октомври (10), декември (12).

Денят от месеца и самият месец се сливат посредством разделителна точка

(([1-9]|[0-2][0-9]|3[01])\.((0[13578])|[13578]|(1[02])))

и ни дават първата възможна комбинация, която накрая се слива с годината. Втората възможна комбинация започва по подобен начин

([1-9]|[0-2][0-9]|30)

Тук очевидно имаме аналогичен случай на предишния, но за месеци с максимум 30 дни. Те са април (4 или 04), юни (6 или 06), септември (9 или 09) и ноември (11). Това е отбелязано в следния израз

((0[469])|[469]|11)

Отново месец и ден са слети с точка и ни дават втората възможна комбинация, този път за месеците с 30 дни

(([1-9]|[0-2][0-9]|30)\.((0[469])|[469]|11))

На последно място трябва да се справим и с февруари, при който имаме максимум 29 дни.

(([1-9]|[0-2][0-9])\.(2|02))

Накрая трите комбинации са поставени в цяла група, от която да изберем една от трите възможности и към тях е прикрепена годината, за която няма очевидни ограничения (може да бъде разширен regexa в тази насока)

\d{4}

Пропуски

Естествено, regex помага донякъде. Най- добре е да се парсне след това датата, за да се провери със сигурност. В случая има още възможности за оптимизация. Например

[0-2][0-9]

оставя като възможност въвеждане на нулев ден (00). Затова можем да го променим на 

0[1-9]|[12][0-9]

Но сега виждаме друго нещо, имаме

[1-9]|0[1-9]|[12][0-9]

И можем да обединим първите 2 в един израз с optional нула в началото. Т.е.

(0?[1-9])|[12][0-9]

Така пълният израз за дните от месеца може да се запише по следния начин

((0?[1-9])|[12][0-9]|3[01])

Същата оптимизация можем да приложим и при месеците:

(0?[13578]) вместо   (0[13578])|[13578]
(0?[469]) вместо   (0[469])|[469]
(0?2) вместо   (2|02)

Крайният regex изглежда така

((((0?[1-9])|[12][0-9]|3[01])\.((0?[13578])|(1[02])))|(((0?[1-9])|[12][0-9]|30)\.((0?[469])|11))|(((0?[1-9])|[12][0-9])\.(0?2)))\.\d{4}

И ако не съм омазал нещо при оптимизирането, би трябвало да работи коректно. Изключихме датите, започващи с 00, както и опростихме някои от изразите. Отново може да се оптимизират годините, но както вече споменах, най- добре е да се парсне датата след преминаването с regex, защото се приемат дати като 29.02.2013 година, която не би трябвало да е валидна (2013 не е високосна).


от Anubis_Black (1521 точки)


0
Браво колега много добро обяснение :) Но аз все още не мога да си оправя задачата, изкарва ми първата дата и после хвърля изключение за формат. Това е кода : http://pastebin.com/w0wQmMpM

от son4etyyy (416 точки)

0
Дава ти Exception, защото се опитваш да парснеш дата, която е във формат d.M.YYYY посредством формат dd.MM.YYYY. Твоят код ще тръгне ако промениш dd.MM.YYYY на d.M.YYYY, но може при друг пример да се счупи (не мога да измисля такъв в момента). Като цяло идеята е, че регулярният израз ще намери много дати с различни формати, както съм казал в началото. Това несъмнено прави парсването трудно. Най- малкото не трябва да се ползва ParseExact, а само Parse.

от Anubis_Black (1521 точки)



0
http://pastebin.com/LmhgCb0m
Ужасният regex, който този път написах без да го мисля и стана, плюс MatchCollection с обхождането и на края try ... catch

от Evgenia Hristova (0 точки)


0

https://github.com/stoyans/Telerik/blob/master/CSharpPart2/StringsAndTextProcessing/19.ExtractDatetimeFormat/ExtractDatetimeFormat.cs

При моето решение разделям текста на думи и с цикъла се опитвам да парсна 3 последователни числа от масива с думите, ако са числа, след това проверявам дали отговарят на определени условия и принтирам датата.


от Tanis (75 точки)


0
добре е да проверяваш дали месеца има съответния брой дни, т.е. да не стане 30-ти февруари или 31 април

от wnvko (3123 точки)

0
Добра забележка, мерси.

от Tanis (75 точки)