[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 точки)



10

 

За хора като мен, които не обичат регулярни изрази, едно доста кратко решение, за което използвам DateTime.ParseExact()

решение + обяснение


от didi.zlatkova (100 точки)


0
Браво! на мен ми беше много полезно..Много хубава статия! Препоръчвам я и на другите да я прочетат... +1 от мен

от zhelyazkovn (2949 точки)

0
няма да работи, ако датата е в края на изречение, защото ще хване и точката след нея.




4

http://pastebin.com/1pFCsqDX

Предостявам малко по- дълго и тромаво решение, като цяло исках с регулярни изрази, но от тези, които намерих нито един не работеше на 100% правилно, видях, че колега го е направил така, браво на него,дано работи за всички случеи. 
Аз решех да направя задачата по по- ламерския начин.

Обяснение:

1)намирам индекса на първата точка indexOf('.')

2)Влизам в цикъл, докато има точки в текста, ако няма точки в текста indexOf('.'), връща -1, съотвено излизаме от цикъла

3)правя вложени 3 проверки съответно

  • Дали двете числа преди точката са числа, чрез метода TryParse, ако са числа продължаваме на вътре
  • Проверяваме по същия начин двете числа след точката,дали всъщност са числа
  • И черерите числа (т.е. година ли са)числата настринга на разтояние + 4 индекса от точката

По принцип и 2 проверки биха били достатъчни да се уверим, че това ще е дата, но за всеки случей правим и трите.

4) След това търсим нова точка, започвайки от края на предишната. Ако случайно срещем обикновена точка, както има една заблудена някъде из стригна, тя се пропуска, защото нещата преди нея, след нея не са числа и не отговарят на условията.

Не обясних само превръщането в Canada енкодинг на датата,но то е ясна, има го на слайд в лекцията.

 


от boncho.vylkov (1923 точки)


0
Напредваш, късмет и успех :)

от Assi.NET (3050 точки)

0
Мерси.. това кафе не ме оставя още.. и трябва да се прави нещо. :)

от boncho.vylkov (1923 точки)



0

Аз също мъчих с някакви регулярни изрази, но не хваща всичко точно от примера в книгата, ако някой има идея какво да пипна, за да го оправя ? 

 

string text = @"I was born at 14.06.1980. My sister was born at 3.7.1984. In 5/1999 I graduated my high school. 
            The law says (see section 7.3.12) that we are allowed to do this (section 7.4.2.9).";
            string[] splitText = text.Split(' ');
            Regex dates = new Regex("((19|20)\\d\\d)");
            for (int i = 0; i < splitText.Length; i++)
            {
                if (dates.IsMatch(splitText[i]))
                {
                    Console.WriteLine(splitText[i].ToString(CultureInfo.GetCultureInfo("en-CA")));
                }

от stoyankm (5 точки)


0
Тук е обяснено подробно: http://www.regular-expressions.info/dates.html

от jasssonpet (6814 точки)


2

http://pastebin.com/n7XfNY2p

едно решение от мен - има малко повече променливи но така по лесно дебъгвам. Ако не се лъжа този израз - "\d{2}.\d{2}.\d{4}" трябва да търси само за две цифри, последвани от точка, пак две цифри, пак точка и накрая четери цифри.

и с това ги екстрактвам от текста 


от ge_or_gi (110 точки)


0

Колега jasssonpet решението ти изкарва само пъррвата дата която отговаря на условието.


от son4etyyy (416 точки)


0
пробвах с "Static void Main() 12.10.2012, 50.13.2012, 01.01.2001, 12.12.2012" и си изкарва всичките, това е и идеята на foreach match нека и jasssonpet каже нещо по въпроса ти с какъв точно вход си пробвала?

от pdrenovska (2196 точки)

0
пробвах входа от книгата: "I was born at 14.06.1980. My sister was born at 3.7.1984. In 5/1999 I graduated my high school. The law says (see section 7.3.12) that we are allowed to do this (section 7.4.2.9)." изкарва само 14.06.1980, а би трябвало да работи и за 3.7.1984

от son4etyyy (416 точки)



0

Колеги помощ!!!. Нещо постоянно ми дава грешка при парсването и немога да разбера защо .

Еdt:

С любезното съдействие на AneliyaP.Petkova

измислих това:

http://pastebin.com/RGV2HwSb


от Svetli (280 точки)


0
Най-вече грешката идва от това, че след всичките ти дати има точки. Тоест регулярния ти израз ги открива, но DateTime не може да ги парсне. Например, ако махнеш точката след 14.06.1980. , тази дата ще ти излезе.


0
Да сега го изпробвах и е така. Пропускам момента, че парсвам date[i] което е с точка, защото сплитвам по интервалите. Месри много ще се опитам да го оправя .

от Svetli (280 точки)


0

Решение: source

Пак с помощта на познатите регулярни изрази извличам датите, конвертирам ги в указания формат с помощта на ToShortDateString() и ги пъхам в един списък. Печатам ги на конзолата като преди това съм и задал канадска култура.


от stanchev (197 точки)


0

// Вдигам бялото знаме и моля за помощ. Къде ми е грешката?

edit: оправих грешната дата - 31 ноември,, благодарение на vazzzz.
Чудех се цял час що не става,  малко ме е  срам!!! Също така регекса вече намира само първата дата.

        string text = "Today is 30.11.2013 and tomorow is 1.10.2013.";
     
        string pattern = @"\d{2}\.\d{2}\.\d{4}";
        Regex myReg = new Regex(pattern);
        MatchCollection matches = myReg.Matches(text);
        foreach (Match match in matches)
        {
            string str = match.ToString();
            string format = "dd.MM.yyyy";
            DateTime date = DateTime.ParseExact(str, format, CultureInfo.InvariantCulture);
            Console.WriteLine(date.ToString(CultureInfo.GetCultureInfo("en-CA")));
        }


от wooden_jesus (2128 точки)


0
Първата дата ти я хваща - 31.11.2013(въпреки, че ноември няма 31 дни :)). Не виждам грешка. Единственото, което трябва да направиш е да не изкарваш дати, които не съвападат с формата dd.MM.yyyy. В твоя случай 1.9.2013 не трябва да се мъчиш да я изпишеш, а просто да я прескочиш. Правилният отгоров е само 31.11.2013, защото е правилния формат. Като цяло не виждам проблем с програмата, а по-скоро си недоразбрал условието. Все пак, ако не съм прав може малко обяснение какъв точно е проблема, който търсиш. Може да промениш малко и регулярният израз, за да не прихваща дати в неправилен формат. Ще си спестиш работа след това. Това е изразът, който аз съм направил за тази задача. Освен, че прихваща само дати във формата dd.MM.yyy, също така и хваща само правилно записани такива. Т.е. няма да отчете 32.12.2012 или 01.13.2012, защото не съществуват 32 дни в месеца, нито 13 месеца. Ето го и самият израз @"((([0][1-9])|[1-2][0-9])|([3][0-1]))\.(([0][1-9])|([1][0-2]))\.[0-9]{1,4}").

от Vazzzz (1380 точки)

0
"(въпреки, че ноември няма 31 дни :))" Не е истина, колко съм тъп. Мерси за отговора, хаха, точно заради това ми даваше ексепшън.

от wooden_jesus (2128 точки)



0

GitHub

Решението е с регулярен израз - след като погледнах какво са сътворили колегите.


от dzhenko (3893 точки)


0

Подозрително лесно реших задачата и именно заради това имам огромни съмнения за коректността на решението http://pastebin.com/EAj4nwXy.

Идеята на решението ми е да чета текст,  да го превърна в стринг, от който да прехвърла само думите започващи с цифра.След това проверявам дали може да се парсне до DateTime и ако да го печатя.

С удоволствие ще приема критики (ползвания текст е най-накрая във вид на коментар) 


от geniusvil (192 точки)