Python 2, 158157 bytes
def f(s,o=0,d=0,D={}):T=s,o,d;x=D[T]=D[T]if T in D else~o<0andelse~o and 0**o+sum(f(s[1:],cmp(c,"[")%-3-~o,d or cmp(c,s[0]))for c in"+,-.<>[]")if s else~d<0==o;return+x Stack Exchange network consists of 183 Q&A communities including Stack Overflow, the largest, most trusted online community for developers to learn, share their knowledge, and build their careers.
Visit Stack ExchangeStack Internal
Knowledge at work
Bring the best of human thought and AI automation together at your work.
Explore Stack Internaldef f(s,o=0,d=0,D={}):T=s,o,d;x=D[T]=D[T]if T in D else~o<0andelse~o and 0**o+sum(f(s[1:],cmp(c,"[")%-3-~o,d or cmp(c,s[0]))for c in"+,-.<>[]")if s else~d<0==o;return+x def f(s,o=0,d=0,D={}):T=s,o,d;x=D[T]=D[T]if T in D else~o<0and 0**o+sum(f(s[1:],cmp(c,"[")%-3-~o,d or cmp(c,s[0]))for c in"+,-.<>[]")if s else~d<0==o;return+x def f(s,o=0,d=0,D={}):T=s,o,d;x=D[T]=D[T]if T in D else~o and 0**o+sum(f(s[1:],cmp(c,"[")%-3-~o,d or cmp(c,s[0]))for c in"+,-.<>[]")if s else~d<0==o;return+x Still looks pretty golfable, but I'm posting this for now. It uses recursion with a bit of caching. Annoyingly, D.get doesn't short circuit for the caching, so I can't save 9 bytes that way...
Still looks pretty golfable, but I'm posting this for now. It uses recursion with a bit of caching.
Still looks pretty golfable, but I'm posting this for now. It uses recursion with a bit of caching. Annoyingly, D.get doesn't short circuit for the caching, so I can't save 9 bytes that way...
The mapping prioritises length first, then lexicographical order over the ordering "][><.-,+" (see output examples below). The main idea is to compare prefixes.
The variable o keeps track of the number of [ brackets still open for the current prefix, while the variable d takes one of three values indicating:
d = 1: The current prefix is lexicographically earlier than s. Add all programs with this prefix and length <= s,d = -1: The current prefix is lexicographically later than s. Add all programs with this prefix and length < s.d = 0: The current prefix is a prefix of s, so we might change d to 1 or -1 later. For example, if we have s = "[-]" and our current prefix is p = "+", since p is later than s lexicographically we know only to add the programs starting with p which are strictly shorter than s.
To give a more detailed example, suppose we have an input program s = "-[]". The first recursive expansion does this:
(o == 0) # Adds a program shorter than s if it's valid # For the first expansion, this is 1 for the empty program + f(s[1:], o=-1, d=1) # ']', o goes down by one due to closing bracket + f(s[1:], o=1, d=1) # '[', o goes up by one due to opening bracket + f(s[1:], o=0, d=1) # '>' + f(s[1:], o=0, d=1) # '<' + f(s[1:], o=0, d=1) # '.', d is set to 1 for this and the previous branches # since they are lexicographically earlier than s's first char + f(s[1:], o=0, d=0) # '-', d is still 0 since this is equal to s's first char + f(s[1:], o=0, d=-1) # ',', d is set to -1 for this and the later branches # since they are lexicographically later than s's first char + f(s[1:], o=0, d=-1) # '+' Note how we don't actually use the prefixes in the recursion - all we care about them is captured through the variables d, o and the shrinking input program s. You'll notice a lot of repetition above - this is where caching comes in, allowing us to process 100-char programs well within the time limit.
When s is empty, we look at (d>=0 and o==0), which decides whether to return 1 (count this program because it's lexicographically early/equal and the program is valid), or 0 (don't count this program).
Any situtation with o < 0 immediately returns 0, since any programs with this prefix have more ]s than [, and are thus invalid.
The first 20 outputs are:
o keep track of the number of open [ brackets, d can be in -1, 0, 1 depending on which programs we want to add, and D is a cache for storing intermediate results. I'll add a better explanation later, but the idea of d is to determine whether to add only programs which are shorter than s (current prefix is lexicographically higher than s), all programs with the current prefix (if current prefix is lexicographically lower than s), or unknown (current prefix is also a prefix of s).
The mapping prioritises length first, then lexicographical order over the ordering "][><.-,+". The first 20 outputs are:
o keep track of the number of open [ brackets, d can be in -1, 0, 1 depending on which programs we want to add, and D is a cache for storing intermediate results. I'll add a better explanation later, but the idea of d is to determine whether to add only programs which are shorter than s (current prefix is lexicographically higher than s), all programs with the current prefix (if current prefix is lexicographically lower than s), or unknown (current prefix is also a prefix of s).
The mapping prioritises length first, then lexicographical order over the ordering "][><.-,+" (see output examples below). The main idea is to compare prefixes.
The variable o keeps track of the number of [ brackets still open for the current prefix, while the variable d takes one of three values indicating:
d = 1: The current prefix is lexicographically earlier than s. Add all programs with this prefix and length <= s,d = -1: The current prefix is lexicographically later than s. Add all programs with this prefix and length < s.d = 0: The current prefix is a prefix of s, so we might change d to 1 or -1 later. For example, if we have s = "[-]" and our current prefix is p = "+", since p is later than s lexicographically we know only to add the programs starting with p which are strictly shorter than s.
To give a more detailed example, suppose we have an input program s = "-[]". The first recursive expansion does this:
(o == 0) # Adds a program shorter than s if it's valid # For the first expansion, this is 1 for the empty program + f(s[1:], o=-1, d=1) # ']', o goes down by one due to closing bracket + f(s[1:], o=1, d=1) # '[', o goes up by one due to opening bracket + f(s[1:], o=0, d=1) # '>' + f(s[1:], o=0, d=1) # '<' + f(s[1:], o=0, d=1) # '.', d is set to 1 for this and the previous branches # since they are lexicographically earlier than s's first char + f(s[1:], o=0, d=0) # '-', d is still 0 since this is equal to s's first char + f(s[1:], o=0, d=-1) # ',', d is set to -1 for this and the later branches # since they are lexicographically later than s's first char + f(s[1:], o=0, d=-1) # '+' Note how we don't actually use the prefixes in the recursion - all we care about them is captured through the variables d, o and the shrinking input program s. You'll notice a lot of repetition above - this is where caching comes in, allowing us to process 100-char programs well within the time limit.
When s is empty, we look at (d>=0 and o==0), which decides whether to return 1 (count this program because it's lexicographically early/equal and the program is valid), or 0 (don't count this program).
Any situtation with o < 0 immediately returns 0, since any programs with this prefix have more ]s than [, and are thus invalid.
The first 20 outputs are: