`(accumulate 1) ... (into $)` together with `*(repeat forever) ... (fail)`?

I am trying to write a program that receives a stream of data as user input, runs a query on each element, and then I’m interested in counting how many of the elements the query succeeds for. To use a minimal example, here’s something that counts the even numbers in the stream:

(program entry point)
  (count-evens 0 $Final)
  Found $Final even numbers.

(count-evens $Current $Final)
  {
    (get input [$Number | $])
    ($Number modulo 2 into $Rem)
    {
      ($Rem = 0)
      ($Current plus 1 into $Next)
      (count-evens $Next $Final)
    (or)
      (count-evens $Current $Final)
    }
  (or)
    ($Final = $Current)
  }

This could be written any number of ways, but this is one of them. If it’s easier to read, we can get out of the middle disjunction with arithmetic:

(count-evens $Current $Final)
  {
    (get input [$Number | $])
    ($Number modulo 2 into $Rem)
    (1 minus $Rem into $This)
    ($Current plus $This into $Next)
    (count-evens $Next $Final)
  (or)
    ($Final = $Current)
  }

This, as discovered, does not scale up to very many iterations: the program runs out of heap after less than 200 iterations.

If all the numbers were in a list, it would have been easy to use (accumulate 1) … (into $) to solve this:

(count-evens $List $Final)
  (accumulate 1)
    *($Number is one of $List)
    ($Number modulo 2 into $Rem)
    ($Rem = 0)
  (into $Final)

That would presumably be more heap-efficient, but it does require the full list to be instantiated, as far as I understand it.

In order to process the values in a streaming fashion, I think one needs *(repeat forever) … (fail). However, I cannot figure out how to get this to play nicely with (accumulate 1) … (into $).

Am I trying the impossible or does anyone have good ideas?

I think this is a place where you need to use the long-term heap to stash data while clearing the main heap.

(global variable (result 0))

(program entry point)
    *(repeat forever)
    (get input $Input)
    (if) ($Input = [$Number]) (number $Number) (then)
         (if) ($Number modulo 2 into 0) (then)
             (result $Old)
             ($Old plus 1 into $New)
             (now) (result $New)
         (endif)
         (fail)
    (endif)

As you can see, doing any sort of number manipulation in Dialog is a pain! It’s possible, but often a headache. It’s much better at working with lists and objects.

2 Likes

Now, you might be able to do this:

(program entry point)
    (accumulate 1)
        (stoppable) {
            *(repeat forever)
            (get input $Input)
            (if) ($Input = [$Number]) (number $Number) (then)
                ($Number modulo 2 into 0)
            (else)
                (stop)
            (endif)
        }
    (into $Result)

I suspect this will work, but can’t test it on my phone. (Even typing it is hard!) The trick is escaping from the *(repeat forever), because (accumulate $) exhausts its child, so succeeding isn’t enough.

1 Like

Hm. Maybe someone with a higher level of wizardry than I could get it to work, but none of the variations of it I’ve tried have worked. It stops after receiving the number 1, which gets counted among the even numbers.

Ohh. Of course. Mutable variables! I forgot Dialog has them! Thank you.

When trying to get this work I discovered the initial value of the variable needs to be set to zero here, just in case someone else finds their way here through a web search.

Oops, yes. Edited!

1 Like