//Copyright (C) Troy Magennis

using System;
using System.Collections;   
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
using SampleSupport;
using QuerySamples;
using System.Xml;
using System.Data.SqlClient;
using System.Security.Cryptography;
using System.Text;
using SampleQueries;
using System.Drawing;
using System.Runtime.InteropServices;

namespace SampleQueries
{
    [Title("Rozdział 3 - Pisanie podstawowych zapytań")]
    [Prefix("Listing_3_")]
    public class Chapter03Samples : SampleHarness
    {
        public class Contact
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
            public string Email { get; set; }
            public string Phone { get; set; }
            public DateTime DateOfBirth { get; set; }
            public string State { get; set; }

            public static List<Contact> SampleData()
            {
                return new List<Contact> {
                    new Contact {FirstName = "Bartłomiej", LastName = "Gajewski",      DateOfBirth = new DateTime(1945,10,19), Phone = "885 983 885", Email = "gajewski@aspiring-technology.com", State = "MA" },
                    new Contact {FirstName = "Alfred",     LastName = "Wieczorek",     DateOfBirth = new DateTime(1973,12,09), Phone = "848 553 848", Email = "al1@aspiring-technology.com", State = "WM" },
                    new Contact {FirstName = "Adam",       LastName = "Gadomski",      DateOfBirth = new DateTime(1959,10,03), Phone = "115 999 115", Email = "adamg@aspiring-technology.com", State = "OP" },
                    new Contact {FirstName = "Jan",        LastName = "Detka",         DateOfBirth = new DateTime(1950,12,16), Phone = "677 602 677", Email = "jan.detka@aspiring-technology.com", State = "MA" },
                    new Contact {FirstName = "Cezary",     LastName = "Zbytek",        DateOfBirth = new DateTime(1935,02,10), Phone = "603 303 603", Email = "czbytek@aspiring-technology.com", State = "LU" },
                    new Contact {FirstName = "Stanisław",  LastName = "Kowal",         DateOfBirth = new DateTime(1950,02,20), Phone = "546 607 546", Email = "kowals@aspiring-technology.com", State = "WM" },
                    new Contact {FirstName = "Cyryl",      LastName = "Latos",         DateOfBirth = new DateTime(1951,10,21), Phone = "278 918 278", Email = "latos@aspiring-technology.com", State = "WM" },
                    new Contact {FirstName = "Bernard",    LastName = "Radliński",     DateOfBirth = new DateTime(1946,05,18), Phone = "715 920 715", Email = "bernard@aspiring-technology.com", State = "WP" },
                    new Contact {FirstName = "Maciej",     LastName = "Karaś",         DateOfBirth = new DateTime(1977,09,17), Phone = "364 202 364", Email = "mac.karas@aspiring-technology.com", State = "WP" },
                    new Contact {FirstName = "Adrian",     LastName = "Hawrat",        DateOfBirth = new DateTime(1922,05,23), Phone = "165 737 165", Email = "adrianh@aspiring-technology.com", State = "SW" }
                };

            }
        }

        public class CallLog
        {
            public string Number { get; set; }
            public int Duration { get; set; }
            public bool Incoming { get; set; }
            public DateTime When { get; set; }

            public static List<CallLog> SampleData()
            {
                return new List<CallLog> {
                    new CallLog { Number = "885 983 885", Duration = 2,  Incoming = true,  When = new DateTime(2006,	8,	7,	8,	12,	0)},
                    new CallLog { Number = "165 737 165", Duration = 15, Incoming = true,  When = new DateTime(2006,	8,	7,	9,	23,	0) },
                    new CallLog { Number = "364 202 364", Duration = 1,  Incoming = false, When = new DateTime(2006,	8,	7,	10,	5,	0) },
                    new CallLog { Number = "603 303 603", Duration = 2,  Incoming = false, When = new DateTime(2006,	8,	7,	10,	35,	0) },
                    new CallLog { Number = "546 607 546", Duration = 4,  Incoming = true,  When = new DateTime(2006,	8,	7,	11,	15,	0) },
                    new CallLog { Number = "885 983 885", Duration = 15, Incoming = false, When = new DateTime(2006,	8,	7,	13,	12,	0) },
                    new CallLog { Number = "885 983 885", Duration = 3,  Incoming = true,  When = new DateTime(2006,	8,	7,	13,	47,	0) },
                    new CallLog { Number = "546 607 546", Duration = 1,  Incoming = false, When = new DateTime(2006,	8,	7,	20,	34,	0) },
                    new CallLog { Number = "546 607 546", Duration = 3,  Incoming = false, When = new DateTime(2006,	8,	8,	10,	10,	0) },
                    new CallLog { Number = "603 303 603", Duration = 23, Incoming = false, When = new DateTime(2006,	8,	8,	10,	40,	0) },
                    new CallLog { Number = "848 553 848", Duration = 3,  Incoming = false, When = new DateTime(2006,	8,	8,	14,	0,	0) },
                    new CallLog { Number = "848 553 848", Duration = 7,  Incoming = true,  When = new DateTime(2006,	8,	8,	14,	37,	0) },
                    new CallLog { Number = "278 918 278", Duration = 6,  Incoming = true,  When = new DateTime(2006,	8,	8,	15,	23,	0) },
                    new CallLog { Number = "364 202 364", Duration = 20, Incoming = true,  When = new DateTime(2006,	8,	8,	17,	12,	0) },
                    new CallLog { Number = "885 983 885", Duration = 5,  Incoming = true,  When = new DateTime(2006,	7,	12,	8,	12,	0)},
                    new CallLog { Number = "165 737 165", Duration = 12, Incoming = true,  When = new DateTime(2006,	6,	14,	9,	23,	0) },
                    new CallLog { Number = "364 202 364", Duration = 10,  Incoming = false, When = new DateTime(2006,	7,	9,	10,	5,	0) },
                    new CallLog { Number = "603 303 603", Duration = 22,  Incoming = false, When = new DateTime(2006,	7,	5,	10,	35,	0) },
                    new CallLog { Number = "546 607 546", Duration = 9,  Incoming = true,  When = new DateTime(2006,	6,	7,	11,	15,	0) },
                    new CallLog { Number = "885 983 885", Duration = 10, Incoming = false, When = new DateTime(2006,	6,	7,	13,	12,	0) },
                    new CallLog { Number = "885 983 885", Duration = 21,  Incoming = true,  When = new DateTime(2006,	7,	7,	13,	47,	0) },
                    new CallLog { Number = "546 607 546", Duration = 7,  Incoming = false, When = new DateTime(2006,	7,	7,	20,	34,	0) },
                    new CallLog { Number = "546 607 546", Duration = 2,  Incoming = false, When = new DateTime(2006,	6,	8,	10,	10,	0) },
                    new CallLog { Number = "603 303 603", Duration = 3,  Incoming = false, When = new DateTime(2006,	6,	8,	10,	40,	0) },
                    new CallLog { Number = "848 553 848", Duration = 32,  Incoming = false, When = new DateTime(2006,	7,	8,	14,	0,	0) },
                    new CallLog { Number = "848 553 848", Duration = 13,  Incoming = true,  When = new DateTime(2006,	7,	8,	14,	37,	0) },
                    new CallLog { Number = "278 918 278", Duration = 16,  Incoming = true,  When = new DateTime(2006,	5,	8,	15,	23,	0) },
                    new CallLog { Number = "364 202 364", Duration = 24, Incoming = true,  When = new DateTime(2006,	6,	8,	17,	12,	0) }
                };
            }
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-1 : Podstawowe zapytanie za pomocą metody rozszerzenia")]
        [Description("Zapytanie wyszukuje wszystkie kontakty w województwie oznaczonym skrótem WM i sortuje je według nazwiska, a następnie imienia za pomocą składni metod rozszerzeń.")]
        [LinkedClass("Contact")]
        public void Listing_3_1_ExtensionMethods()
        {
            List<Contact> contacts = Contact.SampleData();

            var q = contacts.Where(c => c.State == "WM")
                            .OrderBy(c => c.LastName)
                            .ThenBy(c => c.FirstName);

            foreach (Contact c in q)
                Console.WriteLine("{0} {1}", 
                    c.FirstName, c.LastName);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-2 : Podstawowe zapytanie za pomocą wyrażenia zapytania)"]
        [Description("Zapytanie wyszukuje wszystkie kontakty w województwie oznaczonym skrótem WM i sortuje je według nazwiska, a następnie imienia za pomocą składni wyrażeń zapytań.")]
        [LinkedClass("Contact")]
        public void Listing_3_2_QueryExpression()
        {
            List<Contact> contacts = Contact.SampleData();

            var q = from c in contacts
                    where c.State == "WM"
                    orderby c.LastName, c.FirstName
                    select c;

            foreach (Contact c in q)
                Console.WriteLine("{0} {1}",
                    c.FirstName, c.LastName);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-3 : Złączenie za pomocą składni metod rozszerzeń")]
        [Description("Przykład demonstruje zapytanie LINQ za pomocą składni metod rozszerzeń tworzące złączenie z inną kolekcją.")]
        [LinkedClass("Contact")]
        [LinkedClass("CallLog")]
        public void Listing_3_3_ExtensionMethodsWithJoin()
        {
            List<Contact> contacts = Contact.SampleData();
            List<CallLog> callLog = CallLog.SampleData();

            var q = callLog.Join(contacts,
                                  call => call.Number,
                                  contact => contact.Phone,
                                  (call, contact) => new
                                  {
                                      contact.FirstName,
                                      contact.LastName,
                                      call.When,
                                      call.Duration
                                  })
                           .Take(5)
                           .OrderByDescending(call => call.When);

            foreach (var call in q)
                Console.WriteLine("{0} - {1} {2} ({3}min)",
                    call.When.ToString("ddMMM HH:m"),
                    call.FirstName, call.LastName, call.Duration);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-4 : Złączenie za pomocą składni wyrażeń zapytań")]
        [Description("Przykład demonstruje zapytanie LINQ za pomocą składni wyrażeń zapytań tworzące złączenie z inną kolekcją.")]
        [LinkedClass("Contact")]
        [LinkedClass("CallLog")]
        public void Listing_3_4_QueryExpressionWithJoin()
        {
            List<Contact> contacts = Contact.SampleData();
            List<CallLog> callLog = CallLog.SampleData();

            var q = (from call in callLog
                     join contact in contacts on 
                        call.Number equals contact.Phone
                     orderby call.When descending
                     select new
                     {
                         contact.FirstName,
                         contact.LastName,
                         call.When,
                         call.Duration
                     }).Take(5);

            foreach (var call in q)
                Console.WriteLine("{0} - {1} {2} ({3}min)",
                    call.When.ToString("ddMMM HH:m"),
                    call.FirstName, call.LastName, call.Duration);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3 : Prosty predykat Where wyrażenia Lambda.")]
        [Description("Przykład demonstruje prosty predykat Where wyrażenia Lambda.")]
        public void Listing_3_SimpleWhereLambdaExpressionPredicate()
        {
            string[] animals = new string[] { "Koala", "Kangur", 
                "Pająk", "Wombat", "Waran", "Wąż", "Rekin", 
                "Płaszczka", "Meduza" };

            var q = animals.Where(
                a => a.StartsWith("W") && a.Length > 4);

            foreach (string s in q)
                Console.WriteLine(s);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3 : Prosty predykat Where wyrażenia zapytania.")]
        [Description("Przykład demonstruje prosty predykat Where wyrażenia zapytania.")]
        public void Listing_3_SimpleWhereQueryExpressionPredicate()
        {
            string[] animals = new string[] { "Koala", "Kangur", 
                "Pająk", "Wombat", "Waran", "Wąż", "Rekin", 
                "Płaszczka", "Meduza" };

            var q = from a in animals
                    where a.StartsWith("W") && a.Length > 4
                    select a;

            foreach (string s in q)
                Console.WriteLine(s);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3 : Prosta funkcja zewnętrzna predykatu Where.")]
        [Description("Przykład demonstruje, jak używać zewnętrznej funkcji predykatu w wyrażeniu Where do wydzielenia logiki w jednym miejscu.")]
        public void Listing_3_SimpleWhereExternalPredicate()
        {
            string[] animals = new string[] { "Koala", "Kangur", 
                "Pająk", "Wombat", "Waran", "Wąż", "Rekin", 
                "Płaszczka", "Meduza" };

            var q = from a in animals
                    where MyPredicate(a)
                    select a;

            foreach (string s in q)
                Console.WriteLine(s);
        }

        public bool MyPredicate(string a)
        {
            if (a.StartsWith("W") && a.Length > 4)
                return true;
            else
                return false;
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-5 : Wyrażenie Where korzystające z metody zewnętrznej .")]
        [Description("Przykład demonstruje wyrażenie Where korzystające z metody zewnętrznej do oceny predykatu Where.")]
        [LinkedMethod("IsAnimalDeadly")]
        public void Listing_3_5_WhereQueryExpressionExternalPredicate()
        {
            string[] animals = new string[] { "Koala", "Kangur", 
                "Pająk", "Wombat", "Waran", "Wąż", "Rekin", 
                "Płaszczka", "Meduza" };

            var q = from a in animals
                    where IsAnimalDeadly(a)
                    select a;

            foreach (string s in q)
                Console.WriteLine("{0} może być niebezpieczny(a).", s);
        }

        public static bool IsAnimalDeadly(string s)
        {
            string[] deadly = new string[] {"Pająk", "Waran", "Wąż", 
                "Rekin", "Płaszczka", "Meduza"};

            return deadly.Contains(s);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-6 : Filtrowanie za pomocą pozycji indeksu.")]
        [Description("Przykład filtruje wyniki za pomocą pozycji indeksu.")]
        public void Listing_3_6_FilterByIndexPosition()
        {
            string[] animals = new string[] { "Koala", "Kangur", 
                "Pająk", "Wombat", "Waran", "Wąż", "Rekin", 
                "Płaszczka", "Meduza" };

           // pobranie pierwszego i co drugiego zwierzęcia (indeksy parzyste)
            var q = animals.Where((a, index) => index % 2 == 0);

            foreach (string s in q)
                Console.WriteLine(s);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3 : Przykładowe operatory zwracające ustalony typ.")]
        [Description("Przykład pokazuje operatory zwracające ustalony typ zamiast IEnumerabe<T>.")]
        public void Listing_3_ExampleOperatorsReturningFixedType()
        {
            int[] nums = new int[] { 5, 3, 4, 2, 7, 6, 4, 5, 
                8, 2, 1, 2, 3, 4, 2 };

            int lessThan5 = nums.Count(n => n < 5); // z filtrem
            int product = nums.Aggregate(
                (runningProduct, n) => runningProduct *= n);

            Console.WriteLine("{0} liczb(y) mniejszych niż 5.", lessThan5);
            Console.WriteLine("Iloczyn wszystkich liczb wynosi {0}", 
                product);

            //
            int first = nums.First();
            int firstGT5 = nums.First(n => n > 5); 

            // brak n == 100, zwraca <T>.default
            int firstDflt = nums.FirstOrDefault(n => n == 100);

            //
            // pobierz 8. wpis (liczenie od zera)
            int element = nums.ElementAt(7); 

            Console.WriteLine(
                "first: {0}, first > 5: {1}, first default: {2}",
                first, firstGT5, firstDflt);
        }

        public class ContactName
        {
            public string FullName { get; set; }
            public int YearsOfAge { get; set; }

            // konstruktor potrzebny dla przykładu inicjalizacji obiektów
            public ContactName() {
            }

            // konstruktor potrzebny dla przykładu projekcji
            public ContactName(string name, int age)
            {
                this.FullName = name;
                this.YearsOfAge = age;
            }
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-7 : Projekcja wybierania tworząca nowy typ.")]
        [Description("Projekcja do kolekcji nowego typu — skonstruowanej za pomocą określonego konstruktora lub za pomocą składni inicjalizacji.")]
        [LinkedClass("ContactName")]
        public void Listing_3_7_SelectConstructingNewType()
        {
            List<Contact> contacts = Contact.SampleData();

            // użycie konstruktora z parametrami
            IEnumerable<ContactName> q1 =
                from c in contacts
                select new ContactName(
                       c.LastName + ", " + c.FirstName,
                       (DateTime.Now - c.DateOfBirth).Days / 365);

            // użycie składni inicjalizowania typów
            // uwaga: typ wymaga konstruktora bez parametrów
            IEnumerable<ContactName> q2 =
              from c in contacts
              select new ContactName
              {
                  FullName = c.LastName + ", " + c.FirstName,
                  YearsOfAge = 
                       (DateTime.Now - c.DateOfBirth).Days / 365
              };
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-8 :  Select kontra SelectMany.")]
        [Description("Przykład demonstruje użycie Select i SelectMany do pod-iterowania elementów będących kolekcjami IEnumerable.")]
        public void Listing_3_8_SelectMany()
        {
            string[] sentence = new string[] { "Pewien szybki brązowy", 
                "lis przeskakuje nad", "bardzo leniwym psem."};

            Console.WriteLine("opcja 1:"); Console.WriteLine("------");

            // opcja 2: Select zwraca trzy wartości string[]
            // każda z nich zawiera trzy ciągi znaków.
            IEnumerable<string[]> words1 =
                sentence.Select(w => w.Split(' '));

            // aby pobrać każde ze słów musimy użyć dwóch pętli foreach
            foreach (string[] segment in words1)
                foreach (string word in segment)
                    Console.WriteLine(word);

            Console.WriteLine();
            Console.WriteLine("opcja 2:"); Console.WriteLine("------");

            // opcja 2: SelectMany zwraca dziewięć ciągów
            // (iteruje przez wynik Select)
            IEnumerable<string> words2 =
                sentence.SelectMany(segment => segment.Split(' '));

            // za pomocą SelectMany mamy dostęp do każdego ciągu osobno
            foreach (var word in words2)
                Console.WriteLine(word);

            // opcja 3: identyczna jak opcja 2 powyżej napisana za pomocą
            // składnia wyrażeń zapytań (kilka instrukcji from)
            IEnumerable<string> words3 =
                from segment in sentence
                from word in segment.Split(' ')
                select word;
        }


        [Category("Jak pisać zapytania")]
        [Title("Listing 3-9 : Rozpoznawanie pozycji indeksu wyników.")]
        [Description("Przykład demonstruje, jak ustalić pozycję indeksu wyniku liczoną od zera.")]
        [LinkedClass("CallLog")]
        public void Listing_3_9_IndexPositionWithSelect()
        {
            List<CallLog> callLog = CallLog.SampleData();

            var q = callLog.GroupBy(g => g.Number)
                           .OrderByDescending(g => g.Count())
                           .Select((g, index) => new
                           {
                               number = g.Key,
                               rank = index + 1,
                               count = g.Count()
                           });

            foreach (var c in q)
                Console.WriteLine(
                    "Pozycja {0} - {1}, dzwonił(a) {2} razy.",
                    c.rank, c.number, c.count);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3 : Usuwanie duplikatów za pomocą operatora Distinct.")]
        [Description("Przykład demonstruje, jak usuwać duplikaty za pomocą operatora Distinct.")]
        public void Listing_3_RemovingDuplicatesWithDistinct()
        {
            string[] names = new string[] { "Piotr", "Paweł", 
                "Maria", "Piotr", "Paweł", "Maria", "Janina" };

            var q = (from s in names
                     select s).Distinct();

            foreach (var name in q)
                Console.WriteLine(name);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3 : Operator Reverse.")]
        [Description("Przykład demonstruje, jak odwracać kolejność elementów w sekwencji.")]
        public void Listing_3_ReverseOperator()
        {
            string[] letters = new string[] { "A", "B", "C" };
            var q = letters.Reverse();
            foreach (string s in q)
                Console.Write(" " + s);
        }
        
        [Category("Jak pisać zapytania")]
        [Title("Listing 3-10 : Sortowanie ciągów znaków bez uwzględniania wielkości liter za pomocą StringComparer.")]
        [Description("Przykład demonstruje sortowanie ciągów znaków bez uwzględniania wielkości liter za pomocą wbudowancyh funkcji StringComparer.")]
        public void Listing_3_10_CaseInsensitiveOrderingUsingStringComparer()
        {
            string[] words = new string[] { 
                "jAnina", "JAnina", "janina", "Janina" };

            var cs = words.OrderBy(w => w);
            var ci = words.OrderBy(w => w, 
                StringComparer.CurrentCultureIgnoreCase);

            Console.WriteLine("Oryginalna kolejność:");
            foreach (string s in words)
                Console.WriteLine(" " + s);

            Console.WriteLine("Uwzględniające wielkości liter (domyślne):");
            foreach (string s in cs)
                Console.WriteLine(" " + s);

            Console.WriteLine("Nieuwzględniające wielkości liter:");
            foreach (string s in ci)
                Console.WriteLine(" " + s);
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-11 : Implementacja komparatora własnego.")]
        [Description("Przykład demonstruje własną funkcję komparatora.")]
        [LinkedClass("RandomShuffleStringSort")]
        public void Listing_3_11_SortingUsingCustomComparer()
        {
            string[] strings = new string[] { "1-jeden", "2-dwa", 
                "3-trzy", "4-cztery", "5-pięć" };

            var normal = strings.OrderBy(s => s);
            var custom = strings.OrderBy(s => s, 
                new RandomShuffleStringSort<string>());

            Console.WriteLine("Zwykła kolejność sortowania:");
            foreach (string s in normal) {
                Console.WriteLine(" " + s);
            }

            Console.WriteLine("Własna kolejność sortowania:");
            foreach (string s1 in custom) {
                Console.WriteLine(" " + s1);
            }
        }

        public class RandomShuffleStringSort<T> : IComparer<T>
        {
            internal Random random = new Random();

            public int Compare(T x, T y)
            {
                // liczba losowa: 0 lub 1
                int i = random.Next(2);

                if (i == 0)
                    return -1;
                else
                    return 1;
            }
        }

        [Category("Jak pisać zapytania")]
        [Title("Listing 3-12 : Sortowanie za pomocą własnego komparatora.")]
        [Description("Przykład demonstruje sortowanie za pomocą własnej funkcji komparatora.")]
        public void Listing_3_12_SortingUsingAlphaNumericComparer()
        {
            string[] partNumbers = new string[] { "SCW10", "SCW1", 
                "SCW2", "SCW11", "NUT10", "NUT1", "NUT2", "NUT11" };

            var normal = partNumbers.OrderBy(s => s);
            var custom = partNumbers.OrderBy(s => s,
                new AlphaNumberSort());

            Console.WriteLine("Zwykły porządek sortowania:");
            foreach (string s in normal)
                Console.WriteLine(" " + s);

            Console.WriteLine("Własny porządek sortowania:");
            foreach (string s in custom)
                Console.WriteLine(" " + s);

            // za pomocą wbudowanego API 2003, XP i Vista
            // http://msdn.microsoft.com/en-us/library/bb759947(VS.85).aspx
            var native = partNumbers.OrderBy(s => s, new NaturalStringComparer());
            Console.WriteLine("Zwykły porządek sortowania naturalnego:");
            foreach (string s in native)
                Console.WriteLine(" " + s);
        }

        internal static class Shlwapi
        {
            // http://msdn.microsoft.com/en-us/library/bb759947(VS.85).aspx
            [DllImport("shlwapi.dll", CharSet = CharSet.Unicode)]
            public static extern int StrCmpLogicalW(string a, string b);
        }

        public sealed class NaturalStringComparer : IComparer<string>
        {
            public int Compare(string a, string b)
            {
                return Shlwapi.StrCmpLogicalW(a, b);
            }
        }

        public class AlphaNumberSort : IComparer<string>
        {
            public int Compare(string a, string b)
            {
                StringComparer sc = 
                    StringComparer.CurrentCultureIgnoreCase;

                // jeśli którakolwiek wartość wejściowa jest null lub pusta
                // wykonaj proste porównanie ciągów
                if (string.IsNullOrEmpty(a) || 
                    string.IsNullOrEmpty(b))
                        return sc.Compare(a, b);

                // znajdywanie części numerycznych
                string numericX = FindTrailingNumber(a);
                string numericY = FindTrailingNumber(b);

                // jeśli w obu ciągach istnieje część numeryczna, 
                // musimy badać dalej
                if (numericX != string.Empty && 
                    numericY != string.Empty)
                {
                    // na początku porównujemy przyrostek
                    int stringPartCompareResult =
                        sc.Compare(
                            a.Remove(a.Length - numericX.Length),
                            b.Remove(b.Length - numericY.Length));

                    // jeśli ciągi stanowiące przyrostki są różne,
                    // zwracamy wynik ich porównania
                    if (stringPartCompareResult != 0)
                        return stringPartCompareResult;

                    // jeśli ciągi stanowiące przyrostki są takie same,
                    // musimy sprawdzić także część numeryczną
                    double nX = double.Parse(numericX);
                    double nY = double.Parse(numericY);
                    return nX.CompareTo(nY);
                }
                else
                    return sc.Compare(a, b);
            }

            private static string FindTrailingNumber(string s)
            {
                string numeric = string.Empty;
                for (int i = s.Length - 1; i > -1; i--)
                {
                    if (char.IsNumber(s[i]))
                        numeric = s[i] + numeric;
                    else
                        break;
                }
                return numeric;
            }
        }


        [Category("Inne zagadnienia związane z sortowaniem")]
        [Title("Test stabilności implementacji algorytmu Quicksort")]
        [Description("Przykład testuje implementację algorytmu Quicksort w LINQ to Objects.")]
        [LinkedMethod("Sort")]
        [LinkedMethod("QuickSort")]
        public void Listing_3_QuicksortTest()
        {
            int[] elements = new int[] { 0, 1, 2, 1 };
            int[] map = Sort(elements, elements.Length);

            // Zmienna map zawiera pozycję indeksu, w kolejności po sortowaniu.
            // Zauważ, że indeks 3 znajduje się przed 1, mimo że oba przechowują
            // wartość 1. Zobacz  http://en.wikipedia.org/wiki/Quicksort

            Console.WriteLine("Sortowanie stabilne - pozycje indeksu 0 1 3 2");
            Console.WriteLine("Sortowanie niestabilne - pozycje indeksu - 0 3 1 2");
            Console.WriteLine();
            Console.WriteLine("Pozycje indeksu w zmiennej map:");
            foreach (int i in map)
                Console.Write(i + " ");

        }

        // Aktualna implementacja algorytmu sortowania w .Net 3.5 z 11/2008
        public void QuickSort(int[] keys, int[] map, int left, int right)
        {
            do
            {
                int index = left;
                int num2 = right;
                int num3 = map[index + ((num2 - index) >> 1)];
                do
                {
                    while ((index < map.Length) && (keys[num3].CompareTo(keys[index]) > 0))
                    {
                        index++;
                    }
                    while ((num2 >= 0) && (keys[num3].CompareTo(keys[num2]) < 0))
                    {
                        num2--;
                    }
                    if (index > num2)
                    {
                        break;
                    }
                    if (index < num2)
                    {
                        int num4 = map[index];
                        map[index] = map[num2];
                        map[num2] = num4;
                    }
                    index++;
                    num2--;
                }
                while (index <= num2);
                if ((num2 - left) <= (right - index))
                {
                    if (left < num2)
                    {
                        this.QuickSort(keys, map, left, num2);
                    }
                    left = index;
                }
                else
                {
                    if (index < right)
                    {
                        this.QuickSort(keys, map, index, right);
                    }
                    right = num2;
                }
            }
            while (left < right);
        }

        public Int32[] Sort(int[] elements, int count)
        {
            int[] map = new int[count];
            for (int i = 0; i < count; i++)
            {
                map[i] = i;
            }
            
            QuickSort(elements, map, 0, count - 1);
            return map;
        }
    }
}