На главную Наши проекты:
Журнал   ·   Discuz!ML   ·   Wiki   ·   DRKB   ·   Помощь проекту
ПРАВИЛА FAQ Помощь Участники Календарь Избранное RSS
msm.ru
! ПРАВИЛА РАЗДЕЛА · FAQ раздела Delphi · Книги по Delphi
Пожалуйста, выделяйте текст программы тегом [сode=pas] ... [/сode]. Для этого используйте кнопку [code=pas] в форме ответа или комбобокс, если нужно вставить код на языке, отличном от Дельфи/Паскаля.
Следующие вопросы задаются очень часто, подробно разобраны в FAQ и, поэтому, будут безжалостно удаляться:
1. Преобразовать переменную типа String в тип PChar (PAnsiChar)
2. Как "свернуть" программу в трей.
3. Как "скрыться" от Ctrl + Alt + Del (заблокировать их и т.п.)
4. Как прочитать список файлов, поддиректорий в директории?
5. Как запустить программу/файл?
... (продолжение следует) ...

Вопросы, подробно описанные во встроенной справочной системе Delphi, не несут полезной тематической нагрузки, поэтому будут удаляться.
Запрещается создавать темы с просьбой выполнить какую-то работу за автора темы. Форум является средством общения и общего поиска решения. Вашу работу за Вас никто выполнять не будет.


Внимание
Попытки открытия обсуждений реализации вредоносного ПО, включая различные интерпретации спам-ботов, наказывается предупреждением на 30 дней.
Повторная попытка - 60 дней. Последующие попытки бан.
Мат в разделе - бан на три месяца...
Модераторы: jack128, D[u]fa, Shaggy, Rouse_
Страницы: (4) 1 2 [3] 4  все  ( Перейти к последнему сообщению )  
> Spring4D , Функциональное программирование в действии
    Рефакторим CreateRecipes в примере из поста #16:
    ExpandedWrap disabled
      ...
        type TMapFunc<T> = reference to function (Value: TJSONValue): T;
       
        TRecipesManager = record
          class function LoadFile(fileName: string): string; static;
          class function CreateRecipes(json: string): IList<TRecipe>; static;
          class function CreateIngredients(AValues: TJSONArray): IList<TIngredient>; static;
          class function CreateSteps(AValues: TJSONArray): IList<string>; static;
          class function Map<T>(const AValues: TJSONArray; AFunc: TMapFunc<T>): IList<T>; static;
        end;
       
        TJSONParser = record
          class function GetRoot(json: string): TJSONValue; static;
          class function GetNumber(AValue: TJSONValue; APath: string): integer; static;
          class function GetDouble(AValue: TJSONValue; APath: string): Double; static;
          class function GetString(AValue: TJSONValue; APath: string): string; static;
          class function GetArray(AValue: TJSONValue; APath: string): TJSONArray; static;
        end;
       
      ....
       
      { TJSONParser }
       
      class function TJSONParser.GetArray(AValue: TJSONValue;
        APath: string): TJSONArray;
      begin
        Result:= AValue.GetValue<TJSONArray>(APath);
      end;
       
      class function TJSONParser.GetDouble(AValue: TJSONValue; APath: string): Double;
      begin
        Result:= AValue.GetValue<Double>(APath);
      end;
       
      class function TJSONParser.GetNumber(AValue: TJSONValue;
        APath: string): integer;
      begin
        Result:= AValue.GetValue<integer>(APath);
      end;
       
      class function TJSONParser.GetRoot(json: string): TJSONValue;
      begin
        Result:= TJSONObject.ParseJSONValue(json);
      end;
       
      class function TJSONParser.GetString(AValue: TJSONValue; APath: string): string;
      begin
        Result:= AValue.GetValue<string>(APath);
      end;
       
      { TRecipesManager }
       
      class function TRecipesManager.Map<T>(const AValues: TJSONArray;
        AFunc: TMapFunc<T>): IList<T>;
      begin
        Result:= TCollections.CreateList<T>;
        for var val in AValues do
          Result.Add(AFunc(val));
      end;
       
      class function TRecipesManager.CreateIngredients(
        AValues: TJSONArray): IList<TIngredient>;
      begin
        Result:= TRecipesManager.Map<TIngredient>(AValues,
          function (ingr: TJSONValue): TIngredient
          begin
            Result:= TIngredient.Create(
              TJSONParser.GetString(ingr, 'name'),
              TJSONParser.GetDouble(ingr, 'amount'),
              TJSONParser.GetString(ingr, 'measurement'));
          end);
      end;
       
      class function TRecipesManager.CreateRecipes(json: string): IList<TRecipe>;
      begin
        Result:= TCollections.CreateList<TRecipe>;
        try
          var Root:= TJSONParser.GetRoot(json);
          var Recipes := TJSONParser.GetArray(Root, 'recipes');
          try
            Result:= TRecipesManager.Map<TRecipe>(Recipes,
              function (recipe: TJSONValue): TRecipe
              begin
                var id:= TJSONParser.GetNumber(recipe, 'id');
                var name:= TJSONParser.GetString(recipe, 'name');
                var rating:=  TJSONParser.GetNumber(recipe, 'rating');
                var ingredients:= CreateIngredients(
                                TJSONParser.GetArray(recipe, 'ingredients'));
                var steps:= CreateSteps(TJSONParser.GetArray(recipe, 'steps'));
                Result:= TRecipe.Create(id, name, ingredients, steps, rating);
              end);
          finally
            Recipes.Free;
          end;
        except
          on EJSONException do
            Writeln('Error parsing JSON. Check file format.');
        end;
      end;
       
      class function TRecipesManager.CreateSteps(AValues: TJSONArray): IList<string>;
      begin
        Result:= TRecipesManager.Map<string>(AValues,
          function (step: TJSONValue): string
          begin
            Result:= step.Value;
          end);
      end;
       
      class function TRecipesManager.LoadFile(fileName: string): string;
      begin
        var reader:= TStreamReader.Create(fileName);
        try
          Result:= reader.ReadToEnd;
        finally
          reader.Free;
        end;
      end;
       
       
      begin
        // load json file.
        var s:= TRecipesManager.LoadFile(TPath.Combine(
                        TPath.GetDocumentsPath, 'recipes.json'));
       
        // create recipes list.
        var recipes: IList<TRecipe> := TRecipesManager.CreateRecipes(s);
       
      ....
       
      end.

    - А зачем в CreateRecipes лишний начальный Result:= TCollections.CreateList<TRecipe> получается дважды выделяется память! :huh:
    Это мой стиль программирования! Я не лублу проверять результат операции типо
    if not Assigned(recipes) then Exit на случай ошибки :D
    - Надо думаю лучше все же вставить проверку :huh:
    Ага себе вставь ее :lool:
    Моя стиль прогить с позиции изначально нет ошибок! Я это называю "оптимистиское программирование"! О надо бы зарегить термин за авторством :D
    - Спасибо маста вы гений! :scratch:
    А то :D
    Сообщение отредактировано: Cfon -
      IEnumerable<T>.Max
      ExpandedWrap disabled
        uses
          Spring.Collections;
         
        type
          TPet = record
            Name: string;
            Age: integer;
            constructor Create(AName: string; AAge: integer);
          end;
         
        { TPet }
         
        constructor TPet.Create(AName: string; AAge: integer);
        begin
          Name:= AName; Age:=AAge;
        end;
         
        begin
          var Ints: IList<integer>:= TCollections.CreateList<integer>([1,2,3,4,5]);
          var maxInt:= Ints.Max;
          Writeln(maxInt);
         
          var Pets: IList<TPet>:= TCollections.CreateList<TPet>([
                TPet.Create('Barley', 8),
                TPet.Create('Boots', 4),
                TPet.Create('Whiskers', 1)
          ]);
          
          var max: integer:= Pets.Max(function ( pet: TPet): integer
            begin
              Result:= pet.Age + Length(pet.Name);
            end
          );
          Writeln(max);
         
          Readln;
        end.
        (*C# code
          Pet[] pets = { new Pet { Name="Barley", Age=8 },
                             new Pet { Name="Boots", Age=4 },
                             new Pet { Name="Whiskers", Age=1 } };
         
              int max = pets.Max(pet => pet.Age + pet.Name.Length);
         
              Console.WriteLine(
                  "The maximum pet age plus name length is {0}.",
                  max);
         
          /*
           This code produces the following output:
         
           The maximum pet age plus name length is 14.
          */
        *)

      Вот немного сложнее
      ExpandedWrap disabled
        ...
          TPetComparer = class(TComparer<TPet>)
            function Compare(const Left, Right: TPet): Integer; override;
          end;
         
        { TPetComparer }
         
        function TPetComparer.Compare(const Left, Right: TPet): Integer;
        begin
          var sumLeft:= Left.Age + Length(Left.Name);
          var sumRight:= Right.Age + Length(Right.Name);
         
          if sumLeft < sumRight then
            Result:= -1
          else if sumLeft = sumRight then
            Result:= 0
          else
            Result:= 1;
        end;
         
        begin
          var Pets: IList<TPet>:= TCollections.CreateList<TPet>([
                TPet.Create('Barley', 8),
                TPet.Create('Boots', 4),
                TPet.Create('Whiskers', 1)
          ]);
         
          // comparer:  TComparison<T>
          var max: TPet:= Pets.Max(function (const Left, Right: TPet): integer
            begin
              var sumLeft:= Left.Age + Length(Left.Name);
              var sumRight:= Right.Age + Length(Right.Name);
         
              if sumLeft < sumRight then
                Result:= -1
              else if sumLeft = sumRight then
                Result:= 0
              else
                Result:= 1;
            end
          );
         
          Writeln('The "maximum" animal is ', max.Name);
          // The "maximum" animal is Barley.
         
          // ну или через IComparer<T>
          var comparer: IComparer<TPet>:= TPetComparer.Create;
          var max: TPet:= Pets.Max(comparer);
         
          Writeln('The "maximum" animal is ', max.Name);
          // The "maximum" animal is Barley.  
        end.
        (* C# code
        /// <summary>
          /// This class implements IComparable to be able to
          /// compare one Pet to another Pet.
          /// </summary>
          class Pet : IComparable<Pet>
          {
              public string Name { get; set; }
              public int Age { get; set; }
         
              /// <summary>
              /// Compares this Pet to another Pet by
              /// summing each Pet's age and name length.
              /// </summary>
              /// <param name="other">The Pet to compare this Pet to.</param>
              /// <returns>-1 if this Pet is 'less' than the other Pet,
              /// 0 if they are equal,
              /// or 1 if this Pet is 'greater' than the other Pet.</returns>
              int IComparable<Pet>.CompareTo(Pet other)
              {
                  int sumOther = other.Age + other.Name.Length;
                  int sumThis = this.Age + this.Name.Length;
         
                  if (sumOther > sumThis)
                      return -1;
                  else if (sumOther == sumThis)
                      return 0;
                  else
                      return 1;
              }
          }
         
          public static void MaxEx3()
          {
              Pet[] pets = { new Pet { Name="Barley", Age=8 },
                             new Pet { Name="Boots", Age=4 },
                             new Pet { Name="Whiskers", Age=1 } };
         
              Pet max = pets.Max();
         
              Console.WriteLine(
                  "The 'maximum' animal is {0}.",
                  max.Name);
          }
         
          /*
           This code produces the following output:
         
           The 'maximum' animal is Barley.
          */*)

      Шарповый пример показан на IComparable, с ним Spring4D не работает.

      Добавлено
      Тест произодительности Spring4D vs простой цикл :D
      тестил на массиве из ~2000 слов, Spring4D Distint быстрее обычного цикла :blink:
      В чем я ошибся или у меня массив не правильный? :D
      ExpandedWrap disabled
        uses
          System.Classes, System.SysUtils, System.Diagnostics, System.IOUtils,
          Spring.Collections;
         
        begin
          var s := LoadFile(TPath.Combine(TPath.GetDocumentsPath, 'latin-lipsum.txt'));
          var words := TCollections.CreateList<string>(s.Split([' ']));  
         
          var sw:= TStopwatch.StartNew;
          var distinctWords:= TEnumerable.Distinct<string>(words);
          var arrayWords:= distinctWords.ToArray;
          sw.Stop;
         
          Writeln(Format('Distinct Words: %d, %d ms', [distinctWords.Count, sw.ElapsedMilliseconds]));
        // Distinct Words: 781, 1 ms
         
          var distinctWords2 := TCollections.CreateList<string>;
          sw.Reset;
          sw.Start;
          for var word in words do
          begin
            if distinctWords2.IndexOf(word) = -1 then
              distinctWords2.Add(word);
          end;
          sw.Stop;
          Writeln(Format('Distinct2 Words: %d, %d ms', [distinctWords2.Count, sw.ElapsedMilliseconds]));
        // Distinct Words: 781, 12 ms
        end;
      Сообщение отредактировано: Cfon -
        Цитата Cfon @
        Тест произодительности Spring4D vs простой цикл :D
        тестил на массиве из ~2000 слов, Spring4D Distint быстрее обычного цикла :blink:
        В чем я ошибся или у меня массив не правильный? :D

        Все понел! :D
        Дело в том что Spring Distinct юзает хешсет для создания уникального списка слов, т.е он фастом в один проход создает список, а в примере на цикле идет два цикла, один на добавление другой на поиск совпадения, что естесно ведет к низкой производительности. Как то так :D
          Enumerable<T>.ToLookup весьма интересная функция группировки данных, пришлось снова заюзать Enumerable по причине отсутчвия реализации в TEnumerable :D
          Не самая сложная в переводе, возможно по причине уже набитости руки :blush:
          ExpandedWrap disabled
            uses
              System.SysUtils,
              Spring.Collections,
              Spring.Collections.Enumerable;
             
            type
              TPackage = record
                Company: string;
                Weight: double;
                TrackingNumber: uint64;
                constructor Create(ACompany: string; AWeight: double; ATrackingNumber: uint64);
              end;
             
            { TPackage }
             
            constructor TPackage.Create(ACompany: string; AWeight: double;
              ATrackingNumber: uint64);
            begin
              Company:= ACompany;
              Weight:= AWeight;
              TrackingNumber:= ATrackingNumber;
            end;
             
             
            begin
              // Create a list of Packages.
              var Packages := TCollections.CreateList<TPackage>([
                TPackage.Create('Coho Vineyard', 25.2, 89453312),
                TPackage.Create('Lucerne Publishing', 18.7, 89112755),
                TPackage.Create('Wingtip Toys', 6.0, 299456122),
                TPackage.Create('Contoso Pharmaceuticals', 9.3, 670053128),
                TPackage.Create('Wide World Importers', 33.8, 4665518773)
              ]);
             
              // Create a Lookup to organize the packages.
              // Use the first character of Company as the key value.
              // Select Company appended to TrackingNumber
              // as the element values of the Lookup.
              var lookup := Enumerable<TPackage>.Create(Packages.ToArray)
                .ToLookup<Char, string>(
                  function (p: TPackage): Char
                  begin
                    Result:= p.Company.Substring(0, 1)[1];
                  end,
                  function (p: TPackage): string
                  begin
                    Result:= p.Company + ' ' + p.TrackingNumber.ToString;
                  end
                );
             
                // Iterate through each IGrouping in the Lookup.
                for var packageGroup in lookup do
                begin
                  // Print the key value of the IGrouping.
                  Writeln(packageGroup.Key);
                  // Iterate through each value in the
                  // IGrouping and print its value.
                  for var str in packageGroup do
                    Writeln('    ', str);
                end;
            end;
            (* C# code
            // Create a list of Packages.
                List<Package> packages =
                    new List<Package>
                        { new Package { Company = "Coho Vineyard",
                              Weight = 25.2, TrackingNumber = 89453312L },
                          new Package { Company = "Lucerne Publishing",
                              Weight = 18.7, TrackingNumber = 89112755L },
                          new Package { Company = "Wingtip Toys",
                              Weight = 6.0, TrackingNumber = 299456122L },
                          new Package { Company = "Contoso Pharmaceuticals",
                              Weight = 9.3, TrackingNumber = 670053128L },
                          new Package { Company = "Wide World Importers",
                              Weight = 33.8, TrackingNumber = 4665518773L } };
             
                // Create a Lookup to organize the packages.
                // Use the first character of Company as the key value.
                // Select Company appended to TrackingNumber
                // as the element values of the Lookup.
                ILookup<char, string> lookup =
                    packages
                    .ToLookup(p => Convert.ToChar(p.Company.Substring(0, 1)),
                              p => p.Company + " " + p.TrackingNumber);
             
                // Iterate through each IGrouping in the Lookup.
                foreach (IGrouping<char, string> packageGroup in lookup)
                {
                    // Print the key value of the IGrouping.
                    Console.WriteLine(packageGroup.Key);
                    // Iterate through each value in the
                    // IGrouping and print its value.
                    foreach (string str in packageGroup)
                        Console.WriteLine("    {0}", str);
                }
            }
             
            /*
             This code produces the following output:
             
             C
                 Coho Vineyard 89453312
                 Contoso Pharmaceuticals 670053128
             L
                 Lucerne Publishing 89112755
             W
                 Wingtip Toys 299456122
                 Wide World Importers 4665518773
            */*)
            end.
          Сообщение отредактировано: Cfon -
            Enumerable<T>.ToDictionary - выполняет трансформацию массива в словарь. Тоже полезная вещь, ее кстати можно селектом зафигачить :D
            ExpandedWrap disabled
              uses
                System.SysUtils,
                Spring.Collections,
                Spring.Collections.Enumerable;
               
              type
                TPackage = record
                  Company: string;
                  Weight: double;
                  TrackingNumber: uint64;
                  constructor Create(ACompany: string; AWeight: double; ATrackingNumber: uint64);
                end;
               
              { TPackage }
               
              constructor TPackage.Create(ACompany: string; AWeight: double;
                ATrackingNumber: uint64);
              begin
                Company:= ACompany;
                Weight:= AWeight;
                TrackingNumber:= ATrackingNumber;
              end;
               
              begin
                // Create a list of Packages.
                var Packages := Enumerable<TPackage>.Create([
                  TPackage.Create('Coho Vineyard', 25.2, 89453312),
                  TPackage.Create('Lucerne Publishing', 18.7, 89112755),
                  TPackage.Create('Wingtip Toys', 6.0, 299456122),
                  TPackage.Create('Adventure Works', 33.8, 4665518773)
                ]);
               
                // Create a Dictionary of Package objects,
                // using TrackingNumber as the key.
                var dictionary:= Packages.ToDictionary<UInt64>(
                    function (p: TPackage): UInt64
                    begin
                      Result:= p.TrackingNumber;
                    end
                );
               
                for var kvp in dictionary do
                begin
                  Writeln(Format('Key %d: %s, %f pounds', [
                          kvp.Key, kvp.Value.Company, kvp.Value.Weight]));
                end;
              end.
              (*C# code
                List<Package> packages =
                        new List<Package>
                            { new Package { Company = "Coho Vineyard", Weight = 25.2, TrackingNumber = 89453312L },
                              new Package { Company = "Lucerne Publishing", Weight = 18.7, TrackingNumber = 89112755L },
                              new Package { Company = "Wingtip Toys", Weight = 6.0, TrackingNumber = 299456122L },
                              new Package { Company = "Adventure Works", Weight = 33.8, TrackingNumber = 4665518773L } };
               
                    // Create a Dictionary of Package objects,
                    // using TrackingNumber as the key.
                    Dictionary<long, Package> dictionary =
                        packages.ToDictionary(p => p.TrackingNumber);
               
                    foreach (KeyValuePair<long, Package> kvp in dictionary)
                    {
                        Console.WriteLine(
                            "Key {0}: {1}, {2} pounds",
                            kvp.Key,
                            kvp.Value.Company,
                            kvp.Value.Weight);
                    }
                }
               
                /*
                 This code produces the following output:
               
                 Key 89453312: Coho Vineyard, 25.2 pounds
                 Key 89112755: Lucerne Publishing, 18.7 pounds
                 Key 299456122: Wingtip Toys, 6 pounds
                 Key 4665518773: Adventure Works, 33.8 pounds
                */*)


            Кстати изучая Enumerable<T> обнаружил что у него есть метод Concat так что можно добавить данные и тут, хотя скорее не добавить, а создать новый Enumerable<T>, ибо Concat не изменяет массив как IList.Add:
            ExpandedWrap disabled
              var Packages2: Enumerable<TPackage>;
                Packages2:= Packages.Concat([TPackage.Create('Contoso Pharmaceuticals', 9.3, 670053128)]);

            вот так не пашет, точнее пашет но тип получается IEnumerable<TPackage>:
            ExpandedWrap disabled
              var Packages2:= Packages.Concat([TPackage.Create('Contoso Pharmaceuticals', 9.3, 670053128)]);

            надо кастить
            ExpandedWrap disabled
              var Packages2:= Enumerable<TPackage>(Packages.Concat([TPackage.Create('Contoso Pharmaceuticals', 9.3, 670053128)]));

            ну или так
            ExpandedWrap disabled
              Packages := Packages.Concat([TPackage.Create('Contoso Pharmaceuticals', 9.3, 670053128)]);

            но это не в духе функционального прогинга, но кто запретит :D
            Думаю если это не будет привычкой, то все можно если осторожно :rake:
            Сообщение отредактировано: Cfon -
              еще два вызова ToDictionary, первый в качестве значения добавляет строку, а не запись TPackage, а второй юзает дефолтный сравниватель, что по идее является вариантом первого вызова :D
              ExpandedWrap disabled
                ...
                  // Result: IDictionary<UInt64, STRING>
                  var dictionary := Packages.ToDictionary<UInt64, STRING>(
                      function (p: TPackage): UInt64
                      begin
                        Result:= p.TrackingNumber;
                      end,
                      function (p: TPackage): STRING
                      begin
                        Result:= p.Company;
                      end
                  );
                ....

              ExpandedWrap disabled
                ...
                  // Result: IDictionary<UInt64, TPackage>
                  var dictionary := Packages.ToDictionary<UInt64>(
                      function (p: TPackage): UInt64
                      begin
                        Result:= p.TrackingNumber;
                      end,
                      TEqualityComparer<UInt64>.Default
                  );
                ...


              Добавлено
              Пример со словарем IDictionary<TKey, TValue>
              ExpandedWrap disabled
                uses
                  System.SysUtils,
                  System.Generics.Defaults,
                  Spring.Collections;
                 
                type
                  TBox = class
                    Height: integer;
                    Width: integer;
                    Length: integer;
                    constructor Create(AHeight, ALength, AWidth: integer);
                    function ToString: string; override;
                  end;
                 
                  TBoxEqualityComparer = class(TInterfacedObject, IEqualityComparer<TBox>)
                    function Equals(const Left, Right: TBox): Boolean; reintroduce;
                    function GetHashCode(const Value: TBox): Integer; reintroduce;
                  end;
                 
                  TBoxManager = record
                    class procedure AddBox(dict: IDictionary<TBox, String>; box: TBox; name: string); static;
                  end;
                 
                { TBox }
                 
                constructor TBox.Create(AHeight, ALength, AWidth: integer);
                begin
                  Height := AHeight;
                  Length := ALength;
                  Width := AWidth;
                end;
                 
                function TBox.ToString: string;
                begin
                  Result:= Format('(%d, %d, %d)', [Height, Length, Width]);
                end;
                 
                { TBoxEqualityComparer }
                 
                function TBoxEqualityComparer.Equals(const Left, Right: TBox): Boolean;
                begin
                   if (Left.Height = Right.Height)
                          and (Left.Length = Right.Length)
                          and (Left.Width = Right.Width)  then
                    Result:= True
                  else
                    Result:= False;
                end;
                 
                function TBoxEqualityComparer.GetHashCode(const Value: TBox): Integer;
                begin
                  var hCode := Value.Height xor Value.Length xor Value.Width;
                  Result:= hCode;
                end;
                 
                { TBoxManager }
                 
                class procedure TBoxManager.AddBox(dict: IDictionary<TBox, String>; box: TBox;
                  name: string);
                begin
                  try
                    dict.Add(box, name);
                  except
                    on E: EListError do
                      WriteLn(Format('Unable to add %s: %s', [box.ToString, E.Message]));
                  end;
                end;
                 
                { DictionaryExample }
                 
                procedure DictionaryExample;
                begin
                  var comparer:= TBoxEqualityComparer.Create;
                  var boxes:= TCollections.CreateDictionary<TBox, string>(
                    comparer
                // TEqualityComparer<TBox>.Default
                  );
                 
                  var redBox:= TBox.Create(4,3,4);
                  TBoxManager.AddBox(boxes, redBox, 'red');
                 
                  var blueBox:= TBox.Create(4,3,4);
                  TBoxManager.AddBox(boxes, blueBox, 'blue');
                 
                  var greenBox:= TBox.Create(3,4,3);
                  TBoxManager.AddBox(boxes, greenBox, 'green');
                 
                  Writeln;
                  Writeln(Format('The dictionary contains %d Box objects.', [boxes.Count]));
                end;
                // The example displays the following output:
                //    Unable to add (4, 3, 4): Duplicates not allowed
                //
                //    The dictionary contains 2 Box objects.
                 
                end.

              Тут дефолтный сравнитель не катит, ибо он сравнивает адреса объектов TBox, поэтому пишем кастомный сравнитель.
              Если же заменить класс TBox на запись, то дефотный прокатит.
              Да если скорость добавления объектов в словарь критична, то в TBoxEqualityComparer.Equals можно добавить в начало проверку адресов объектов.
              Думаю на 100 тыщах будет заметен эффект :D
              Сообщение отредактировано: Cfon -
                Цитата Cfon @
                Enumerable<T>.ToDictionary - выполняет трансформацию массива в словарь. Тоже полезная вещь, ее кстати можно селектом зафигачить :D

                ошибочка не селектом, а агригейтом :D
                селект возварщает тот же тип контейнера его нельзя изменить, а вот агригейтом можно что угодно вернуть
                ExpandedWrap disabled
                  // Create a Dictionary of Package objects,
                    // using TrackingNumber as the key.
                    // Result:  IDictionary<Uint64, TPackage>
                    var dictionary:= Packages.Aggregate<IDictionary<Uint64, TPackage>>(
                      TCollections.CreateDictionary<Uint64, TPackage>,
                      function (dict: IDictionary<Uint64, TPackage>; p: TPackage): IDictionary<Uint64, TPackage>
                      begin
                        dict.Add(p.TrackingNumber, p);
                        Result:= dict;
                      end
                    );


                Добавлено
                Пример со словарем функциональном стиле, тут мы определяем объект сразу по месту, а не в виде отдельного класса:
                ExpandedWrap disabled
                  begin
                    // Result: IDictionary<TBox, string>
                    var boxes:= TCollections.CreateDictionary<TBox, string>(
                      TEqualityComparer<TBox>.Construct(
                        function(const Left, Right: TBox): Boolean
                        begin
                          if  Left = Right then
                            Result:= True
                          else if (Left.Height = Right.Height) and
                              (Left.Length = Right.Length) and
                              (Left.Width = Right.Width)  then
                            Result:= True
                          else
                            Result:= False;
                        end,
                        function(const Value: TBox): Integer
                        begin
                          Result:= Value.Height xor Value.Length xor Value.Width;
                        end
                      )
                    );
                   
                    var redBox:= TBox.Create(4,3,4);
                    TBoxManager.AddBox(boxes, redBox, 'red');
                    TBoxManager.AddBox(boxes, redBox, 'red2');
                   
                    var blueBox:= TBox.Create(4,3,4);
                    TBoxManager.AddBox(boxes, blueBox, 'blue');
                   
                    var greenBox:= TBox.Create(3,4,3);
                    TBoxManager.AddBox(boxes, greenBox, 'green');
                   
                    Writeln;
                    Writeln(Format('The dictionary contains %d Box objects.', [boxes.Count]));
                  end;
                  // The example displays the following output:
                  //    Unable to add (4, 3, 4): Duplicates not allowed
                  //    Unable to add (4, 3, 4): Duplicates not allowed
                  //
                  //    The dictionary contains 2 Box objects.

                Замечу что надо отдельно удалять объекты TBox, их словарь не чистит.

                Добавлено
                Цитата Cfon @
                Да если скорость добавления объектов в словарь критична, то в TBoxEqualityComparer.Equals можно добавить в начало проверку адресов объектов.
                Думаю на 100 тыщах будет заметен эффект :D

                Оуоу.. неа, тока если одинаковых объектов будет много, например, 50% :D
                Почему? Потому шо сравнитель запускает проверку на equals тока если хешер вернет одинаковое значение для двух объектов. Поэтому время выполнения вставки данных зависит от скорости хешера, а не от проверки на идентичность.
                Сообщение отредактировано: Cfon -
                  А вот и сюрприз! Чекал OrderBy все путем полет нормальный, далее ThenBy и все тупик :D
                  Короче объясняю OrderBy всем понятно сортировка, а вот ThenBy это дополнение к сортировке заданой OrderBy, т.е. например нам надо сортировать по длине строки и в алфавитном порядке, но в Spring делфи это не пашет! Следущий пример сначало сортирует по длине строки, ну а потом в алфавитном порядке:
                  ExpandedWrap disabled
                    begin
                      var fruits := Enumerable<string>.Create([
                        'grape', 'passionfruit', 'banana', 'mango',
                        'orange', 'raspberry', 'apple', 'blueberry'
                      ]);
                     
                      // Sort the strings first by their length and then
                      //alphabetically by passing the identity selector function.
                      var query := fruits.OrderBy<integer>(
                        function (fruit: string): integer
                        begin
                          Result:= Length(fruit);
                        end
                      );
                     
                      var query2:= Enumerable<string>.Create(query.ToArray)
                        .ThenBy<string>(
                          function (fruit: string): string
                          begin
                            Result:= fruit;
                          end
                        );
                     
                      query2.ForEach(procedure (const fruit: string)
                        begin
                          Writeln(fruit);
                        end
                      );
                      (* у меня output:
                        apple
                        banana
                        blueberry
                        grape
                        mango
                        orange
                        passionfruit
                        raspberry
                      *)
                      (* а надо:
                        apple
                        grape
                        mango
                        banana
                        orange
                        blueberry
                        raspberry
                        passionfruit
                      *)
                    end;
                    (*C# code
                      string[] fruits = { "grape", "passionfruit", "banana", "mango",
                                            "orange", "raspberry", "apple", "blueberry" };
                     
                      // Sort the strings first by their length and then
                      //alphabetically by passing the identity selector function.
                      IEnumerable<string> query =
                          fruits.OrderBy(fruit => fruit.Length).ThenBy(fruit => fruit);
                     
                      foreach (string fruit in query)
                      {
                          Console.WriteLine(fruit);
                      }
                     
                      /*
                          This code produces the following output:
                     
                          apple
                          grape
                          mango
                          banana
                          orange
                          blueberry
                          raspberry
                          passionfruit
                      */*)

                  Я хз зачем тогда ваще нужен ThenBy :wacko:

                  Добавлено
                  Все понел в чем трабла :D
                  Дело в том что в предыдущем примере имел место разрыв цепочки отложенного выполнения и вызов запроса сортировки через query.ToArray, следущий запрос query2 уже выполнял сортировку на уже отсортированых данных.
                  Как я решил траблу? Все гениальное просто :blush: определил функции ThenBy в классе TEnumerableHelper для TEnumerable и юзал их :D
                  ExpandedWrap disabled
                    type
                      TEnumerableHelper = class helper for TEnumerable
                        ...
                     
                        class function ThenBy<T, TKey>(const source: IEnumerable<T>;
                          const keySelector: TFunc<T, TKey>) : IEnumerable<T>; overload; static;
                     
                        class function ThenBy<T, TKey>(const source: IEnumerable<T>;
                          const keySelector: TFunc<T, TKey>;
                          const comparer: IComparer<TKey>): IEnumerable<T>; overload; static;
                      end;
                     
                    { TEnumerableHelper }
                     
                    class function TEnumerableHelper.ThenBy<T, TKey>(const source: IEnumerable<T>;
                      const keySelector: TFunc<T, TKey>;
                      const comparer: IComparer<TKey>): IEnumerable<T>;
                    begin
                    //  Guard.CheckNotNull(Assigned(source), 'source');
                    //  Guard.CheckNotNull(Assigned(keySelector), 'keySelector');
                     
                      Result := TOrderedEnumerable<T, TKey>.Create(source, keySelector, comparer, False);
                    end;
                     
                    class function TEnumerableHelper.ThenBy<T, TKey>(const source: IEnumerable<T>;
                      const keySelector: TFunc<T, TKey>): IEnumerable<T>;
                    begin
                      Result := ThenBy<T, TKey>(source, keySelector, nil);
                    end;

                  Теперь все пучком :dance: :hang:
                  ExpandedWrap disabled
                    begin
                      var fruits := TCollections.CreateList<string>([
                        'grape', 'passionfruit', 'banana', 'mango',
                        'orange', 'raspberry', 'apple', 'blueberry'
                      ]);
                      
                      // Sort the strings first by their length and then
                      //alphabetically by passing the identity selector function.
                      var query := TEnumerable.OrderBy<string, integer>(fruits,
                        function (fruit: string): integer
                        begin
                          Result:= Length(fruit);
                        end
                      );
                     
                      // отложеное выполнение, ибо нет вызова query.ToArray
                      var query2:= TEnumerable.ThenBy<string, string>(query,
                        function (fruit: string): string
                        begin
                          Result:= fruit;
                        end
                      );
                     
                      // а теперь сам запрос
                      query2.ForEach(procedure (const fruit: string)
                        begin
                          Writeln(fruit);
                        end
                      );  
                      (*
                          apple
                          grape
                          mango
                          banana
                          orange
                          blueberry
                          raspberry
                          passionfruit
                      *)
                    end.

                  - Маста так а в чем проблема Enumerable<T>? Почему на TEnumerable пашет то? :huh:
                  Проблема в разрыве цепочки отложеного выполнения, Enumerable<T>.OrderBy возвращает IEnumerable<T> у которого нет ThenBy, но который есть в Enumerable<T>.
                  А чтобы юзать Enumerable<T> надо снова его создать и передать предыдущий запрос, но поскольку в конструктор Enumerable<T> принимает тока динамический массив, а не ссылку на IEnumerable<T>, то надо копировать его через ToArray, что приводит к выполнению запроса и как следствие прерывание отложенного выполнения. Последующий запрос уже будет сортировать другие данные. Поэтому я определил в TEnumerableHelper ThenBy и передавал ему ссылку на IEnumerable<T>, что не прервало отложенного выполнения. :D
                  - Маста вы, вы, вы... ну... просто маста! :jokingly:
                  Думаешь? :D
                  - Определено :jokingly:
                  Ну ок :popcorn:

                  - Маста это снова я! А почему б не определить хелпер для Enumerable<T>? :huh:
                  Оуоу! Не получится Enumerable<T> это генерик, а на генерики хелпер нельзя наложить.
                  - А зачем ваще хелпер для TEnumerable? Можно просто создать функцию ThenBy.. :huh:
                  Ну да можно, но мы ж пишем не гавнокод верно? :D
                  В Spring.Collections уже есть TEnumerable и в нем уже были определния некоторых функций из Enumerable<T>, вот я и добавляю еще через хелпер.
                  Хотя стоп эти функции параметризованы, их нельзя создать вне класса или записи, поэтому надо по любому создавать отдельно класс или запись.

                  ПС. Так я скоро на книжку напечатаю :lool:
                  Сообщение отредактировано: Cfon -
                    ОК. Задание!
                    Надо попрактиковаться на примере, возьмем пример с рецептами.
                    Вывести данные в виде:
                    - название рецепта
                    -- список названий отсортированных инградиентов.
                    Сообщение отредактировано: Cfon -
                      Ответ :D
                      ExpandedWrap disabled
                        type
                          TRecipeNameAndIngredientNames = record
                            RecipeName: string;
                            IngredientNames: IList<string>;
                            constructor Create(ARecipeName: string; AIngredientNames: IList<string>);
                          end;
                         
                        { TRecipeNameAndIngredientNames }
                         
                        constructor TRecipeNameAndIngredientNames.Create(ARecipeName: string;
                          AIngredientNames: IList<string>);
                        begin
                          RecipeName:= ARecipeName;
                          IngredientNames:= AIngredientNames;
                        end;
                         
                        begin
                          // load json file.
                          var s:= TRecipesManager.LoadFile(TPath.Combine(
                                          TPath.GetDocumentsPath, 'recipes.json'));
                         
                          // create recipes list.
                          var recipes: IList<TRecipe> := TRecipesManager.CreateRecipes(s);
                        ....
                         
                          var query := TEnumerable.Select<TRecipe, TRecipeNameAndIngredientNames>(recipes,
                              function (recipe: TRecipe): TRecipeNameAndIngredientNames
                              begin
                                var ingredientNames := TEnumerable.Select<TIngredient, string>(
                                  recipe.Ingredients,
                                  function (ingredient: TIngredient): string
                                  begin
                                    Result:= ingredient.IngredientName;
                                  end
                                );
                                var sorted:= TEnumerable.OrderBy<string, string>(ingredientNames,
                                  function (ingredientName: string): string
                                  begin
                                    Result:= ingredientName;
                                  end
                                );
                         
                                Result:= TRecipeNameAndIngredientNames.Create(recipe.RecipeName, IList<string>(sorted));
                              end
                            );
                         
                          // print the results.
                          query.ForEach(procedure (const value: TRecipeNameAndIngredientNames)
                            begin
                              Writeln(value.RecipeName);
                              value.IngredientNames.ForEach(procedure (const name: string)
                                begin
                                  Writeln('    ', name);
                                end
                              );
                            end
                          );  
                        (*
                         Baked Salmon
                            Butter Lettuce
                            Garlic
                            Olive Oil
                            Pine Nuts
                            Salmon
                            Yellow Squash
                        Fish Tacos
                            Cheese
                            Iceberg Lettuce
                            Tomatoes
                            Tortillas
                            Whitefish
                        Checken Noodle Soup
                            Carrots
                            Celery
                            Chiken Broth
                            Cooked Chicken
                            Extra-virgin olive oil
                            Wide Egg Noodles
                            Yello Onion
                        *)
                        end.

                      У кого возник тихий ужос? Понимаю! :crazy:
                      Тут нужна практика, это в основном из-за избыточного синтаксиса параметров генериков Делфи, в сравнении с JS или тем же C# это мягко говоря неудобно :jokingly:

                      Вот более удобоваримый код, тут просто вывод на консоль без создания списка, генериков тут нет и читать проще :D
                      Точнее генерики то есть параметризованые анонимки, нет угловых скобок :D
                      ExpandedWrap disabled
                        recipes.ForEach(procedure (const recipe: TRecipe)
                          begin
                            Writeln(recipe.RecipeName);
                            recipe.Ingredients.Ordered(
                              function(const Left, Right: TIngredient): Integer
                              begin
                                if Left.IngredientName < Right.IngredientName then
                                  Result:= -1
                                else if Left.IngredientName = Right.IngredientName then
                                  Result:= 0
                                else
                                  Result:= 1;
                              end)
                              .ForEach(procedure (const ingredient: TIngredient)
                              begin
                                Writeln('   ', ingredient.IngredientName);
                              end);
                          end);
                      Сообщение отредактировано: Cfon -
                        Давайте сравним императивный подход с функциональным:
                        ExpandedWrap disabled
                            // Императивный стиль
                            var resultList: TArray<TRecipeNameAndIngredientNames>;
                            for var recipe: TRecipe in recipes do
                            begin
                              var listIngrNames: TArray<string>;
                              for var ingr in recipe.Ingredients do
                              begin
                                listIngrNames:= listIngrNames + [ingr.IngredientName];
                              end;
                              TArray.Sort<string>(listIngrNames);
                              resultList:= resultList + [TRecipeNameAndIngredientNames.Create(recipe.RecipeName, listIngrNames)];
                            end;
                           
                            // Функциональный
                            const resultList = TEnumerable.Select<TRecipe, TRecipeNameAndIngredientNames>(recipes,
                              function (recipe: TRecipe): TRecipeNameAndIngredientNames
                              begin
                                const ingredientNames := TEnumerable.Select<TIngredient, string>(recipe.Ingredients,
                                  function (ingredient: TIngredient): string
                                  begin
                                    Result:= ingredient.IngredientName;
                                  end);
                                const sorted:= TEnumerable.OrderBy<string, string>(ingredientNames,
                                  function (ingredientName: string): string
                                  begin
                                    Result:= ingredientName;
                                  end);
                                Result:= TRecipeNameAndIngredientNames.Create(recipe.RecipeName, IList<string>(sorted));
                              end);

                        Как по мне императивный сложно сразу понять что он делает, особенно без комментов, а вот функциональный читается без коментов. Плас функц. имеет на один меньше цикл, у него цикл сортировки и селекта объединен в силу отложеного выполнения. Т.е. потенционально он будет быстрее при большом числе записей.
                        Кроме того Функц. легко распарралелить, поскольку данные неизменяемы.
                        Плас императивного он немного короче, хотя он короче чем на Делфи, в отношении C# сами видели не короче :D
                        Сообщение отредактировано: Cfon -
                          Немного лекций :writer:
                          Что отличает функциональный код от императивного?
                          - Неизменяемые данные
                          - Чистые функции (идемпотентные)
                          - Функции высшего порядка (преобразование данных, композиция, рекурсия, каррирование).

                          Ну с неизменяемостью данных понятно. В чисто функц. языках они не меняются, но делфи не является чисто функциональным языком, он просто поддерживает некоторые его концепции, но только не консистентность данных. Данные в делфи можно легко случайно изменить, поэтому надо быть внимтельным, хотя можно юзать везде константы это защитит от случайного изменения данных :D

                          - Маста я не пойму, как прогать если данные не меняются? :wacko:
                          Новичок, данные меняются, но не изменяются :blink: :lool:
                          - Это как? :huh:
                          Если мы объявим переменную, то сразу записываем туда значение, потом его не изменяем. Если мы хотим изменить эти данные, то просто передаем его преобразователю данных.
                          - Это что за хрень? :jokingly:
                          Так называют функции высшего порядка :punish:
                          Например нам надо изменить массив строк, что мы делаем мы передаем его в функцию map или reduce в качестве источника истины :D, а также передаем вторым параметром функцию первого класса, которая собственно преобразует данные, далее результат возвращается и присваивается другой переменной все епт :D
                          Другими словами исходные данные не меняются, создается новые измененые данные.
                          - Маста а можно все тоже, но на примере? :scratch:
                          Угу ща Новичок сообразимс! :D
                          - Подождем
                          Уфф вот написал, тут я юзанул Select из Spring это аналог map:
                          ExpandedWrap disabled
                            begin
                              var strs := TCollections.CreateList<string>([
                                'grape', 'passionfruit', 'banana', 'mango',
                                'orange', 'raspberry', 'apple', 'blueberry'
                              ]);
                             
                              var ints:= TEnumerable.Select<string, integer>(strs,
                                function (s: string): integer
                                begin
                                  Result:= Length(s);
                                end
                              );
                             
                              // print
                              for var i in ints do
                              begin
                                Write(i, ', ');
                              end;
                              // 5, 12, 6, 5, 6, 9, 5, 9,
                             
                               Writeln;
                             
                              for var s in strs do
                              begin
                                Write(s, ', ');
                              end;
                              // grape, passionfruit, banana, mango, orange, raspberry, apple, blueberry,
                            end.

                          Как видишь имеем два массива с разными данными.
                          - Маста а что надо всегда менять тип данных? :huh:
                          Нет конешно можно не менять, например если нам надо удалить слово "банан", то пишем следущее:
                          ExpandedWrap disabled
                            var deletedBananas:= strs.Where(
                                function (const s: string): Boolean
                                begin
                                  if s = 'banana' then
                                    Result:= False
                                  else
                                    Result:= True;
                                end
                              );
                             
                              // print
                              for var s in deletedBananas do
                              begin
                                Write(s, ', ');
                              end;
                              // grape, passionfruit, mango, orange, raspberry, apple, blueberry,

                          Тут я заюзал функцию Where тоже и Spring4D это аналог filter.
                          - Ого начинаю понимать, а если нам надо удалить не банан, а passionfruit :D
                          - Че нам опять все писать Where? :huh:
                          Зачем? Надо определить функцию и передавать туда значение которое надо удалить! Давай напиши ее а я заценю.
                          Сообщение отредактировано: Cfon -
                            - Маста вот что у меня получилось, я заюзал анонимку для простоты, все как вы учили, есть вопрос
                            ExpandedWrap disabled
                              var Deleter := function (AValue: string): //<-- что возвращать???
                                  begin
                                    Result:= strs.Where(
                                      function (const s: string): Boolean
                                      begin
                                        Result:= not (s = AValue);
                                      end
                                    );
                                  end;

                            Какой?
                            - Что возвращать то? :huh:
                            Хороший вопрос :D
                            Возвращать можно что угодно, главное вернуть! :D это один из принципов функционального программирования. Функции принимают что-то и возвращают что-то. Только тогда они могут быть чистыми.
                            Ок, коль скоро мы программируем в Spring4D, то вернуть надо интерфейс IEnumerable<T>. В твоем случае IEnumerable<string>, поскольку именно его возвращает Where видишь ты ее вызов присвоил Result? Вот поэтому возвращаем IEnumerable<string>.
                            - Е-е все работает!
                            ExpandedWrap disabled
                              var deletedBananas:= Deleter('banana');

                            Добже Новичок, а теперь давай сделаем еще один шаг и унифицируем нашу функцию. Ну т.е. сделаем из нее дженерик, чтобы можно было вызвать на любых данных. Сможешь? :)
                            - Попробую :huh:

                            Добавлено
                            - Вот че получилось, ругается :huh:
                            ExpandedWrap disabled
                              type
                                TExample = record
                                  class function Delete<T>(ASource: IEnumerable<T>; AValue: T): IEnumerable<T>; static;
                                end;
                               
                              class function TExample.Delete<T>(ASource: IEnumerable<T>; AValue: T): IEnumerable<T>;
                              begin
                                Result := ASource.Where(
                                  function (const s: T): Boolean
                                  begin
                                    if s = AValue then //<-- тут ошибка "E2015 Operator not applicable to this operand type"
                                      Result:= False
                                    else
                                      Result:= True;
                                  end
                                );
                              end;

                            Новичок красавчек! Ну ты уже не новичок раз такое написал! :D
                            На счет ошибки все верно там у тебя идет проверка на равенство, компилятору неизвестно какого типа является T и как их сравнивать.
                            - А че делать? :huh:
                            Юзать дефотный компарер
                            ExpandedWrap disabled
                              class function TExample.Delete<T>(ASource: IEnumerable<T>; AValue: T): IEnumerable<T>;
                              begin
                                Result := ASource.Where(
                                  function (const ALeft: T): Boolean
                                  begin
                                    if TComparer<T>.Default.Compare(ALeft, AValue) = 0 then
                                      Result:= False
                                    else
                                      Result:= True;
                                  end
                                );
                              end;

                            Начинку Default не иследовал, оставляю тебе для самостоятельного изучения :D
                            - Ух ты! Работает! :good:

                            - Маста функционнально прогать конешно прикольно, но память то не резиновая, это сколько же объектов будет в итоге если их все копировать и менять :wacko:
                            Новичок ты меня удивляешь, ты что ли их копируешь? Тебе че больше всех надо? :lool:
                            - :huh:
                            Лана я пошутил :jokingly:
                            Все дело в том что объекты не копятся не нужные объекты сразу или почти сразу удаляются! Вот к примеру наш код, тут strs и deletedBananas будут удалены, как только ссылки на них буду равны nil или они выйдут из области видимости.
                            Сообщение отредактировано: Cfon -
                              - Маста, а как насчет времени затраченое на копирование данных? :huh:
                              Ну тут тоже есть свои секреты :D
                              Как в Делфи не знаю точно, но например в Хаскеле данные, если к примеру брать два списка, не копируются, а создаются ссылки на одни и те же данные. Само копирование происходит тока тогда когда данные изменяются, вот тогда создается копия и затем эта копия изменяется. Так что никаких лишних копий нет. Заметь копия не всех данных, а ток тех что надо изменить.
                              Надо почитать про то как Делфи управляет памятью. Думаю что делфиразрабы тоже это учитывают ибо не просто так они добавили функциональные возможности типо ананимок, вывод типов, паралельное програмирование и тд.
                              - Ясно почитаю, но в Хаскеле хитро придумано :)
                              Ну как бэ не пальцем деланый язык то :D
                              да и к слову сразу про рекурсии в Хаскеле они в итоге преобразуются на уровне бинарного кода в обычные циклы :D так что у тут нет замедления от их юзания.
                              Ну естестно поэтому компилятор Хаскеля на порядок медленнее например чем в Делфи :D
                              В Делфи как я вижу походу самый быстрый компилятор из тех что я видел.
                              Сообщение отредактировано: Cfon -
                                - Маста, а если дефолтное сравнение не катит, что делать? :huh:
                                Тогда надо вынести логику сравнения наружу и дать возможность самим определить как сравнивать объекты :)
                                - А пример можно?
                                Ок, смотри я немного изменил твое определение дженерика
                                ExpandedWrap disabled
                                  class function TExample.Delete<T>(ASource: IEnumerable<T>; AValue: T;
                                    const AComparison: TEqualityComparison<T>): IEnumerable<T>;
                                  begin
                                    Result := ASource.Where(
                                      function (const ALeft: T): Boolean
                                      begin
                                        Result:= AComparison(ALeft, AValue); //<-- тут наша логика вызывается
                                      end
                                    );
                                  end;
                                   
                                  var deletedBananas:= TExample.Delete<string>(strs, 'banana',
                                      function (const Left, Right: string): integer //<-- тут определяем логику через анонимку
                                      begin
                                        Result:= not (Left = Right);
                                      end
                                    );

                                - Вау! Маста вы вы вы..
                                Что? Гений? :popcorn:
                                - Да однозначно :jokingly:
                                Сообщение отредактировано: Cfon -
                                0 пользователей читают эту тему (0 гостей и 0 скрытых пользователей)
                                0 пользователей:
                                Страницы: (4) 1 2 [3] 4  все


                                Рейтинг@Mail.ru
                                [ Script execution time: 0,0768 ]   [ 17 queries used ]   [ Generated: 19.04.24, 19:11 GMT ]