Maximum consecutive count in a list

If I have a field “outcome” containing “a” or “b”

producing a list like a b b a a a a b etc.

This will count the total number of occurrences of “a”:

{{{ [tag[A]outcome[a]]+[count[]] }}}  

How would I count the maximum consecutive number i.e. 4 in the above example?

Thanks
Jon

john,

In some ways what you are asking for is less mathematics and more string handling. I am curious why you may think getting this result would be of use to you? I am confident that it can be done but expect the answer to take some effort to write, so I won’t jump into it without a little more confidence.

The typical algorithm would be to count occurrences and reset the count on change eg from a to b, b to a. to detect a change you need to store the last value to compare.

  • the new %if statements within a list widget may be helpful.
  • perhaps you could generate substrings of a and b and then test length[] or split and count.
  • the list widget now has the counter parameter

post script

the new let and multi-value variables in the 5.4.0 prerelease may be of use here.

also depending on the nature of your output you could use the contains operator to test for different sized strings ‘aaaa’ ‘aaa’ etc…

I suspect your question is a stand-in for something more complex, but here’s one way to solve your example:

[[aaabbbbbbbbaaaaaaaaabbbbabab]search-replace:g[ab],[a|b]search-replace:g[ba],[b|a]split[|]length[]maxall[]]

Sorry, I should have been clearer - the outcome field is in individual tiddlers collecting data so I will have a series of tiddlers with each outcome field containing a or b

Perhaps no need to go into the detail, but this answer is definitely what I need but unfortunately the potential solutions are beyond me. I suspect if I copy and paste into a spreadsheet I could get the answer and do that process periodically, but it would have been nice to have it within the wiki and producing the result continually.

Gotcha, if you want to use then what I sent, you’d just do a +[join[]] first then?

Apologies in advance if your solution is ‘pearls before swine’, but what would it be searching for (and what is the g - e.g. g[ab])?

If you’re literally talking about outcomes of “a” and “b”, then something like this should work:

{{{ [tag[A]get[outcome]join[]splitregexp[b+]] :map[split[]count[]] +[maxall[]] }}}

We join together the outcome strings, then split on any run of b's. This leaves us with a list of runs of a's. We take the length of each run and return the maximum value of all of them.

We could make this slightly more generic, where we don’t know about b, but only not-a with this version:

<$let re="[^a]+">
{{{ [tag[A]get[outcome]join[]splitregexp<re>] :map[split[]count[]] +[maxall[]] }}}
</$let>

But this technique only works if your outcome values are single characters.

For something more generic, I’m sure we could write a procedure that finds the longest contiguous group of a given title in a title-list. But, as big a fan as I am of recursion, I still struggle to turn simple recursive JS functions into TW procedures or functions.

If someone else wants to have a go, here’s a pretty simple JS version:

const maxContiguous = (x, xs, m = 0) =>
  xs.length === 0
    ? m
  : x === xs[0] 
    ? maxContiguous (x, xs.slice(1), m + 1)
  : Math.max(m, maxContiguous(x, xs.slice(1), 0))

maxContiguous ('a', 'abaabbbabaaaaabaaabaaa') //=> 5
2 Likes

Ah, with a search, I see the need to know what is being searched for when things are being split up - the actual content of the field outcome would be “win” and “loss”. I was thinking keeping it more generic would be easier - sorry for any confusion.

…thinking about it, I could just do a bulk change of the field contents to “W” and “L”. I’ll have a go with your suggestion later - thanks.

I did assume that was the case, and we can modify this technique to handle your update:

{{{ [tag[A]get[outcome]join[]splitregexp[(loss)+]] :map[split[]count[]] +[maxall[]divide[3]] }}}

(The divide[3] is because there are three characters in "win".)

This is fragile, in that it expects every tiddler tagged A to have a outcome field with value of either win or loss (no blanks, no ties, no missing fields.) But that might still be reasonable.

I would like to find a way to write a procedure for this along the lines of the JS sample I sent, but I don’t have time to play with that now.

Ah, great. So is that finding the maximum consecutive numbers of loss?

And to find the same for win, it would be a search for (win) and divide[4]?

… I’ll go through my data to check

Not quite. This counts wins. It’s actually splitting the string "winwinlosslosslosswinlosswinwinwinwinlosswin at every run of losses, leaving you with ["winwin", "win", "winwinwinwin", "win"], counting characters to get [6, 3, 12, 3], taking the max, 12, and dividing by 3 to get 4. The maximum streak of wins is 4.

By splitting at (win)+ and dividing by 4, you would get the longest streak of losses.

MaxContiguous.json (2.9 KB)

Mind:Blown!

Oh dear - just realised I will need another. So there would be 3 possibilities: ‘win’, ‘loss’ and ‘even’ :lying_face:

We can continue this for a while, but with the | required in the regex, we will need to make it a variable:

<$let re="(loss|even)+">
Maximum winning streak: {{{ [tag[A]get[outcome]join[]splitregexp<re>] :map[split[]count[]] +[maxall[]divide[3]] }}}
</$let>

and of course,

<$let re="(win|even)+">
Maximum losing streak: {{{ [tag[A]get[outcome]join[]splitregexp<re>] :map[split[]count[]] +[maxall[]divide[4]] }}}
</$let>

and

<$let re="(win|loss)+">
Maximum tying streak: {{{ [tag[A]get[outcome]join[]splitregexp<re>] :map[split[]count[]] +[maxall[]divide[4]] }}}
</$let>

You can test this by downloading the following and dragging the resulting file onto a wiki:

MaxContiguous_2.json (4.0 KB)

Wow, thanks Scott - that is amazing!

It’s going to be a massive help to what I’m doing.

I’ve put it in my wiki and it’s working perfectly.

And thanks for your patience! :crazy_face:

Jon

1 Like

since I now know what you are analysing beware “the gamblers fallacy” :wink:

More like a weighted coin, a loaded dice and the data revealing the edge! :wink:

1 Like

A slight variation of Scott’s solution:

<$let outcome="aaccabbbbbccccbbbaacacaaccaaaccccccccaaabbcbbabab"
          re="(a+|b+|c+)"
>
outcome: <<outcome>>  <br>
Consecutive string of ( "a" or "b" or "c" ) with a trailing space: {{{ [<outcome>search-replace:g:regexp<re>,[$1 ]] }}} <br>
Length of each consecutive string: {{{ [<outcome>search-replace:g:regexp<re>,[$1 ]split[ ]length[]butlast[]join[ ]] }}} <br>
Max length: {{{ [<outcome>search-replace:g:regexp<re>,[$1 ]split[ ]length[]maxall[]] }}} <br>
</$let>

Output:

outcome: aaccabbbbbccccbbbaacacaaccaaaccccccccaaabbcbbabab
Consecutive string of  ( "a" or "b" or "c" ) with a trailing space: aa cc a bbbbb cccc bbb aa c a c aa cc aaa cccccccc aaa bb c bb a b a b
Length of each consecutive string: 2 2 1 5 4 3 2 1 1 1 2 2 3 8 3 2 1 2 1 1 1 1
Max length: 8

This sort of works with the words “win”,“loss” and “even” also.

<$let outcome="winwinevenevenwinlosslosslosslosslossevenevenevenevenlosslosslosswinwinevenwinevenwinwinevenevenwin"
          re="((win)+|(loss)+|(even)+)"
>
outcome: <<outcome>>  <br>
Consecutive string of keywords : {{{ [<outcome>search-replace:g:regexp<re>,[$1 ]] }}} <br>

Output:

outcome: winwinevenevenwinlosslosslosslosslossevenevenevenevenlosslosslosswinwinevenwinevenwinwinevenevenwin
Consecutive string of keywords : winwin eveneven win losslosslosslossloss eveneveneveneven losslossloss winwin even win even winwin eveneven win

But it requires three global search-replace for the three keywords into a different character for the counting. So maybe better to perform the three global search-replace right at the beginning into a different character each, and just use the first solution.

2 Likes