Programowanie w języku C# » Artykuły

R&D - Przykład przygotowań do stworzenia algorytmu. Korzystanie z COM

Wstęp


Jest rok 2016. Ceni się innowacyjność, jako motor gospodarki. Można czasem znaleźć oferty pracy dla programistów w działach R&D. Być może dla współczesnego młodego programisty  lub kandydata na studia techniczne R&D to skrót doskonale znany. Skrót pochodzi od angielskiego Research And Development, czyli po polsku Badania i Rozwój. Warto może wspomnieć, że coś takiego istniało przed transformacją ustrojową w roku 1989. Wiele zakładów przemysłowych posiadało działy badawczo-rozwojowe. Pozostały po nich liczne wynalazki i patenty. To rola historyków - porównać istnienie, rolę i szanse innowacyjności w okresie socjalizmu i obecnie. Porównanie to może okazać się niezwykle trudne.

Artykuł powstał na bazie rzeczywistego przykładu, nad którym autor pracował w jednej z firm. Chodziło o wyszukiwanie gotowych, już raz napisanych odpowiedzi email mentora na pytania uczestników szkoleń typu e-learning (internetowych) w oparciu o statystyczną analizę treści często obszernych pytań i odpowiedzi bez analizy semantycznej i pragmatycznej, czyli znaczenia słów i zdań oraz intencji pytającego. Algorytm miał dobrać najbardziej pasujące wzorce odpowiedzi, które po nieznacznej modyfikacji przez mentora mogły być udzielone szybciej.

Tym razem analizie poddawane jest forum 4programmers.net. Dla danego wątku i zbioru postów uczestników dyskusji, wybierane są te posty, na które oddano głos. Artykuł zawiera tylko początkowy etap tworzenia algorytmu. Zwykle przy tworzeniu poważniejszych programów powstaje wiele specjalizowanych programów narzędziowych. Dobrze jest korzystać z gotowych narzędzi, ale na drodze może stanąć szczególna specyfika celu badań i koszty komercyjnych narzędzi.

Zaznaczmy - to nie jest gotowe rozwiązanie i gotowy algorytm, tylko etap początkowy jego poszukiwania. Autor ma świadomość, że żyjemy w dobie dominacji Google Search. Być może jednak artykuł pokaże, że badania i rozwój - R&D - to ciekawa oferta pracy.

Metoda autora


Autor wybrał nieco ponad 10 wątków z forum Off-Topic, skopiował i wkleił kolejne posty ze stron internetowych forum do dokumentów programu MS Word - jeden wątek, jeden dokument. W dokumentach powstało wiele tabel, z których trzeba wydobyć tekst i  punktację. Zaczyna się więc od małego zbioru, a nie od analizy całego, ogromnego forum. Zaczyna się od obserwacji formy i struktury danych, z którymi trzeba będzie się zmierzyć. Można przewidzieć, że będzie potrzebny szybki parser tekstu. Banalna metoda kopiowania i wklejania okazuje się bardzo szybka do przygotowania próbnego zbioru, a forma dokumentu MS Word ułatwia wizualną analizę. Błędem byłoby rozpoczynanie od oglądania źródeł html stron forum. Do wydobycia tekstu z tabel dokumentów MS Office przydadzą się referencje do bibliotek COM. Pokazano jak je zrealizować pisząc program w C#. Początek analizy lepszy lub gorszy, może prymitywny, ale tani.

Nie tylko statystyka - źródłem punktacji są oceny wystawione przez ludzi


Czym jest punkt otrzymany na forum? 4programmers.net to duża społeczność - różne mogą być kryteria oceny poszczególnych osób. Jednak wszyscy reprezentujemy tę samą dziedzinę - pisanie programów komputerowych. Jedni są już ekspertami, inny zadają bardzo podstawowe pytania. Rozpoczęcie od pewnej  metody doprowadziło do statystyki, lecz ta tylko opisuje oceny postów oparte na wiedzy członków społeczności. Dobę inżynierii wiedzy przewidywano wiele lat temu. Mówienie o prymitywnej metodzie przygotowania algorytmu generalnie jest raczej pozbawione sensu, jednak pokazuje, że już na początku można się potknąć choćby o brak umiejętności skorzystania z bibliotek COM.

Załącznik - zbiór postów z forum w formacie MS Word


zbior_tekstow_z_forum.zip (3,51 MB)

Dodawanie referencji w C# - podstawy



Menu Visual Studio


Lista dostępnych bibliotek


Referencje w Solution Explorerze

// Word 2010, Reference COM: Microsoft Word 14.0 Object Library
using Word = Microsoft.Office.Interop.Word;
 
// Word 2003, Reference COM: Microsoft Word 11.0 Object Library
// using Word; 

Wpisanie do sekcji using


using System;
using System.Collections;
using System.Diagnostics;
using System.IO;
 
// Word 2010, Reference COM: Microsoft Word 14.0 Object Library
using Word = Microsoft.Office.Interop.Word;
 
// Word 2003, Reference COM: Microsoft Word 11.0 Object Library
// using Word; 
 
namespace Extractor
{
    class Program
    {
        static void Main(string[] args)
        {
            if (args.GetLength(0) != 3)              // sprawdzenie ilości argumentów programu
                return;
 
            string path = args[0];                   // argument 1: ścieżka folderu z dokumentami źródłowymi Word
            string filename = path + "\\" + args[1]; // argument 2: nazwa przetwarzanego dokumentu
            string topicId = args[2];                // argument 3: sygnatura grupy postów (dowolna)
 
            const int MaxPoints = 50;                // założone ograniczenie z góry liczby głosów oddanych na post
            char[] separators = { '\r', '\n' };      // separatory oddzielające wiersze
 
            object openFileName = new object();      // nazwa przetwarzanego dokumentu
            openFileName = filename;
            object separator = new object();         // separator komórek tabel
            separator = "\n";
            object nestedTables = new object();      // flaga nakazująca przetwarzanie tabel zagnieżdżonych
            nestedTables = true;
            ArrayList lines = new ArrayList();       // lista wierszy
 
            // podczas tworzenia programu
            // ta funkcja bywa przydatna
 
            // KillWordProcesses();                  // zakończenie wszystkich uruchominych procesów aplikacji Word
 
            Console.WriteLine(filename);             // wypisanie na ekranie konsoli nazwy przetwarzanego dokumentu
 
            Word.Application word = new Word.Application();       // uruchomienie aplikacji Word
            word.Visible = false;                                 // nie wyświetlaj aplikacji Word
 
            Word.Document doc = null;                             // dokument otwierany w aplikacji Word
 
            try
            {
                doc = word.Documents.Open(ref openFileName);     // otwarcie dokumentu
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine("Nacisnij dowolny klawisz...");
                Console.ReadKey();                                // oczekiwanie na wciśnięcie dowolnego klawisza,
                return;                                           // aby było możliwe przeczytanie tekstu na konsoli
            }
 
            foreach (Word.Table t in doc.Tables)                  // przekształć wszystkie tabele w dokumencie na tekst 
            {
                string s = t.ConvertToText(ref separator, ref nestedTables).Text;       // konwersja na tekst
                string[] range = s.Replace(('\v').ToString(), "").Split(separators);    // usunięcie zbędnych znaków 
                // i podział na wiersze
                lines.AddRange(range);                                                  // dodanie wszystkich wierszy 
            }                                                                           // pojedynczej tabeli do listy
 
            object saveFileName = new object();
            saveFileName = filename.Substring(0, filename.Length - (".doc").Length) + "-Kopia.doc";  // przygotowanie nazwy 
                                                                                                     // kopii dokumentu
            try
            {
                doc.SaveAs(ref saveFileName);        // zapisanie dokumetu pod inną nazwą
            }
            catch (IOException ex)
            {
                Console.WriteLine(ex.Message);
                Console.WriteLine("Nacisnij dowolny klawisz...");
                Console.ReadKey();
                return;
            }
 
#pragma warning disable 0467
 
            // ostrzeżenie kompilatora przed możliwością dwojakiego zinterpretowania kodu
            // tu jednak kompilator wybiera poprawnie i ostrzeżenie można wyłączyć
 
            //Warning: Ambiguity between method 'Word._Application.Quit(ref object, ref object, ref object)' 
            //and non-method 'Word.ApplicationEvents4_Event.Quit'.
 
            word.Quit();  // zamknięcie Worda
 
#pragma warning restore 0467
 
            int iPost = 1;    // numer kolejny postu w wątku na forum
            int iBegin = 0;   // wiersz z początkiem kolejnego postu w tekście niepodzielonym
            int k;            // numer kolejny wiersza w tekście niepodzielonym
 
            // iteracja po wszystkich wierszach dokumentu
 
            for (int li = 0; li < lines.Count; li++)
 
                // znajdowanie tekstu zawierającego liczbę głosów oddanych na post
                // teksty postaci np. "4Głosuj na ten post"
                // ograniczenie MaxPoints = 50 jest przyjęte z bardzo dużym zapasem
                // zwykle oddawanych jest 0, 1, 2, 3 głosy, najczesciej zero
                // iteracja 0..50 byłaby bardzo nieefektywna przy innym rozkładzie ilości
                // oddawanych głosów, tu jest wystarczajaca, przerywana przez break
                // po znalezieniu tekstu
 
                for (int j = 0; j < MaxPoints; j++)
                    if (lines[li].ToString() == j.ToString() + "Głosuj na ten post")
                    {
                        // utworzenie nazwy pliku (pojedynczy post) do dalszego przetwarzania
 
                        string filename2 = path + @"\Post_" + topicId + "_" + iPost.ToString("D04") + "_" + j.ToString("D02") + ".txt";
 
                        StreamWriter sw = new StreamWriter(filename2);   // otwarcie strumienia do zapisu pliku
 
                        for (k = iBegin; k <= li; k++)                   // iteracja po wierszach dokumentu bez tabel
                        {
                            string s = lines[k].ToString().Trim();       // obcięcie skrajnych spacji i znaków sterujących wiersza
 
                            while (s.Contains("  "))                     // pozostawienie tylko odstępów jednej spacji miedzy słowami
                                s.Replace("  ", " ");
 
                            if (s != j.ToString() + "Głosuj na ten post")
                                try
                                {
                                    sw.WriteLine(s);
                                }
                                catch (IOException ex)
                                {
                                    sw.Close();
                                    Console.WriteLine(ex.Message);
                                    Console.WriteLine("Nacisnij dowolny klawisz...");
                                    Console.ReadKey();
                                    return;
                                }
                        }
 
                        sw.Close();
                        Console.WriteLine(filename2); // po pomyślnym przetworzeniu i zapiasaniu postu kolejne iteracje 
                        iPost++;                      // wypisz nazwę pliku na ekranie konsoli 
                        iBegin = k;
                        break;
                    }
 
            // podczas tworzenia programu
            // ta funkcja bywa przydatna
 
            // KillWordProcesses(); 
 
            // poniższe 3 linie wziąć w komentarz przy przetwarzaniu
            // wielu dokumentów, przydatne przy testach przetwarzania pojedynczego dokumentu
 
            Console.WriteLine("Pliki zostaly przetworzone.");
            Console.WriteLine("Nacisnij dowolny klawisz...");
            Console.ReadKey();
        }
 
        // funkcja kończenia wszystkich procesów Worda
        // w fazie tworzenia algorytmu przetwarzania
        // zdarzają sie pomyłki prowadzące do uruchomienia
        // Worda zbyt wiele razy
 
        static void KillWordProcesses()
        {
            Process[] processList = Process.GetProcesses();
 
            foreach (Process process in processList)
                if (process.ProcessName.ToLower() == "winword")
                    process.Kill();
        }
    }
}