diff --git a/Src/AST/Myc.Ast.RTL.pas b/Src/AST/Myc.Ast.RTL.pas index ef34b3c..5e9b9c9 100644 --- a/Src/AST/Myc.Ast.RTL.pas +++ b/Src/AST/Myc.Ast.RTL.pas @@ -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; - 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; +var + sourceArg: TDataValue; + sourceSeries: ISeries; + accumulator: TDataValue; + reducerFunc: TDataValue.TFunc; + i: Integer; + currentItem: TScalar; + reducerArgs: TArray; +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; +var + sourceArg: TDataValue; + sourceSeries: ISeries; + predicateFunc: TDataValue.TFunc; + matchingIndices: TList; + 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.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 + 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; +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 diff --git a/Src/Data/Myc.Data.Scalar.pas b/Src/Data/Myc.Data.Scalar.pas index 98ba69c..d172b09 100644 --- a/Src/Data/Myc.Data.Scalar.pas +++ b/Src/Data/Myc.Data.Scalar.pas @@ -254,6 +254,16 @@ type property TotalCount: Int64 read GetTotalCount; end; + TIndexSeries = class(TInterfacedObject, ISeries) + private + FIndexArray: TArray; + function GetCount: Int64; + function GetTotalCount: Int64; + function GetItems(Idx: Integer): TScalar; + public + constructor Create(const AIndexArray: TArray); + 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); +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); diff --git a/Src/Data/Myc.Data.Value.pas b/Src/Data/Myc.Data.Value.pas index 1c5a317..a090754 100644 --- a/Src/Data/Myc.Data.Value.pas +++ b/Src/Data/Myc.Data.Value.pas @@ -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; + 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.