Skip to content

Commit 61a73f7

Browse files
fingolfincodex
andcommitted
coll: finish FoldLeft and X-function polish
Turn FoldLeft into an operation with early methods for built-in lists, make it skip holes consistently, and tighten FoldLeftX and the X-style boolean predicates to match the surrounding collection API semantics. Complete the manual entries for FoldLeft, FoldLeftX, and the X-style helper functions, and extend the collection tests to cover hole skipping, FoldLeftX validation, short-circuiting, and boolean result checks. AI assistance was provided by Codex for reviewing the branch, implementing the changes, updating documentation, and preparing this commit message. Co-authored-by: Codex <codex@openai.com>
1 parent e4272cd commit 61a73f7

File tree

4 files changed

+173
-119
lines changed

4 files changed

+173
-119
lines changed

doc/ref/lists.xml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1697,6 +1697,8 @@ The following functions are generalizations of
16971697
<Ref Func="Number"/>,
16981698
and <Ref Func="Perform"/>.
16991699

1700+
<#Include Label="FoldLeft">
1701+
<#Include Label="FoldLeftX">
17001702
<#Include Label="ListX">
17011703
<#Include Label="SetX">
17021704
<#Include Label="SumX">
@@ -1902,4 +1904,3 @@ such as before objectifying, or calling some kernel functions.
19021904
<!-- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -->
19031905
<!-- %% -->
19041906
<!-- %E -->
1905-

lib/coll.gd

Lines changed: 82 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -2482,27 +2482,44 @@ DeclareGlobalFunction( "Elements" );
24822482
##
24832483
#O FoldLeft( <C>, <func>[, <init>] )
24842484
##
2485+
## <#GAPDoc Label="FoldLeft">
24852486
## <ManSection>
2486-
## <Func Name="FoldLeft" Arg='C, func[, init]'/>
2487+
## <Oper Name="FoldLeft" Arg='C, func[, init]'/>
24872488
##
24882489
## <Description>
2489-
## TODO
2490+
## <Ref Oper="FoldLeft"/> applies the binary function <A>func</A>
2491+
## left-associatively to the elements of the list or collection <A>C</A>.
2492+
## <P/>
2493+
## If no initial accumulator <A>init</A> is given, the first element of
2494+
## <A>C</A> is used as the initial accumulator and the remaining elements
2495+
## are combined with it from left to right. In that case <A>C</A> must not
2496+
## be empty.
2497+
## <P/>
2498+
## If <A>init</A> is given, the accumulator is initialized with
2499+
## <A>init</A>. Then an empty list or collection is allowed, and
2500+
## <A>init</A> is returned unchanged in that case.
2501+
## <P/>
2502+
## If <A>C</A> is a list with holes, these holes are ignored, just as when
2503+
## iterating over the list with a <C>for</C>-loop.
2504+
## <P/>
2505+
## This operation can be used to express reductions similar to
2506+
## <Ref Func="Sum"/> or <Ref Func="Product"/>, but it is more general
2507+
## because the accumulator is updated by an arbitrary binary function.
2508+
## <P/>
2509+
## <Example><![CDATA[
2510+
## gap> FoldLeft( [ 1 .. 10 ], \+ );
2511+
## 55
2512+
## gap> FoldLeft( [ 1 .. 4 ], \*, 2 );
2513+
## 48
2514+
## gap> FoldLeft( [ 1,, 3 ], function( a, b ) return a + b; end );
2515+
## 4
2516+
## ]]></Example>
24902517
## </Description>
24912518
## </ManSection>
2519+
## <#/GAPDoc>
24922520
##
2493-
## TODO: explain in docs that to imitate the behavior of
2494-
## x := Sum(list, func, init);
2495-
## one can do this:
2496-
## x := FoldLeft(list, {x,y} -> x + func(y), func(init));
2497-
## or of course also this (but requires more memory)
2498-
## x := FoldLeft(List(list, func), \+);
2499-
##
2500-
## There is no good way to imitate `Sum(list, func)` without
2501-
## an initial value.
2502-
##
2503-
DeclareGlobalFunction( "FoldLeft" );
2504-
DeclareOperation( "FoldLeftOp", [ IsListOrCollection, IsFunction ] );
2505-
DeclareOperation( "FoldLeftOp", [ IsListOrCollection, IsFunction, IsObject ] );
2521+
DeclareOperation( "FoldLeft", [ IsListOrCollection, IsFunction ] );
2522+
DeclareOperation( "FoldLeft", [ IsListOrCollection, IsFunction, IsObject ] );
25062523

25072524

25082525
#############################################################################
@@ -2518,8 +2535,10 @@ DeclareOperation( "FoldLeftOp", [ IsListOrCollection, IsFunction, IsObject ] );
25182535
## which applies an accumulation function <A>func</A> to a bunch of
25192536
## inputs which are derived from <A>gens</A>.
25202537
## <P/>
2521-
## Specifically, let <C>n</A> denote the length of <A>gens</A>. Then
2522-
## each of the entries <A>gens</A><C>[1]</C>, <M>\ldots</M> <A>gens</A><C>[n]</C>
2538+
## The argument <A>gens</A> must be a plain list whose entries describe a
2539+
## sequence of nested loops and filters. Specifically, let <C>n</C> denote
2540+
## the length of <A>gens</A>. Then each of the entries
2541+
## <A>gens</A><C>[1]</C>, <M>\ldots</M> <A>gens</A><C>[n]</C>
25232542
## must be one of the following:
25242543
## <List>
25252544
## <Mark>a list or collection</Mark>
@@ -2543,69 +2562,33 @@ DeclareOperation( "FoldLeftOp", [ IsListOrCollection, IsFunction, IsObject ] );
25432562
## The argument <A>func</A> must be a binary function, whose first
25442563
## argument is an accumulator variable, and the second argument is
25452564
## the tuple of values of the loop-variables.
2546-
##
2547-
# TODO: continue editing after this point
2548-
# TODO: document initial as initial accumulator value
2549-
# TODO: document abortValue
2550-
# TODO: perhaps explain how to implement ListX via FoldLeftX as one of
2551-
# the examples?
2552-
#
2553-
##
2554-
## <P/>
2555-
## Thus <C>ListX( <A>list</A>, <A>func</A> )</C> is the same as
2556-
## <C>List( <A>list</A>, <A>func</A> )</C>,
2557-
## and <C>ListX( <A>list</A>, <A>func</A>, x -> x )</C> is the same as
2558-
## <C>Filtered( <A>list</A>, <A>func</A> )</C>.
2559-
## <P/>
2560-
## As a more elaborate example, assume <A>arg1</A> is a list or collection,
2561-
## <A>arg2</A> is a function returning <K>true</K> or <K>false</K>,
2562-
## <A>arg3</A> is a function returning a list or collection, and
2563-
## <A>arg4</A> is another function returning <K>true</K> or <K>false</K>,
2564-
## then
25652565
## <P/>
2566-
## <C><A>result</A> := ListX( <A>arg1</A>, <A>arg2</A>, <A>arg3</A>,
2567-
## <A>arg4</A>, <A>func</A> );</C>
2566+
## The accumulator is initialized with <A>init</A>. For every tuple that is
2567+
## produced by the loops and filters described by <A>gens</A>,
2568+
## <A>func</A> is called with the current accumulator and that tuple.
2569+
## Its return value becomes the new accumulator.
25682570
## <P/>
2569-
## is equivalent to
2570-
## <P/>
2571-
## <Listing><![CDATA[
2572-
## result := [];
2573-
## for v1 in arg1 do
2574-
## if arg2( v1 ) then
2575-
## for v2 in arg3( v1 ) do
2576-
## if arg4( v1, v2 ) then
2577-
## Add( result, func( v1, v2 ) );
2578-
## fi;
2579-
## od;
2580-
## fi;
2581-
## od;
2582-
## ]]></Listing>
2583-
## <P/>
2584-
## The following example shows how <Ref Func="ListX"/> can be used to
2585-
## compute all pairs and all strictly sorted pairs of elements in a list.
2571+
## If the optional argument <A>abortValue</A> is given, iteration stops as
2572+
## soon as the accumulator becomes identical to <A>abortValue</A>.
2573+
## This is useful for short-circuiting computations such as
2574+
## <Ref Func="ForAllX"/> and <Ref Func="ForAnyX"/>.
25862575
## <P/>
25872576
## <Example><![CDATA[
2588-
## gap> l:= [ 1, 2, 3, 4 ];;
2589-
## gap> pair:= function( x, y ) return [ x, y ]; end;;
2590-
## gap> ListX( l, l, pair );
2591-
## [ [ 1, 1 ], [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], [ 2, 1 ], [ 2, 2 ],
2592-
## [ 2, 3 ], [ 2, 4 ], [ 3, 1 ], [ 3, 2 ], [ 3, 3 ], [ 3, 4 ],
2593-
## [ 4, 1 ], [ 4, 2 ], [ 4, 3 ], [ 4, 4 ] ]
2594-
## ]]></Example>
2595-
## <P/>
2596-
## In the following example, <Ref Oper="\&lt;"/> is the comparison
2597-
## operation:
2598-
## <P/>
2599-
## <Example><![CDATA[
2600-
## gap> ListX( l, l, \<, pair );
2601-
## [ [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], [ 2, 3 ], [ 2, 4 ], [ 3, 4 ] ]
2577+
## gap> FoldLeftX( [ [ 1 .. 3 ], i -> [ 1 .. i ],
2578+
## > function( i, j ) return i <> j; end ],
2579+
## > function( acc, x ) return acc + 1; end, 0 );
2580+
## 3
2581+
## gap> FoldLeftX( [ [ 1 .. 3 ], [ 1 .. 3 ], \< ],
2582+
## > function( acc, x )
2583+
## > Add( acc, ShallowCopy( x ) );
2584+
## > return acc;
2585+
## > end, [] );
2586+
## [ [ 1, 2 ], [ 1, 3 ], [ 2, 3 ] ]
26022587
## ]]></Example>
26032588
## </Description>
26042589
## </ManSection>
26052590
## <#/GAPDoc>
26062591
##
2607-
## TODO: document FoldLeftX (based on ListX documentation?)
2608-
##
26092592
DeclareGlobalFunction( "FoldLeftX" );
26102593

26112594

@@ -3079,9 +3062,12 @@ DeclareGlobalFunction( "ProductX" );
30793062
## <Func Name="ForAllX" Arg='arg1, arg2, ... func'/>
30803063
##
30813064
## <Description>
3082-
## <Ref Func="ForAllX"/> returns <K>true</A> if all elements are
3083-
## <K>true</A> in the list obtained by calling <Ref Func="ListX"/> with the
3084-
## same arguments. Otherwise <K>false</A> is returned.
3065+
## <Ref Func="ForAllX"/> returns <K>true</K> if all elements are
3066+
## <K>true</K> in the list obtained by calling <Ref Func="ListX"/> with the
3067+
## same arguments. Otherwise <K>false</K> is returned.
3068+
## As with <Ref Func="ForAll"/>, the last argument must return either
3069+
## <K>true</K> or <K>false</K>, and evaluation stops as soon as the result
3070+
## is known.
30853071
## </Description>
30863072
## </ManSection>
30873073
## <#/GAPDoc>
@@ -3098,9 +3084,12 @@ DeclareGlobalFunction( "ForAllX" );
30983084
## <Func Name="ForAnyX" Arg='arg1, arg2, ... func'/>
30993085
##
31003086
## <Description>
3101-
## <Ref Func="ForAnyX"/> returns <K>true</A> if any element is
3102-
## <K>true</A> in the list obtained by calling <Ref Func="ListX"/> with the
3103-
## same arguments. Otherwise <K>false</A> is returned.
3087+
## <Ref Func="ForAnyX"/> returns <K>true</K> if any element is
3088+
## <K>true</K> in the list obtained by calling <Ref Func="ListX"/> with the
3089+
## same arguments. Otherwise <K>false</K> is returned.
3090+
## As with <Ref Func="ForAny"/>, the last argument must return either
3091+
## <K>true</K> or <K>false</K>, and evaluation stops as soon as the result
3092+
## is known.
31043093
## </Description>
31053094
## </ManSection>
31063095
## <#/GAPDoc>
@@ -3117,14 +3106,20 @@ DeclareGlobalFunction( "ForAnyX" );
31173106
## <Func Name="FilteredX" Arg='arg1, arg2, ... func'/>
31183107
##
31193108
## <Description>
3120-
## <Ref Func="FilteredX"/> returns the TODO of the elements in the list
3121-
## obtained by <Ref Func="ListX"/> when this is called with the same
3122-
## arguments.
3109+
## <Ref Func="FilteredX"/> returns the tuples of loop-variable values for
3110+
## which the last argument returns <K>true</K>.
3111+
## In other words, it keeps precisely those tuples that would pass the
3112+
## filters when calling <Ref Func="ListX"/> with the same generators.
3113+
## <P/>
3114+
## Even with only one generator, the result consists of singleton tuples.
3115+
## Thus <C>FilteredX( [ 1 .. 4 ], IsEvenInt )</C> returns
3116+
## <C>[ [ 2 ], [ 4 ] ]</C>, not <C>[ 2, 4 ]</C>.
3117+
## Use <Ref Func="Filtered"/> if you want to filter the elements of a single
3118+
## list or collection directly.
31233119
## </Description>
31243120
## </ManSection>
31253121
## <#/GAPDoc>
31263122
##
3127-
## TODO: perhaps better to document this in terms of FoldLeftX
31283123
DeclareGlobalFunction( "FilteredX" );
31293124

31303125

@@ -3137,9 +3132,10 @@ DeclareGlobalFunction( "FilteredX" );
31373132
## <Func Name="NumberX" Arg='arg1, arg2, ... func'/>
31383133
##
31393134
## <Description>
3140-
## <Ref Func="NumberX"/> returns the TODO of the elements in the list
3141-
## obtained by <Ref Func="ListX"/> when this is called with the same
3142-
## arguments.
3135+
## <Ref Func="NumberX"/> returns the number of tuples selected by the last
3136+
## argument, using the same generators as <Ref Func="ListX"/>.
3137+
## Equivalently, it counts the entries of the result that
3138+
## <Ref Func="FilteredX"/> would return with the same arguments.
31433139
## </Description>
31443140
## </ManSection>
31453141
## <#/GAPDoc>
@@ -3158,7 +3154,8 @@ DeclareGlobalFunction( "NumberX" );
31583154
## <Description>
31593155
## <Ref Func="PerformX"/> works like <Ref Func="ListX"/> except that it
31603156
## returns nothing and ignores the return values of <A>func</A>.
3161-
## arguments.
3157+
## It is useful for iterating through the tuples described by the generators
3158+
## purely for their side effects.
31623159
## </Description>
31633160
## </ManSection>
31643161
## <#/GAPDoc>

lib/coll.gi

Lines changed: 59 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1716,45 +1716,54 @@ end );
17161716
#F FoldLeft( <coll>, <func> )
17171717
#F FoldLeft( <coll>, <func>, <init> )
17181718
##
1719-
InstallGlobalFunction( FoldLeft,
1720-
function( arg )
1721-
local tnum, C, func, result, i, l;
1722-
l := Length( arg );
1723-
if l < 2 or l > 3 or not IsFunction(arg[2]) then
1719+
InstallEarlyMethod( FoldLeft,
1720+
function( C, func )
1721+
local tnum, result, found, x;
1722+
if not IsFunction( func ) then
17241723
Error( "usage: FoldLeft( <C>, <func>[, <init>] )" );
17251724
fi;
1726-
tnum:= TNUM_OBJ( arg[1] );
1727-
# handle built-in lists directly, to avoid method dispatch overhead
1728-
if FIRST_LIST_TNUM <= tnum and tnum <= LAST_LIST_TNUM then
1729-
C:= arg[1];
1730-
func:= arg[2];
1731-
if l = 2 then
1732-
if IsEmpty( C ) then
1733-
Error("folding an empty collection without initial value is not supported");
1734-
else
1735-
result:= C[1];
1736-
for i in [ 2 .. Length( C ) ] do
1737-
result:= func( result, C[i] );
1738-
od;
1739-
fi;
1725+
tnum:= TNUM_OBJ( C );
1726+
if not ( FIRST_LIST_TNUM <= tnum and tnum <= LAST_LIST_TNUM ) then
1727+
TryNextMethod();
1728+
fi;
1729+
found := false;
1730+
for x in C do
1731+
if found then
1732+
result := func( result, x );
17401733
else
1741-
result:= arg[3];
1742-
for i in C do
1743-
result:= func( result, i );
1744-
od;
1734+
result := x;
1735+
found := true;
17451736
fi;
1746-
return result;
1747-
else
1748-
return CallFuncList( FoldLeftOp, arg );
1737+
od;
1738+
if not found then
1739+
Error("folding an empty collection without initial value is not supported");
17491740
fi;
1750-
end );
1741+
return result;
1742+
end );
1743+
1744+
InstallEarlyMethod( FoldLeft,
1745+
function( C, func, init )
1746+
local tnum, result, x;
1747+
if not IsFunction( func ) then
1748+
Error( "usage: FoldLeft( <C>, <func>[, <init>] )" );
1749+
fi;
1750+
tnum:= TNUM_OBJ( C );
1751+
if not ( FIRST_LIST_TNUM <= tnum and tnum <= LAST_LIST_TNUM ) then
1752+
TryNextMethod();
1753+
fi;
1754+
result := init;
1755+
for x in C do
1756+
result := func( result, x );
1757+
od;
1758+
return result;
1759+
end );
17511760

17521761

17531762
#############################################################################
17541763
##
1755-
#M FoldLeftOp( <C>, <func> ) . . . . . . . for a list/collection, and a function
1764+
#M FoldLeft( <C>, <func> ) . . . . . . . . for a list/collection, and a function
17561765
##
1757-
InstallMethod( FoldLeftOp,
1766+
InstallMethod( FoldLeft,
17581767
"for a list/collection, and a function",
17591768
[ IsListOrCollection, IsFunction ],
17601769
function ( C, func )
@@ -1773,9 +1782,9 @@ InstallMethod( FoldLeftOp,
17731782

17741783
#############################################################################
17751784
##
1776-
#M FoldLeftOp( <C>, <func>, <init> ) . for a list/coll., a func., and init. val.
1785+
#M FoldLeft( <C>, <func>, <init> ) . . for a list/coll., a func., and init. val.
17771786
##
1778-
InstallMethod( FoldLeftOp,
1787+
InstallMethod( FoldLeft,
17791788
"for a list/collection, and a function, and an initial value",
17801789
[ IsListOrCollection, IsFunction, IsObject ],
17811790
function ( C, func, init )
@@ -1795,6 +1804,9 @@ InstallMethod( FoldLeftOp,
17951804
InstallGlobalFunction( FoldLeftX, function ( gens, f, init, extra... )
17961805
local abortValue;
17971806

1807+
if not IsPlistRep( gens ) or not IsFunction( f ) or Length(extra) > 1 then
1808+
Error( "usage: FoldLeftX( <gens>, <func>, <init>[, <abortValue>] )" );
1809+
fi;
17981810
if Length(extra) > 0 then
17991811
abortValue := extra[1];
18001812
else
@@ -1861,7 +1873,14 @@ end );
18611873
InstallGlobalFunction( ForAllX, function ( arg )
18621874
local f;
18631875
f := Remove(arg);
1864-
return FoldLeftX(arg, {acc,x} -> CallFuncList(f, x), true, false);
1876+
return FoldLeftX(arg,
1877+
function(acc, x)
1878+
if CallFuncList(f, x) then
1879+
return true;
1880+
else
1881+
return false;
1882+
fi;
1883+
end, true, false);
18651884
end );
18661885

18671886

@@ -1872,7 +1891,14 @@ end );
18721891
InstallGlobalFunction( ForAnyX, function ( arg )
18731892
local f;
18741893
f := Remove(arg);
1875-
return FoldLeftX(arg, {acc,x} -> CallFuncList(f, x), false, true);
1894+
return FoldLeftX(arg,
1895+
function(acc, x)
1896+
if CallFuncList(f, x) then
1897+
return true;
1898+
else
1899+
return false;
1900+
fi;
1901+
end, false, true);
18761902
end );
18771903

18781904

0 commit comments

Comments
 (0)