Ast RTL: Map, Reduce, Where, Any
This commit is contained in:
+140
-50
@@ -17,55 +17,8 @@ uses
|
|||||||
|
|
||||||
implementation
|
implementation
|
||||||
|
|
||||||
type
|
uses
|
||||||
// Implements a lazily evaluated, read-only series based on a source series and a mapper function.
|
System.Generics.Collections;
|
||||||
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;
|
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
//== Native Function Implementations
|
//== Native Function Implementations
|
||||||
@@ -179,7 +132,141 @@ begin
|
|||||||
if Args[1].Kind <> vkMethod then
|
if Args[1].Kind <> vkMethod then
|
||||||
raise EArgumentException.Create('The second argument to Map must be a function.');
|
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;
|
end;
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------------------------
|
||||||
@@ -194,6 +281,9 @@ begin
|
|||||||
AScope.Define('Floor', TDataValue(NativeFloor));
|
AScope.Define('Floor', TDataValue(NativeFloor));
|
||||||
AScope.Define('Sign', TDataValue(NativeSign));
|
AScope.Define('Sign', TDataValue(NativeSign));
|
||||||
AScope.Define('Map', TDataValue(NativeMap));
|
AScope.Define('Map', TDataValue(NativeMap));
|
||||||
|
AScope.Define('Reduce', TDataValue(NativeReduce));
|
||||||
|
AScope.Define('Where', TDataValue(NativeWhere));
|
||||||
|
AScope.Define('Any', TDataValue(NativeAny));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
initialization
|
initialization
|
||||||
|
|||||||
@@ -254,6 +254,16 @@ type
|
|||||||
property TotalCount: Int64 read GetTotalCount;
|
property TotalCount: Int64 read GetTotalCount;
|
||||||
end;
|
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
|
TBinaryOperatorHelper = record helper for TBinaryOperator
|
||||||
function ToString: string;
|
function ToString: string;
|
||||||
end;
|
end;
|
||||||
@@ -1557,6 +1567,34 @@ begin
|
|||||||
Result := FTotalCount;
|
Result := FTotalCount;
|
||||||
end;
|
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
|
initialization
|
||||||
Assert(sizeof(TScalarValue) = 8);
|
Assert(sizeof(TScalarValue) = 8);
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ type
|
|||||||
|
|
||||||
class function Defer(const Value: TDataValue; const Calc: TFunc): TDataValue; overload; static;
|
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 FromRecordSeries(const AValue: IRecordSeries): TDataValue; static; inline;
|
||||||
class function FromRecord(const [ref] AValue: TScalarRecord): TDataValue; static; inline;
|
class function FromRecord(const [ref] AValue: TScalarRecord): TDataValue; static; inline;
|
||||||
class function FromSeries(const AValue: ISeries): TDataValue; static; inline;
|
class function FromSeries(const AValue: ISeries): TDataValue; static; inline;
|
||||||
@@ -79,7 +81,6 @@ type
|
|||||||
property Kind: TDataValueKind read GetKind;
|
property Kind: TDataValueKind read GetKind;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
type
|
|
||||||
TDataValueKindHelper = record helper for TDataValueKind
|
TDataValueKindHelper = record helper for TDataValueKind
|
||||||
public
|
public
|
||||||
function ToString: string;
|
function ToString: string;
|
||||||
@@ -112,6 +113,18 @@ type
|
|||||||
constructor Create(const AVal: TScalar; const ACalc: TDataValue.TFunc);
|
constructor Create(const AVal: TScalar; const ACalc: TDataValue.TFunc);
|
||||||
end;
|
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);
|
constructor TManagedLazy.Create(AKind: TDataValueKind; const AVal: IInterface; const ACalc: TDataValue.TFunc);
|
||||||
begin
|
begin
|
||||||
inherited Create;
|
inherited Create;
|
||||||
@@ -329,6 +342,11 @@ begin
|
|||||||
Result := TDataValueKind(FKind);
|
Result := TDataValueKind(FKind);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
class function TDataValue.Map(const SourceSeries: ISeries; const MapperFunc: TFunc): ISeries;
|
||||||
|
begin
|
||||||
|
Result := TMapSeries.Create(SourceSeries, MapperFunc);
|
||||||
|
end;
|
||||||
|
|
||||||
function TDataValue.ToString: String;
|
function TDataValue.ToString: String;
|
||||||
var
|
var
|
||||||
sb: TStringBuilder;
|
sb: TStringBuilder;
|
||||||
@@ -430,4 +448,40 @@ begin
|
|||||||
end;
|
end;
|
||||||
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.
|
end.
|
||||||
|
|||||||
Reference in New Issue
Block a user