But the length of the string is arbitrary I can't use any sort of length.This should do it, if I understood your requirements correctly:
${${(M)string:#cat(|s*)}[1,4]}
That tests fine. Too bad what Mark said about using the '$' to test for the end of the string seems not to work, it would have been simpler. In my actual situation I'm testing for a comma then a number, and only a number, either at the end of the string OR followed by another comma and then anything or nothing else:
Another option would be to capture the interesting part of the match explicitly (requires extended_glob):
[[ $string == (#b)((cat)|(cats)*) ]] && out=$match[2]$match[3] || out=