@@ -205,11 +205,59 @@ SEXP gforce(SEXP env, SEXP jsub, SEXP o, SEXP f, SEXP l, SEXP irowsArg) {
205205 UNPROTECT (1 );
206206 ans = tt ;
207207 }
208+
209+ // now replicate group values to match the number of rows in each group
210+ // in most cases gfuns return one value per group and there is little to do here
211+ // however, in first/last with na.rm=TRUE, each column could have a different
212+ // number of non-NA values, so those need to be padded
213+ lens = allocVector (INTSXP , ngrp ); // TODO: avoid allocatation if not necessary
214+ int * anslens = INTEGER (lens );
215+ memset (anslens , 0 , ngrp * sizeof (int ));
216+ int anslen = 0 ;
208217 for (int i = 0 ; i < LENGTH (ans ); ++ i ) {
209218 SEXP tt = VECTOR_ELT (ans , i );
219+ SEXP lens = getAttrib (tt , sym_lens );
220+ anslen = 0 ;
221+ if (!isNull (lens )) {
222+ const int * ss = INTEGER (lens );
223+ anslen = 0 ; // count again as the next column might make it bigger
224+ for (g = 0 ; g < ngrp ; ++ g ) {
225+ if (ss [g ]> anslens [g ]) anslens [g ]= ss [g ];
226+ anslen += anslens [g ];
227+ }
228+ }
210229 setAttrib (tt , sym_lens , R_NilValue );
211230 setAttrib (tt , sym_first , R_NilValue );
212231 }
232+ // now we have the max size of each group across the columns, we can pad if necessary
233+
234+ // so, we either create another column, or we budge up/down the padding within the same allocated column
235+ // budging up/down many small groups will take a lot of reads and writes, albeit saving total memory. The extra
236+ // memory is only one-at-a-time per column, so choose speed over memory here
237+
238+ // now we know the final result size, allocate and fill
239+ for (int i = 0 ; i < LENGTH (ans ); ++ i ) {
240+ SEXP tt = VECTOR_ELT (ans , i );
241+ SEXP lens = getAttrib (tt , sym_lens ); // how long the items are, we will never parallelize within column so we can sweep forwards
242+ const bool first = LOGICAL (getAttrib (tt , sym_first ))[0 ];
243+
244+ col = allocVector (TYPEOF (tt ), anslen );
245+
246+ anslen = 0 ;
247+
248+ if (!isNull (lens )) {
249+ const int * ss = INTEGER (lens );
250+ anslen = 0 ; // count again as the next column might make it bigger
251+ for (g = 0 ; g < ngrp ; ++ g ) {
252+ const int targetlen = anslens [g ];
253+ thislen = ss [g ];
254+ const int napad = anslen [g ]- ss [g ];
255+ if (!first ) for (int i = 0 ; i < napad ; ++ i ) ASSIGNNA ;
256+ for (int i = 0 ; i < thislen ; ++ i ) COPYVAL ;
257+ if (first ) for (int i = 0 ; i < napad ; ++ i ) ASSIGNNA ;
258+ }
259+ }
260+ }
213261 UNPROTECT (1 );
214262 return ans ;
215263}
0 commit comments