Array.count in Belt.Array

I need to count matching elements in an array and have two approaches:

// 1 use a reduce to keep from allocating an array
let count = arr -> Array.reduce(0, (count, x) => predicate(x) ? count + 1 : count)

// 2 filter the array, then measure it
let count = arr -> Array.keep(predicate) -> Array.length()

#1 uses a reduce and is a bomb when I need a hammer.
Neither feels as clean as having Array.count(arr, predicate) be a top level function exposed in Belt.

#2 is fine but allocates an array unnecessarily

My question is: is there a reason something as useful as counting is not a first class citizen in StdLib?

Thanks

To minimize allocations, you can also use a for loop:

let count = (a, f) => {
  let result = ref(0)
  for i in 0 to Js.Array.length(a) - 1 {
    if f(Js.Array.unsafe_get(a, i)) {
      result := succ(result.contents)
    }
  }
  result.contents
}

You can use forEach to accomplish the same thing, but I suspect that the loop version would be slightly more performant.

And I don’t have an answer to your question, but it’s possible that just no one has thought to add it yet.

2 Likes

Thanks!

I didn’t know ReScript had for loop syntax! :man_facepalming:

I’ve been exclusively using the Belt.Array and Js.Array2 functions.

There’s nothing necessarily wrong with using those functions! If you’re using lots of immutable structures then you probably won’t use imperative loops much. Using Belt.Array.forEach / Js.Array.forEach might look slightly nicer since you don’t need to manually keep track of indexes and use unsafe_get each time. But loops are still there for when you want to use them. Belt.Array uses them a lot under the hood IIRC.

Docs page on loops.

2 Likes

result := succ(result.contents)

you can replace it with incr(result)

3 Likes

I didn’t know of the existence of this built-in!
I suspect this is the same for more users, this might be a good addition to the mutation doc :thinking: