(495) 925-0049, ITShop интернет-магазин 229-0436, Учебный Центр 925-0049
  Главная страница Карта сайта Контакты
Поиск
Вход
Регистрация
Рассылки сайта
 
 
 
 
 

Использование анонимных методов в Delphi

Источник: Блог компании Embarcadero

uses SysUtils, Math; type TArrayHelper = record helper for TArray<Double> strict private type TForEachRef = reference to procedure(X: Double; I: Integer; var Done: Boolean); TMapRef = reference to function(X: Double): Double; TFilterRef = reference to function(X: Double; I: Integer): Boolean; TPredicateRef = reference to function(X: Double): Boolean; TReduceRef = reference to function(Accumulator, X: Double): Double; public function ToString: string; procedure ForEach(Lambda: TForEachRef); function Map(Lambda: TMapRef): TArray<Double>; function Filter(Lambda: TFilterRef): TArray<Double>; function Every(Lambda: TPredicateRef): Boolean; function Some(Lambda: TPredicateRef): Boolean; function Reduce(Lambda: TReduceRef): Double; overload; function Reduce(Init: Double; Lambda: TReduceRef): Double; overload; function ReduceRight(Lambda: TReduceRef): Double; end;
Большинство описываемых ниже методов принимают функцию в качестве аргумента и вызывают ее для каждого элемента (или нескольких элементов) массива. В большинстве случаев указанной функции передается один аргумент: значение элемента массива. Возможны более продвинутые реализации, в которых передается не только значение, но также индекс элемента и ссылка на сам массив. Ни один из методов не изменяет исходный массив, однако функция, передаваемая этим методам, может это делать.

Метод ForEach

Метод ForEach выполняет обход элементов массива и для каждого из них вызывает указанную функцию. Как уже говорилось выше, функция передается методу ForEach в аргументе. При вызове этой функции метод ForEach будет передавать ей значение элемента массива, его индекс, а также булеву переменную Done, присвоение True которой позволит прервать итерации и выйти из метода (аналог инструкции Break для обычного цикла for). Например:

var A: TArray<Double>; begin A := [1, 2, 3]; // Использование литералов массивов стало возможным в XE7 // Умножить все элементы массива на 2 A.ForEach(procedure(X: Double; I: Integer; var Done: Boolean) begin A[I] := X * 2; if I = 1 then Done := True; // Досрочный выход из ForEach end); WriteLn(A.ToString); // => [2, 4, 3] end;
Реализация метода ForEach:
procedure TArrayHelper.ForEach(Lambda: TForEachRef); var I: Integer; Done: Boolean; begin Done := False; for I := 0 to High(Self) do begin Lambda(Self[I], I, Done); if Done then Break; end; end; // Вспомогательный метод: преобразование массива в строку function TArrayHelper.ToString: string; var Res: TArray<string>; begin if Length(Self) = 0 then Exit('[]'); ForEach(procedure(X: Double; I: Integer; var Done: Boolean) begin Res := Res + [FloatToStr(X)]; end); Result := '[' + string.Join(', ', Res) + ']'; end;

Метод Map

Метод Map передает указанной функции каждый элемент массива, относительно которого он вызван, и возвращает массив значений, возвращаемых этой функцией. Например:

var A, R: TArray<Double>; begin A := [1, 2, 3]; // Вычислить квадраты всех элементов R := A.Map(function(X: Double): Double begin Result := X * X; end); WriteLn(R.ToString); // => [1, 4, 9] end;
Метод Map вызывает функцию точно так же, как и метод ForEach. Однако функция, передаваемая методу Map, должна возвращать значение. Обратите внимание, что Map возвращает новый массив: он не изменяет исходный массив.

Реализация метода Map:
function TArrayHelper.Map(Lambda: TMapRef): TArray<Double>; var X: Double; begin for X in Self do Result := Result + [Lambda(X)]; end;

Метод Filter

Метод Filter возвращает массив, содержащий подмножество элементов исходного массива. Передаваемая ему функция должна быть функцией-предикатом, т.к. должна возвращать значение True или False. Метод Filter вызывает функцию точно так же, как методы ForEach и Map. Если возвращается True, переданный функции элемент считается членом подмножества и добавляется в массив, возвращаемый методом. Например:

var Data: TArray<Double>; MidValues: TArray<Double>; begin Data := [5, 4, 3, 2, 1]; // Фильтровать элементы, большме 1, но меньшие 5 MidValues := Data.Filter(function(X: Double; I: Integer): Boolean begin Result := (1 < X) and (X < 5); end); WriteLn(MidValues.ToString); // => [4, 3, 2] // Каскад Data .Map(function(X: Double): Double begin Result := X + 5; // Увеличить каждый элемент на 5. end) .Filter(function(X: Double; I: Integer): Boolean begin Result := (I mod 2 = 0); // Фильтровать элементы с четными номерами end) .ForEach(procedure(X: Double; I: Integer; var Done: Boolean) begin Write(X:2:0) // => 10 8 6 end); end;
Реализация метода Filter:
function TArrayHelper.Filter(Lambda: TFilterRef): TArray<Double>; var I: Integer; begin for I := 0 to High(Self) do if Lambda(Self[I], I) then Result := Result + [Self[I]]; end;

Методы Every и Some

Методы Every и Some являются предикатами массива: они применяют указанную функцию-предикат к элементам массива и возвращают True или False. Метод Every напоминает математический квантор всеобщности ∀: он возвращает True, только если переданная Вами функция-предикат вернула True для всех элементов массива:

var A: TArray<Double>; B: Boolean; begin A := [1, 2.7, 3, 4, 5]; B := A.Every(function(X: Double): Boolean begin Result := (X < 10); end); WriteLn(B); // => True: все значения < 10. B := A.Every(function(X: Double): Boolean begin Result := (Frac(X) = 0); end); WriteLn(B); // => False: имеются числа с дробной частью. end;
Метод Some напоминает математический квантор существования ∃: он возвращает True, если в массиве имеется хотя бы один элемент, для которого функция-предикат вернет True, а значение False возвращается методом, только если функция-предикат вернет False для всех элементов массива:

var A: TArray<Double>; B: Boolean; begin A := [1, 2.7, 3, 4, 5]; B := A.Some(function(X: Double): Boolean begin Result := (Frac(X) = 0); end); WriteLn(B); // => True: имеются числа без дробной части. end;
Реализация методов Every и Some:
function TArrayHelper.Every(Lambda: TPredicateRef): Boolean; var X: Double; begin Result := True; for X in Self do if not Lambda(X) then Exit(False); end; function TArrayHelper.Some(Lambda: TPredicateRef): Boolean; var X: Double; begin Result := False; for X in Self do if Lambda(X) then Exit(True); end;
Обратите внимание, что оба метода, Every и Some, прекращают обход элементов массива, как только результат становится известен. Метод Some возвращает True, как только функция-предикат вернет True, и выполнит обход всех элементов массива, только если функция-предикат всегда возвращает False. Метод Every является полно противоположностью: он возвращает False, как только функция-предикат вернет False, и выполняет обход всех элементов массива, только если функция-предикат всегда возвращает True. Кроме того, отметьте, что в соответствии с правилами математики для пустого массива метод Every возвращает True, а метод Some возвращает False.

Методы Reduce и ReduceRight

Методы Reduce и ReduceRight объединяют элементы массива, используя указанную Вами функцию, и возвращают единственное значение. Это типичная операция в функциональном программировании, где она известна также под названием "свертка". Примеры ниже помогут понять суть этой операции:

var A: TArray<Double>; Total, Product, Max: Double; begin A := [1, 2, 3, 4, 5]; // Сумма значений Total := A.Reduce(0, function(X, Y: Double): Double begin Result := X + Y; end); WriteLn(Total); // => 15.0 // Произведение значений Product := A.Reduce(1, function(X, Y: Double): Double begin Result := X * Y; end); WriteLn(Product); // => 120.0 // Наибольшее значение (используется альтернативная реализация Reduce) Max := A.Reduce(function(X, Y: Double): Double begin if X > Y then Exit(X) else Exit(Y); end); WriteLn(Max); // => 5.0 end;
Метод Reduce принимает два аргумента. Во втором передается функция, которая выполняет операцию свертки. Задача этой функции - объединить некоторым способом или свернуть два значения в одно вернуть свернутое значение. В примерах выше функции выполняют объединение двух значений, складывая их, умножая и выбирая наибольшее. В первом аргументе передается начальное значение для функции.

Функции, передаваемые методу Reduce, отличаются от функций, передаваемых методам ForEach и Map. Значение элемента массива передается им во втором аргументе, а в первом аргументе передается накопленный результат свертки. При первом вызове в первом аргументе функции передается начальное значение, переданное методу Reduce в первом аргументе. Во всех последующих вызовах передается значение, полученное в результате предыдущего вызова функции. В первом примере, из приведенных выше, функция свертки сначала будет вызвана с аргументами 0 и 1. Она сложит эти числа и вернет 1. Затем она будет вызвана с аргументами 1 и 2 и вернет 3. Затем она вычислит 3 + 3 = 6, затем 6 + 4 = 10 и, наконец, 10 + 5 = 15. Это последнее значение 15 будет возвращено методом Reduce.

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

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

Реализация методов Reduce:
function TArrayHelper.Reduce(Init: Double; Lambda: TReduceRef): Double; var I: Integer; begin Result := Init; if Length(Self) = 0 then Exit; for I := 0 to High(Self) do Result := Lambda(Result, Self[I]); end; // Альтернативная реализация Reduce - с одним аргументом function TArrayHelper.Reduce(Lambda: TReduceRef): Double; var I: Integer; begin Result := Self[0]; if Length(Self) = 1 then Exit; for I := 1 to High(Self) do Result := Lambda(Result, Self[I]); end;
Метод ReduceRight действует точно так же, как и метод Reduce, за исключением того, что массив обрабатывается в обратном порядке, от больших индексов к меньшим (справа налево). Это может потребоваться, если операция свертки имеет ассоциативность справа налево, например:

var A: TArray<Double>; Big: Double; begin A := [2, 3, 4]; // Вычислить 2^(3^4). // Операция возведения в степень имеет ассоциативность справа налево Big := A.ReduceRight(function(Accumulator, Value: Double): Double begin Result := Math.Power(Value, Accumulator); end); Writeln(Big); // => 2.41785163922926E+0024 end;
Реализация метода ReduceRight:
function TArrayHelper.ReduceRight(Lambda: TReduceRef): Double; var I: Integer; begin Result := Self[High(Self)]; if Length(Self) = 1 then Exit; for I := High(Self) - 1 downto 0 do Result := Lambda(Result, Self[I]); end;
Следует отметить, что методы Every и Some, описанные выше, являются своеобразной разновидностью операции свертки массива. Однако они отличаются тем, что стремятся завершить обход массива как можно раньше и не всегда проверяют значения всех его элементов.

Вместо заключения

Рассмотрим еще один пример использования анонимных методов. Пусть у нас имеется массив чисел и нам необходимо найти среднее значение и стандартное отклонение для этих значений:

// Вспомогательная функция: вычисление суммы аргументов. // Свободную функцию (как и метод экземпляра) можно использовать // в качестве параметра для метода, принимающего reference-тип function Sum(X, Y: Double): Double; begin Result := X + Y; end; // Вычисление среднего значения (Mean) и СКО (StdDev). procedure MeanAndStdDev; var Data: TArray<Double>; Mean, StdDev: Double; begin Data := [1, 1, 3, 5, 5]; Mean := Data.Reduce(Sum) / Length(Data); StdDev := Sqrt(Data .Map(function(V: Double): Double begin Result := Sqr(V - Mean); // Квадраты разностей end) .Reduce(Sum) / Pred(Length(Data))); WriteLn('Mean: ', Mean, ' StdDev: ', StdDev); // => Mean: 3.0 StdDev: 2.0 end;
Исходники

Интернет-магазин


 Распечатать »
 Правила публикации »
  Написать редактору 
 Рекомендовать » Дата публикации: 14.12.2014 
 

Магазин программного обеспечения   WWW.ITSHOP.RU
Enterprise Connectors (1 Year term)
Delphi Professional Named User
TeeBI for RAD Studio Suite with source code single license
Rational ClearQuest Floating User License
erwin Data Modeler Navigator Edition r9.7 - Product plus 1 Year Enterprise Maintenance Commercial
 
Другие предложения...
 
Курсы обучения   WWW.ITSHOP.RU
 
Другие предложения...
 
Магазин сертификационных экзаменов   WWW.ITSHOP.RU
 
Другие предложения...
 
3D Принтеры | 3D Печать   WWW.ITSHOP.RU
 
Другие предложения...
 
Новости по теме
 
Рассылки Subscribe.ru
Информационные технологии: CASE, RAD, ERP, OLAP
Новости ITShop.ru - ПО, книги, документация, курсы обучения
Программирование на Microsoft Access
CASE-технологии
СУБД Oracle "с нуля"
Новые программы для Windows
Corel DRAW - от идеи до реализации
 
Статьи по теме
 
Новинки каталога Download
 
Исходники
 
Документация
 
 



    
rambler's top100 Rambler's Top100