Skip to content

Commit 1eb3867

Browse files
authored
v: add array.count as a method that accepts a predicate, similar to filter, but returning just the number of matches (#23054)
1 parent 0c8a032 commit 1eb3867

File tree

12 files changed

+206
-17
lines changed

12 files changed

+206
-17
lines changed

vlib/builtin/array.v

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -807,6 +807,13 @@ pub fn (a array) filter(predicate fn (voidptr) bool) array
807807
// Example: array.any(it.name == 'Bob') // will yield `true` if any element has `.name == 'Bob'`
808808
pub fn (a array) any(predicate fn (voidptr) bool) bool
809809

810+
// count counts how many elements in array pass the test.
811+
// Ignore the function signature. `count` does not take an actual callback. Rather, it
812+
// takes an `it` expression.
813+
//
814+
// Example: array.count(it % 2 == 1) // will return how many elements are odd
815+
pub fn (a array) count(predicate fn (voidptr) bool) int
816+
810817
// all tests whether all elements in the array pass the test.
811818
// Ignore the function signature. `all` does not take an actual callback. Rather, it
812819
// takes an `it` expression.

vlib/v/checker/checker.v

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ const generic_fn_postprocess_iterations_cutoff_limit = 1_000_000
3030
// Note that methods that do not return anything, or that return known types, are not listed here, since they are just ordinary non generic methods.
3131
pub const array_builtin_methods = ['filter', 'clone', 'repeat', 'reverse', 'map', 'slice', 'sort',
3232
'sort_with_compare', 'sorted', 'sorted_with_compare', 'contains', 'index', 'wait', 'any', 'all',
33-
'first', 'last', 'pop', 'delete', 'insert', 'prepend']
33+
'first', 'last', 'pop', 'delete', 'insert', 'prepend', 'count']
3434
pub const array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(array_builtin_methods)
3535
pub const fixed_array_builtin_methods = ['contains', 'index', 'any', 'all', 'wait', 'map', 'sort',
36-
'sorted', 'sort_with_compare', 'sorted_with_compare', 'reverse', 'reverse_in_place']
36+
'sorted', 'sort_with_compare', 'sorted_with_compare', 'reverse', 'reverse_in_place', 'count']
3737
pub const fixed_array_builtin_methods_chk = token.new_keywords_matcher_from_array_trie(fixed_array_builtin_methods)
3838
// TODO: remove `byte` from this list when it is no longer supported
3939
pub const reserved_type_names = ['byte', 'bool', 'char', 'i8', 'i16', 'int', 'i64', 'u8', 'u16',

vlib/v/checker/fn.v

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3118,7 +3118,7 @@ fn (mut c Checker) fn_call_error_have_want(p HaveWantParams) {
31183118
c.error('expected ${p.nr_params} ${args_plural}, but got ${p.nr_args}', p.pos)
31193119
}
31203120

3121-
fn (mut c Checker) check_map_and_filter(is_map bool, elem_typ ast.Type, node ast.CallExpr) {
3121+
fn (mut c Checker) check_predicate_param(is_map bool, elem_typ ast.Type, node ast.CallExpr) {
31223122
if node.args.len != 1 {
31233123
c.error('expected 1 argument, but got ${node.args.len}', node.pos)
31243124
// Finish early so that it doesn't fail later
@@ -3327,7 +3327,7 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
33273327
c.table.sym(unaliased_left_type).info as ast.Array
33283328
}
33293329
elem_typ = array_info.elem_type
3330-
if method_name in ['filter', 'map', 'any', 'all'] {
3330+
if method_name in ['filter', 'map', 'any', 'all', 'count'] {
33313331
if node.args.len > 0 && mut node.args[0].expr is ast.LambdaExpr {
33323332
if node.args[0].expr.params.len != 1 {
33333333
c.error('lambda expressions used in the builtin array methods require exactly 1 parameter',
@@ -3513,7 +3513,7 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
35133513
if method_name == 'map' {
35143514
// eprintln('>>>>>>> map node.args[0].expr: ${node.args[0].expr}, left_type: ${left_type} | elem_typ: ${elem_typ} | arg_type: ${arg_type}')
35153515
// check fn
3516-
c.check_map_and_filter(true, elem_typ, node)
3516+
c.check_predicate_param(true, elem_typ, node)
35173517
arg_sym := c.table.sym(arg_type)
35183518
ret_type := match arg_sym.info {
35193519
ast.FnType {
@@ -3542,10 +3542,13 @@ fn (mut c Checker) array_builtin_method_call(mut node ast.CallExpr, left_type as
35423542
} else if node.left.is_auto_deref_var() {
35433543
node.return_type = node.return_type.deref()
35443544
}
3545-
c.check_map_and_filter(false, elem_typ, node)
3545+
c.check_predicate_param(false, elem_typ, node)
35463546
} else if method_name in ['any', 'all'] {
3547-
c.check_map_and_filter(false, elem_typ, node)
3547+
c.check_predicate_param(false, elem_typ, node)
35483548
node.return_type = ast.bool_type
3549+
} else if method_name == 'count' {
3550+
c.check_predicate_param(false, elem_typ, node)
3551+
node.return_type = ast.int_type
35493552
} else if method_name == 'clone' {
35503553
if node.args.len != 0 {
35513554
c.error('`.clone()` does not have any arguments', node.args[0].pos)
@@ -3715,8 +3718,28 @@ fn (mut c Checker) fixed_array_builtin_method_call(mut node ast.CallExpr, left_t
37153718
scope_register_it(mut node.scope, node.pos, elem_typ)
37163719
}
37173720
c.expr(mut node.args[0].expr)
3718-
c.check_map_and_filter(false, elem_typ, node)
3721+
c.check_predicate_param(false, elem_typ, node)
37193722
node.return_type = ast.bool_type
3723+
} else if method_name == 'count' {
3724+
if node.args.len != 1 {
3725+
c.error('`.${method_name}` expected 1 argument, but got ${node.args.len}',
3726+
node.pos)
3727+
return ast.bool_type
3728+
}
3729+
if node.args.len > 0 && mut node.args[0].expr is ast.LambdaExpr {
3730+
if node.args[0].expr.params.len != 1 {
3731+
c.error('lambda expressions used in the builtin array methods require exactly 1 parameter',
3732+
node.args[0].expr.pos)
3733+
return ast.bool_type
3734+
}
3735+
c.support_lambda_expr_one_param(elem_typ, ast.bool_type, mut node.args[0].expr)
3736+
} else {
3737+
// position of `it` doesn't matter
3738+
scope_register_it(mut node.scope, node.pos, elem_typ)
3739+
}
3740+
c.expr(mut node.args[0].expr)
3741+
c.check_predicate_param(false, elem_typ, node)
3742+
node.return_type = ast.int_type
37203743
} else if method_name == 'wait' {
37213744
elem_sym := c.table.sym(elem_typ)
37223745
if elem_sym.kind == .thread {
@@ -3757,7 +3780,7 @@ fn (mut c Checker) fixed_array_builtin_method_call(mut node ast.CallExpr, left_t
37573780
scope_register_it(mut node.scope, node.pos, elem_typ)
37583781
}
37593782

3760-
c.check_map_and_filter(true, elem_typ, node)
3783+
c.check_predicate_param(true, elem_typ, node)
37613784
arg_type := c.check_expr_option_or_result_call(node.args[0].expr, c.expr(mut node.args[0].expr))
37623785
arg_sym := c.table.sym(arg_type)
37633786
ret_type := match arg_sym.info {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
vlib/v/checker/tests/array_count_err.vv:4:4: error: expected 1 argument, but got 2
2+
2 | a := []int{}
3+
3 | a.count(1)
4+
4 | a.count(1, 2)
5+
| ~~~~~~~~~~~
6+
5 | a.count('')
7+
6 | a.count()
8+
vlib/v/checker/tests/array_count_err.vv:5:10: error: type mismatch, should use e.g. `count(it > 2)`
9+
3 | a.count(1)
10+
4 | a.count(1, 2)
11+
5 | a.count('')
12+
| ~~
13+
6 | a.count()
14+
7 | }
15+
vlib/v/checker/tests/array_count_err.vv:6:4: error: expected 1 argument, but got 0
16+
4 | a.count(1, 2)
17+
5 | a.count('')
18+
6 | a.count()
19+
| ~~~~~~~
20+
7 | }
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
fn main() {
2+
a := []int{}
3+
a.count(1)
4+
a.count(1, 2)
5+
a.count('')
6+
a.count()
7+
}

vlib/v/fmt/fmt.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2058,7 +2058,7 @@ fn (mut f Fmt) write_static_method(name string, short_name string) {
20582058
pub fn (mut f Fmt) call_expr(node ast.CallExpr) {
20592059
mut is_method_newline := false
20602060
if node.is_method {
2061-
if node.name in ['map', 'filter', 'all', 'any'] {
2061+
if node.name in ['map', 'filter', 'all', 'any', 'count'] {
20622062
f.in_lambda_depth++
20632063
defer { f.in_lambda_depth-- }
20642064
}

vlib/v/gen/c/array.v

Lines changed: 94 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ fn (mut g Gen) gen_array_map(node ast.CallExpr) {
558558
}
559559
}
560560
ast.CallExpr {
561-
if expr.name in ['map', 'filter', 'all', 'any'] {
561+
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
562562
is_embed_map_filter = true
563563
g.set_current_pos_as_last_stmt_pos()
564564
}
@@ -940,7 +940,7 @@ fn (mut g Gen) gen_array_filter(node ast.CallExpr) {
940940
}
941941
}
942942
ast.CallExpr {
943-
if expr.name in ['map', 'filter', 'all', 'any'] {
943+
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
944944
is_embed_map_filter = true
945945
g.set_current_pos_as_last_stmt_pos()
946946
}
@@ -1439,7 +1439,7 @@ fn (mut g Gen) gen_array_any(node ast.CallExpr) {
14391439
}
14401440
}
14411441
ast.CallExpr {
1442-
if expr.name in ['map', 'filter', 'all', 'any'] {
1442+
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
14431443
is_embed_map_filter = true
14441444
g.set_current_pos_as_last_stmt_pos()
14451445
}
@@ -1469,6 +1469,96 @@ fn (mut g Gen) gen_array_any(node ast.CallExpr) {
14691469
}
14701470
}
14711471

1472+
fn (mut g Gen) gen_array_count(node ast.CallExpr) {
1473+
past := g.past_tmp_var_new()
1474+
defer {
1475+
g.past_tmp_var_done(past)
1476+
}
1477+
1478+
sym := g.table.final_sym(node.left_type)
1479+
left_is_array := sym.kind == .array
1480+
elem_type := if left_is_array {
1481+
(sym.info as ast.Array).elem_type
1482+
} else {
1483+
(sym.info as ast.ArrayFixed).elem_type
1484+
}
1485+
elem_type_str := g.styp(elem_type)
1486+
has_infix_left_var_name := g.write_prepared_tmp_value(past.tmp_var, node, 'int', '0')
1487+
1488+
mut expr := node.args[0].expr
1489+
var_name := g.get_array_expr_param_name(mut expr)
1490+
1491+
mut closure_var := ''
1492+
if mut expr is ast.AnonFn {
1493+
if expr.inherited_vars.len > 0 {
1494+
closure_var = g.new_tmp_var()
1495+
g.declare_closure_fn(mut expr, closure_var)
1496+
}
1497+
}
1498+
i := g.new_tmp_var()
1499+
g.writeln('for (int ${i} = 0; ${i} < ${past.tmp_var}_len; ++${i}) {')
1500+
g.indent++
1501+
1502+
g.write_prepared_var(var_name, elem_type, elem_type_str, past.tmp_var, i, left_is_array)
1503+
g.set_current_pos_as_last_stmt_pos()
1504+
mut is_embed_map_filter := false
1505+
match mut expr {
1506+
ast.AnonFn {
1507+
g.write('if (')
1508+
if expr.inherited_vars.len > 0 {
1509+
g.write_closure_fn(mut expr, var_name, closure_var)
1510+
} else {
1511+
g.gen_anon_fn_decl(mut expr)
1512+
g.write('${expr.decl.name}(${var_name})')
1513+
}
1514+
}
1515+
ast.Ident {
1516+
g.write('if (')
1517+
if expr.kind == .function {
1518+
g.write('${c_name(expr.name)}(${var_name})')
1519+
} else if expr.kind == .variable {
1520+
var_info := expr.var_info()
1521+
sym_t := g.table.sym(var_info.typ)
1522+
if sym_t.kind == .function {
1523+
g.write('${c_name(expr.name)}(${var_name})')
1524+
} else {
1525+
g.expr(expr)
1526+
}
1527+
} else {
1528+
g.expr(expr)
1529+
}
1530+
}
1531+
ast.CallExpr {
1532+
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
1533+
is_embed_map_filter = true
1534+
g.set_current_pos_as_last_stmt_pos()
1535+
}
1536+
g.write('if (')
1537+
g.expr(expr)
1538+
}
1539+
ast.LambdaExpr {
1540+
g.write('if (')
1541+
g.expr(expr.expr)
1542+
}
1543+
else {
1544+
g.write('if (')
1545+
g.expr(expr)
1546+
}
1547+
}
1548+
g.writeln2(') {', '\t++${past.tmp_var};')
1549+
g.writeln('}')
1550+
g.indent--
1551+
g.writeln('}')
1552+
if !is_embed_map_filter {
1553+
g.set_current_pos_as_last_stmt_pos()
1554+
}
1555+
if has_infix_left_var_name {
1556+
g.indent--
1557+
g.writeln('}')
1558+
g.set_current_pos_as_last_stmt_pos()
1559+
}
1560+
}
1561+
14721562
fn (mut g Gen) gen_array_all(node ast.CallExpr) {
14731563
past := g.past_tmp_var_new()
14741564
defer {
@@ -1532,7 +1622,7 @@ fn (mut g Gen) gen_array_all(node ast.CallExpr) {
15321622
}
15331623
}
15341624
ast.CallExpr {
1535-
if expr.name in ['map', 'filter', 'all', 'any'] {
1625+
if expr.name in ['map', 'filter', 'all', 'any', 'count'] {
15361626
is_embed_map_filter = true
15371627
g.set_current_pos_as_last_stmt_pos()
15381628
}

vlib/v/gen/c/fn.v

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,6 +1155,9 @@ fn (mut g Gen) gen_array_method_call(node ast.CallExpr, left_type ast.Type, left
11551155
'any' {
11561156
g.gen_array_any(node)
11571157
}
1158+
'count' {
1159+
g.gen_array_count(node)
1160+
}
11581161
'all' {
11591162
g.gen_array_all(node)
11601163
}
@@ -1251,6 +1254,9 @@ fn (mut g Gen) gen_fixed_array_method_call(node ast.CallExpr, left_type ast.Type
12511254
'any' {
12521255
g.gen_array_any(node)
12531256
}
1257+
'count' {
1258+
g.gen_array_count(node)
1259+
}
12541260
'all' {
12551261
g.gen_array_all(node)
12561262
}

vlib/v/gen/c/infix.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1057,7 +1057,7 @@ fn (mut g Gen) need_tmp_var_in_array_call(node ast.Expr) bool {
10571057
match node {
10581058
ast.CallExpr {
10591059
if node.left_type != 0 && g.table.sym(node.left_type).kind == .array
1060-
&& node.name in ['all', 'any', 'filter', 'map'] {
1060+
&& node.name in ['all', 'any', 'filter', 'map', 'count'] {
10611061
return true
10621062
}
10631063
}

vlib/v/gen/golang/golang.v

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1419,7 +1419,7 @@ pub fn (mut f Gen) call_expr(node ast.CallExpr) {
14191419
// for arg in node.args {}
14201420
mut is_method_newline := false
14211421
if node.is_method {
1422-
if node.name in ['map', 'filter', 'all', 'any'] {
1422+
if node.name in ['map', 'filter', 'all', 'any', 'count'] {
14231423
f.in_lambda_depth++
14241424
defer {
14251425
f.in_lambda_depth--

0 commit comments

Comments
 (0)