]> granicus.if.org Git - vim/commitdiff
patch 8.2.0878: no reduce() function v8.2.0878
authorBram Moolenaar <Bram@vim.org>
Mon, 1 Jun 2020 16:39:20 +0000 (18:39 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 1 Jun 2020 16:39:20 +0000 (18:39 +0200)
Problem:    No reduce() function.
Solution:   Add a reduce() function. (closes #5481)

runtime/doc/eval.txt
src/evalfunc.c
src/globals.h
src/list.c
src/proto/list.pro
src/testdir/test_listdict.vim
src/version.c

index 19547305ea904e457a59134be3ccbebd96c1dc6e..7b85acd55deee37996146011a21c4287221a454a 100644 (file)
@@ -2679,6 +2679,8 @@ readdir({dir} [, {expr}]) List    file names in {dir} selected by {expr}
 readdirex({dir} [, {expr}])    List    file info in {dir} selected by {expr}
 readfile({fname} [, {type} [, {max}]])
                                List    get list of lines from file {fname}
+reduce({object}, {func} [, {initial}])
+                               any     reduce {object} using {func}
 reg_executing()                        String  get the executing register name
 reg_recording()                        String  get the recording register name
 reltime([{start} [, {end}]])   List    get time value
@@ -7965,6 +7967,26 @@ readfile({fname} [, {type} [, {max}]])
                Can also be used as a |method|: >
                        GetFileName()->readfile()
 
+reduce({object}, {func} [, {initial}])                 *reduce()* *E998*
+               {func} is called for every item in {object}, which can be a
+               |List| or a |Blob|.  {func} is called with two arguments: the
+               result so far and current item.  After processing all items
+               the result is returned.
+
+               {initial} is the initial result.  When omitted, the first item
+               in {object} is used and {func} is first called for the second
+               item.  If {initial} is not given and {object} is empty no
+               result can be computed, an E998 error is given.
+
+               Examples: >
+                       echo reduce([1, 3, 5], { acc, val -> acc + val })
+                       echo reduce(['x', 'y'], { acc, val -> acc .. val }, 'a')
+                       echo reduce(0z1122, { acc, val -> 2 * acc + val })
+<
+               Can also be used as a |method|: >
+                       echo mylist->reduce({ acc, val -> acc + val }, 0)
+
+
 reg_executing()                                                *reg_executing()*
                Returns the single letter name of the register being executed.
                Returns an empty string when no register is being executed.
index 06de421eb161f1e75406edbc84d3fbddd4533cba..bfc811bff5f4f19429ad5001fd159542d06b7390 100644 (file)
@@ -769,6 +769,7 @@ static funcentry_T global_functions[] =
     {"readdir",                1, 2, FEARG_1,    ret_list_string, f_readdir},
     {"readdirex",      1, 2, FEARG_1,    ret_list_dict_any, f_readdirex},
     {"readfile",       1, 3, FEARG_1,    ret_any,      f_readfile},
+    {"reduce",         2, 3, FEARG_1,    ret_any,      f_reduce},
     {"reg_executing",  0, 0, 0,          ret_string,   f_reg_executing},
     {"reg_recording",  0, 0, 0,          ret_string,   f_reg_recording},
     {"reltime",                0, 2, FEARG_1,    ret_list_any, f_reltime},
index 57577d4a28e38c41dee750fbf05e7649c9138510..66b204b41ec9ce312d3218aeae72a0b6a9f4a72f 100644 (file)
@@ -1690,6 +1690,7 @@ EXTERN char e_inval_string[]      INIT(= N_("E908: using an invalid value as a String
 EXTERN char e_const_option[]   INIT(= N_("E996: Cannot lock an option"));
 EXTERN char e_unknown_option[] INIT(= N_("E113: Unknown option: %s"));
 EXTERN char e_letunexp[]       INIT(= N_("E18: Unexpected characters in :let"));
+EXTERN char e_reduceempty[]    INIT(= N_("E998: Reduce of an empty %s with no initial value"));
 #endif
 #ifdef FEAT_QUICKFIX
 EXTERN char e_readerrf[]       INIT(= N_("E47: Error while reading errorfile"));
index 7c06cfce573e2880a56977fec8eb610194f65b2c..40a910b1e3a9980fe46bb60aaa64f7b50ee41221 100644 (file)
@@ -2305,4 +2305,109 @@ f_reverse(typval_T *argvars, typval_T *rettv)
     }
 }
 
+/*
+ * "reduce(list, { accumlator, element -> value } [, initial])" function
+ */
+    void
+f_reduce(typval_T *argvars, typval_T *rettv)
+{
+    typval_T   accum;
+    char_u     *func_name;
+    partial_T   *partial = NULL;
+    funcexe_T  funcexe;
+    typval_T   argv[3];
+
+    if (argvars[0].v_type != VAR_LIST && argvars[0].v_type != VAR_BLOB)
+    {
+       emsg(_(e_listblobreq));
+       return;
+    }
+
+    if (argvars[1].v_type == VAR_FUNC)
+       func_name = argvars[1].vval.v_string;
+    else if (argvars[1].v_type == VAR_PARTIAL)
+    {
+       partial = argvars[1].vval.v_partial;
+       func_name = partial_name(partial);
+    }
+    else
+       func_name = tv_get_string(&argvars[1]);
+    if (*func_name == NUL)
+       return;         // type error or empty name
+
+    vim_memset(&funcexe, 0, sizeof(funcexe));
+    funcexe.evaluate = TRUE;
+    funcexe.partial = partial;
+
+    if (argvars[0].v_type == VAR_LIST)
+    {
+       list_T      *l = argvars[0].vval.v_list;
+       listitem_T  *li = NULL;
+
+       CHECK_LIST_MATERIALIZE(l);
+       if (argvars[2].v_type == VAR_UNKNOWN)
+       {
+           if (l == NULL || l->lv_first == NULL)
+           {
+               semsg(_(e_reduceempty), "List");
+               return;
+           }
+           accum = l->lv_first->li_tv;
+           li = l->lv_first->li_next;
+       }
+       else
+       {
+           accum = argvars[2];
+           if (l != NULL)
+               li = l->lv_first;
+       }
+
+       copy_tv(&accum, rettv);
+       for ( ; li != NULL; li = li->li_next)
+       {
+           argv[0] = accum;
+           argv[1] = li->li_tv;
+           if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL)
+               return;
+           accum = *rettv;
+       }
+    }
+    else
+    {
+       blob_T  *b = argvars[0].vval.v_blob;
+       int     i;
+
+       if (argvars[2].v_type == VAR_UNKNOWN)
+       {
+           if (b == NULL || b->bv_ga.ga_len == 0)
+           {
+               semsg(_(e_reduceempty), "Blob");
+               return;
+           }
+           accum.v_type = VAR_NUMBER;
+           accum.vval.v_number = blob_get(b, 0);
+           i = 1;
+       }
+       else
+       {
+           accum = argvars[2];
+           i = 0;
+       }
+
+       copy_tv(&accum, rettv);
+       if (b != NULL)
+       {
+           for ( ; i < b->bv_ga.ga_len; i++)
+           {
+               argv[0] = accum;
+               argv[1].v_type = VAR_NUMBER;
+               argv[1].vval.v_number = blob_get(b, i);
+               if (call_func(func_name, -1, rettv, 2, argv, &funcexe) == FAIL)
+                   return;
+               accum = *rettv;
+           }
+       }
+    }
+}
+
 #endif // defined(FEAT_EVAL)
index c2768748a75938fe8aa804003313909453669b6d..77183af0834b163887bc0f8ac8b4c9aec0dd6489 100644 (file)
@@ -51,4 +51,5 @@ void f_extend(typval_T *argvars, typval_T *rettv);
 void f_insert(typval_T *argvars, typval_T *rettv);
 void f_remove(typval_T *argvars, typval_T *rettv);
 void f_reverse(typval_T *argvars, typval_T *rettv);
+void f_reduce(typval_T *argvars, typval_T *rettv);
 /* vim: set ft=c : */
index 1dea4a88c51aa45bbcd7f5e6f0ea7994e36e521d..b1af87ea9d36744dae2b08b3b1a907fb5a64efdc 100644 (file)
@@ -680,6 +680,37 @@ func Test_reverse_sort_uniq()
   call assert_fails("call sort([1, 2], function('min'))", "E702:")
 endfunc
 
+" reduce a list or a blob
+func Test_reduce()
+  call assert_equal(1, reduce([], { acc, val -> acc + val }, 1))
+  call assert_equal(10, reduce([1, 3, 5], { acc, val -> acc + val }, 1))
+  call assert_equal(2 * (2 * ((2 * 1) + 2) + 3) + 4, reduce([2, 3, 4], { acc, val -> 2 * acc + val }, 1))
+  call assert_equal('a x y z', ['x', 'y', 'z']->reduce({ acc, val -> acc .. ' ' .. val}, 'a'))
+  call assert_equal(#{ x: 1, y: 1, z: 1 }, ['x', 'y', 'z']->reduce({ acc, val -> extend(acc, { val: 1 }) }, {}))
+  call assert_equal([0, 1, 2, 3], reduce([1, 2, 3], function('add'), [0]))
+
+  let l = ['x', 'y', 'z']
+  call assert_equal(42, reduce(l, function('get'), #{ x: #{ y: #{ z: 42 } } }))
+  call assert_equal(['x', 'y', 'z'], l)
+
+  call assert_equal(1, reduce([1], { acc, val -> acc + val }))
+  call assert_equal('x y z', reduce(['x', 'y', 'z'], { acc, val -> acc .. ' ' .. val }))
+  call assert_equal(120, range(1, 5)->reduce({ acc, val -> acc * val }))
+  call assert_fails("call reduce([], { acc, val -> acc + val })", 'E998: Reduce of an empty List with no initial value')
+
+  call assert_equal(1, reduce(0z, { acc, val -> acc + val }, 1))
+  call assert_equal(1 + 0xaf + 0xbf + 0xcf, reduce(0zAFBFCF, { acc, val -> acc + val }, 1))
+  call assert_equal(2 * (2 * 1 + 0xaf) + 0xbf, 0zAFBF->reduce({ acc, val -> 2 * acc + val }, 1))
+
+  call assert_equal(0xff, reduce(0zff, { acc, val -> acc + val }))
+  call assert_equal(2 * (2 * 0xaf + 0xbf) + 0xcf, reduce(0zAFBFCF, { acc, val -> 2 * acc + val }))
+  call assert_fails("call reduce(0z, { acc, val -> acc + val })", 'E998: Reduce of an empty Blob with no initial value')
+
+  call assert_fails("call reduce({}, { acc, val -> acc + val }, 1)", 'E897:')
+  call assert_fails("call reduce(0, { acc, val -> acc + val }, 1)", 'E897:')
+  call assert_fails("call reduce('', { acc, val -> acc + val }, 1)", 'E897:')
+endfunc
+
 " splitting a string to a List using split()
 func Test_str_split()
   call assert_equal(['aa', 'bb'], split('  aa  bb '))
index af3aadf4dd778f3ef904fc2428b330fc343dca03..4a271403a5b277d6683b5e438bfdef745910372a 100644 (file)
@@ -746,6 +746,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    878,
 /**/
     877,
 /**/