Юлия, 90 знаков
s->all(cumsum(map(i->i∈"!|.\":"?-1:i∈"()_@\$&",mapfoldl(collect,hcat,split(s,"
")))').>-1)
В отличие от исходного решения (ниже), здесь используется математика для определения решения. mapfoldl(collect,hcat,split(s,"\n"))(написано выше с \nзаменой фактической новой строки для сохранения символов) преобразует строку в 2d массив символов. map(i->i∈"!|.\":"?-1:i∈"()_@\$&",...)создает массив чисел, с 1, если символ является облаком, -1, если символ дождь, и 0 в противном случае.
cumsum(...')вычисляет кумулятивные суммы строк (обычно записывается cumsum(...,2), но, поскольку с этого момента мы не заботимся об ориентации, транспонирование стоит только один символ), а затем all(... .>-1)проверяет наличие отрицательного числа - отрицания будут появляться только в том случае, если символ дождя появляется без предшествующего облачного персонажа.
Юлия, 139 136 символов
s->(t=join(mapfoldl(i->split(i,""),.*,split(s,"
")),"
");while t!=(t=replace(t,r"[()_@$&](.*?)[!|.\":]",s"\g<1>"))end;∩("!|.\":",t)==[])
Эта функция сначала транспонирует текст так, что строки становятся столбцами, и наоборот. Обратите внимание, что новые строки присутствуют в коде в виде реальных новых строк, чтобы сохранить один символ на экземпляр.
Затем функция итеративно заменяет пары облако / капля пробелами, и как только все такие пары удаляются, она возвращает true, если есть какие-либо оставшиеся капли, и false в противном случае.
r"[()_@$&](.*?)[!|.\":]"- это регулярное выражение, которое будет лениво сопоставлять пары облако / капелька, а группа 1 содержит все, что находится между облаком и каплей. Затем s"\g<1>"говорит ему удалить совпадающее облако и капли, но держать материал между ними (необходимо, поскольку он может содержать облака) - \g<1>это то, что было найдено в группе 1 регулярного выражения. ∩("!|.\":",t)==[]сгенерирует пересечение символов капли с последней строкой, и если она пуста, то ни один из символов капли не присутствует, и идет дождь.