Ast RTL: Map, Reduce, Where, Any
This commit is contained in:
+140
-50
@@ -17,55 +17,8 @@ uses
|
||||
|
||||
implementation
|
||||
|
||||
type
|
||||
// Implements a lazily evaluated, read-only series based on a source series and a mapper function.
|
||||
TMapSeries = class(TInterfacedObject, ISeries)
|
||||
private
|
||||
FSourceSeries: ISeries;
|
||||
FMapperFunc: TDataValue.TFunc;
|
||||
// ISeries
|
||||
function GetCount: Int64;
|
||||
function GetTotalCount: Int64;
|
||||
function GetItems(Idx: Integer): TScalar;
|
||||
public
|
||||
constructor Create(const ASourceSeries: ISeries; const AMapperFunc: TDataValue.TFunc);
|
||||
end;
|
||||
|
||||
{ TMapSeries }
|
||||
|
||||
constructor TMapSeries.Create(const ASourceSeries: ISeries; const AMapperFunc: TDataValue.TFunc);
|
||||
begin
|
||||
inherited Create;
|
||||
FSourceSeries := ASourceSeries;
|
||||
FMapperFunc := AMapperFunc;
|
||||
end;
|
||||
|
||||
function TMapSeries.GetCount: Int64;
|
||||
begin
|
||||
Result := FSourceSeries.Count;
|
||||
end;
|
||||
|
||||
function TMapSeries.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FSourceSeries.TotalCount;
|
||||
end;
|
||||
|
||||
function TMapSeries.GetItems(Idx: Integer): TScalar;
|
||||
var
|
||||
sourceValue: TScalar;
|
||||
argArray: TArray<TDataValue>;
|
||||
mappedValue: TDataValue;
|
||||
begin
|
||||
// Get the original item, apply the mapper function, and return the result.
|
||||
sourceValue := FSourceSeries.Items[Idx];
|
||||
argArray := [TDataValue(sourceValue)];
|
||||
mappedValue := FMapperFunc(argArray);
|
||||
|
||||
if mappedValue.Kind <> vkScalar then
|
||||
raise EInvalidCast.Create('Map function did not return a scalar value.');
|
||||
|
||||
Result := mappedValue.AsScalar;
|
||||
end;
|
||||
uses
|
||||
System.Generics.Collections;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
//== Native Function Implementations
|
||||
@@ -179,7 +132,141 @@ begin
|
||||
if Args[1].Kind <> vkMethod then
|
||||
raise EArgumentException.Create('The second argument to Map must be a function.');
|
||||
|
||||
Result := TDataValue.FromSeries(TMapSeries.Create(sourceArg.AsSeries, Args[1].AsMethod()));
|
||||
Result := TDataValue.FromSeries(TDataValue.Map(sourceArg.AsSeries, Args[1].AsMethod()));
|
||||
end;
|
||||
|
||||
// Reduces a series into a single value.
|
||||
function NativeReduce(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
sourceArg: TDataValue;
|
||||
sourceSeries: ISeries;
|
||||
accumulator: TDataValue;
|
||||
reducerFunc: TDataValue.TFunc;
|
||||
i: Integer;
|
||||
currentItem: TScalar;
|
||||
reducerArgs: TArray<TDataValue>;
|
||||
begin
|
||||
if Length(Args) <> 3 then
|
||||
raise EArgumentException.Create('Reduce requires exactly three arguments: a series, an initial value, and a reducer function.');
|
||||
|
||||
sourceArg := Args[0];
|
||||
if sourceArg.Kind <> vkSeries then
|
||||
raise EArgumentException.Create('The first argument to Reduce must be a series.');
|
||||
|
||||
if Args[2].Kind <> vkMethod then
|
||||
raise EArgumentException.Create('The third argument to Reduce must be a function.');
|
||||
|
||||
// Extract arguments
|
||||
sourceSeries := sourceArg.AsSeries;
|
||||
accumulator := Args[1];
|
||||
reducerFunc := Args[2].AsMethod();
|
||||
|
||||
// Iterate from oldest to newest element (index Count-1 to 0)
|
||||
for i := sourceSeries.Count - 1 downto 0 do
|
||||
begin
|
||||
currentItem := sourceSeries.Items[i];
|
||||
reducerArgs := [accumulator, TDataValue(currentItem)];
|
||||
accumulator := reducerFunc(reducerArgs);
|
||||
end;
|
||||
|
||||
Result := accumulator;
|
||||
end;
|
||||
|
||||
// Filters a series based on a predicate.
|
||||
function NativeWhere(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
sourceArg: TDataValue;
|
||||
sourceSeries: ISeries;
|
||||
predicateFunc: TDataValue.TFunc;
|
||||
matchingIndices: TList<Integer>;
|
||||
i: Integer;
|
||||
item: TScalar;
|
||||
predicateResult: TDataValue;
|
||||
indexSeries: ISeries;
|
||||
mapperFunc: TDataValue.TFunc;
|
||||
finalSeries: ISeries;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Where requires exactly two arguments: a series and a predicate function.');
|
||||
|
||||
sourceArg := Args[0];
|
||||
if sourceArg.Kind <> vkSeries then
|
||||
raise EArgumentException.Create('The first argument to Where must be a series.');
|
||||
|
||||
if Args[1].Kind <> vkMethod then
|
||||
raise EArgumentException.Create('The second argument to Where must be a function.');
|
||||
|
||||
sourceSeries := sourceArg.AsSeries;
|
||||
predicateFunc := Args[1].AsMethod();
|
||||
|
||||
// 1. Eagerly build the index of matching items
|
||||
matchingIndices := TList<Integer>.Create;
|
||||
try
|
||||
for i := sourceSeries.Count - 1 downto 0 do
|
||||
begin
|
||||
item := sourceSeries.Items[i];
|
||||
predicateResult := predicateFunc([TDataValue(item)]);
|
||||
if (predicateResult.Kind = vkScalar) and (predicateResult.AsScalar.Value.AsBoolean) then
|
||||
matchingIndices.Add(i);
|
||||
end;
|
||||
|
||||
// 2. Create a helper series from the index
|
||||
indexSeries := TIndexSeries.Create(matchingIndices.ToArray);
|
||||
finally
|
||||
matchingIndices.Free;
|
||||
end;
|
||||
|
||||
// 3. Define a mapper to transform an index back to a value from the original series
|
||||
mapperFunc :=
|
||||
function(const AArgs: TArray<TDataValue>): TDataValue
|
||||
var
|
||||
idx: Integer;
|
||||
begin
|
||||
idx := AArgs[0].AsScalar.Value.AsInteger;
|
||||
Result := TDataValue(sourceSeries.Items[idx]);
|
||||
end;
|
||||
|
||||
// 4. Use TMapSeries to create the final, filtered series
|
||||
finalSeries := TDataValue.Map(indexSeries, mapperFunc);
|
||||
Result := TDataValue.FromSeries(finalSeries);
|
||||
end;
|
||||
|
||||
// Checks if any element in a series satisfies a predicate.
|
||||
function NativeAny(const Args: TArray<TDataValue>): TDataValue;
|
||||
var
|
||||
sourceArg: TDataValue;
|
||||
sourceSeries: ISeries;
|
||||
predicateFunc: TDataValue.TFunc;
|
||||
i: Integer;
|
||||
item: TScalar;
|
||||
predicateResult: TDataValue;
|
||||
begin
|
||||
if Length(Args) <> 2 then
|
||||
raise EArgumentException.Create('Any requires exactly two arguments: a series and a predicate function.');
|
||||
|
||||
sourceArg := Args[0];
|
||||
if sourceArg.Kind <> vkSeries then
|
||||
raise EArgumentException.Create('The first argument to Any must be a series.');
|
||||
|
||||
if Args[1].Kind <> vkMethod then
|
||||
raise EArgumentException.Create('The second argument to Any must be a function.');
|
||||
|
||||
sourceSeries := sourceArg.AsSeries;
|
||||
predicateFunc := Args[1].AsMethod();
|
||||
|
||||
for i := 0 to sourceSeries.Count - 1 do
|
||||
begin
|
||||
item := sourceSeries.Items[i];
|
||||
predicateResult := predicateFunc([TDataValue(item)]);
|
||||
|
||||
if (predicateResult.Kind = vkScalar) and (predicateResult.AsScalar.Value.AsBoolean) then
|
||||
begin
|
||||
Result := TScalar.FromBoolean(True);
|
||||
exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
Result := TScalar.FromBoolean(False);
|
||||
end;
|
||||
|
||||
//--------------------------------------------------------------------------------------------------
|
||||
@@ -194,6 +281,9 @@ begin
|
||||
AScope.Define('Floor', TDataValue(NativeFloor));
|
||||
AScope.Define('Sign', TDataValue(NativeSign));
|
||||
AScope.Define('Map', TDataValue(NativeMap));
|
||||
AScope.Define('Reduce', TDataValue(NativeReduce));
|
||||
AScope.Define('Where', TDataValue(NativeWhere));
|
||||
AScope.Define('Any', TDataValue(NativeAny));
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
||||
@@ -254,6 +254,16 @@ type
|
||||
property TotalCount: Int64 read GetTotalCount;
|
||||
end;
|
||||
|
||||
TIndexSeries = class(TInterfacedObject, ISeries)
|
||||
private
|
||||
FIndexArray: TArray<Integer>;
|
||||
function GetCount: Int64;
|
||||
function GetTotalCount: Int64;
|
||||
function GetItems(Idx: Integer): TScalar;
|
||||
public
|
||||
constructor Create(const AIndexArray: TArray<Integer>);
|
||||
end;
|
||||
|
||||
TBinaryOperatorHelper = record helper for TBinaryOperator
|
||||
function ToString: string;
|
||||
end;
|
||||
@@ -1557,6 +1567,34 @@ begin
|
||||
Result := FTotalCount;
|
||||
end;
|
||||
|
||||
{ TIndexSeries }
|
||||
|
||||
constructor TIndexSeries.Create(const AIndexArray: TArray<Integer>);
|
||||
begin
|
||||
inherited Create;
|
||||
FIndexArray := AIndexArray;
|
||||
end;
|
||||
|
||||
function TIndexSeries.GetCount: Int64;
|
||||
begin
|
||||
Result := Length(FIndexArray);
|
||||
end;
|
||||
|
||||
function TIndexSeries.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := GetCount;
|
||||
end;
|
||||
|
||||
function TIndexSeries.GetItems(Idx: Integer): TScalar;
|
||||
var
|
||||
sourceIndex: Integer;
|
||||
begin
|
||||
// The series maintains the "0 is newest" convention.
|
||||
// The index array was built from oldest to newest, so we access it in reverse.
|
||||
sourceIndex := FIndexArray[GetCount - 1 - Idx];
|
||||
Result := TScalar.FromInteger(sourceIndex);
|
||||
end;
|
||||
|
||||
initialization
|
||||
Assert(sizeof(TScalarValue) = 8);
|
||||
|
||||
|
||||
@@ -63,6 +63,8 @@ type
|
||||
|
||||
class function Defer(const Value: TDataValue; const Calc: TFunc): TDataValue; overload; static;
|
||||
|
||||
class function Map(const SourceSeries: ISeries; const MapperFunc: TFunc): ISeries; static;
|
||||
|
||||
class function FromRecordSeries(const AValue: IRecordSeries): TDataValue; static; inline;
|
||||
class function FromRecord(const [ref] AValue: TScalarRecord): TDataValue; static; inline;
|
||||
class function FromSeries(const AValue: ISeries): TDataValue; static; inline;
|
||||
@@ -79,7 +81,6 @@ type
|
||||
property Kind: TDataValueKind read GetKind;
|
||||
end;
|
||||
|
||||
type
|
||||
TDataValueKindHelper = record helper for TDataValueKind
|
||||
public
|
||||
function ToString: string;
|
||||
@@ -112,6 +113,18 @@ type
|
||||
constructor Create(const AVal: TScalar; const ACalc: TDataValue.TFunc);
|
||||
end;
|
||||
|
||||
TMapSeries = class(TInterfacedObject, ISeries)
|
||||
private
|
||||
FSourceSeries: ISeries;
|
||||
FMapperFunc: TDataValue.TFunc;
|
||||
// ISeries
|
||||
function GetCount: Int64;
|
||||
function GetTotalCount: Int64;
|
||||
function GetItems(Idx: Integer): TScalar;
|
||||
public
|
||||
constructor Create(const ASourceSeries: ISeries; const AMapperFunc: TDataValue.TFunc);
|
||||
end;
|
||||
|
||||
constructor TManagedLazy.Create(AKind: TDataValueKind; const AVal: IInterface; const ACalc: TDataValue.TFunc);
|
||||
begin
|
||||
inherited Create;
|
||||
@@ -329,6 +342,11 @@ begin
|
||||
Result := TDataValueKind(FKind);
|
||||
end;
|
||||
|
||||
class function TDataValue.Map(const SourceSeries: ISeries; const MapperFunc: TFunc): ISeries;
|
||||
begin
|
||||
Result := TMapSeries.Create(SourceSeries, MapperFunc);
|
||||
end;
|
||||
|
||||
function TDataValue.ToString: String;
|
||||
var
|
||||
sb: TStringBuilder;
|
||||
@@ -430,4 +448,40 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{ TMapSeries }
|
||||
|
||||
constructor TMapSeries.Create(const ASourceSeries: ISeries; const AMapperFunc: TDataValue.TFunc);
|
||||
begin
|
||||
inherited Create;
|
||||
FSourceSeries := ASourceSeries;
|
||||
FMapperFunc := AMapperFunc;
|
||||
end;
|
||||
|
||||
function TMapSeries.GetCount: Int64;
|
||||
begin
|
||||
Result := FSourceSeries.Count;
|
||||
end;
|
||||
|
||||
function TMapSeries.GetTotalCount: Int64;
|
||||
begin
|
||||
Result := FSourceSeries.TotalCount;
|
||||
end;
|
||||
|
||||
function TMapSeries.GetItems(Idx: Integer): TScalar;
|
||||
var
|
||||
sourceValue: TScalar;
|
||||
argArray: TArray<TDataValue>;
|
||||
mappedValue: TDataValue;
|
||||
begin
|
||||
// Get the original item, apply the mapper function, and return the result.
|
||||
sourceValue := FSourceSeries.Items[Idx];
|
||||
argArray := [TDataValue(sourceValue)];
|
||||
mappedValue := FMapperFunc(argArray);
|
||||
|
||||
if mappedValue.Kind <> vkScalar then
|
||||
raise EInvalidCast.Create('Map function did not return a scalar value.');
|
||||
|
||||
Result := mappedValue.AsScalar;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user