Как перестать повторяться?

Advent of Code Day 1 требует зацикливания в той или иной форме длинной строки скобок, например ((((())(())(((()))(( и т. д. , Идея состоит в том, что ( поднимается на один «этаж», ) спускается на один этаж, а задачи состоят в том, чтобы напечатать

  1. первый индекс в строке, где номер этажа отрицательный и
  2. последний этаж, когда найден конец строки.

Императивное решение с циклом for простое (например, Python):

def main():
    flr = 0
    basement = False
    for idx, elt in enumerate(text):
        flr += {
            "(": 1,
            ")": -1
        }.get(elt)

        if flr < 0 and not basement:
            print("first basement pos:", idx + 1)
            basement = True

    print("final floor:", flr)

Рекурсивное функциональное решение немного сложнее, но все же не слишком сложно.

def worker(flr, txt, idx, basement):
    flr += {"(": 1, ")": -1}[ txt[0] ]

    if not (len(txt) - 1): return flr

    if flr < 0 and not basement:
        print("first basement floor index: ", idx + 1)
        basement = True

    return worker(flr, txt[1:], idx + 1, basement)


def starter(txt):
    flr, basement, idx = 0, False, 0
    return worker(flr, txt, idx, basement)


if __name__ == '__main__':
    __import__("sys").setrecursionlimit(int(1e5))
    print("final floor:", starter(text))

Оба они дают правильный вывод

first basement floor index:  1795
final floor: 74

при запуске против моего вызова.

за исключением того, что второй вариант глупый, потому что в Python нет оптимизации хвостовых вызовов, но это неважно

Как я могу реализовать любой из них в Factor? Это то, что меня смущает с тех пор, как я начал использовать Factor.

Мы не можем просто использовать цикл for, потому что нет эквивалента, позволяющего сохранять изменяемое состояние между итерациями.

Мы могли использовать рекурсивное решение:

: day-1-starter ( string -- final-floor )
  [ 0 ] dip 0 f day-1-worker 3drop "final floor: %s" printf ;

: day-1-worker 
  ( floor string index basement? -- floor string index basement? )
  day-1-worker  ! what goes here?
  ; recursive

Отлично, это скелет, но что входит в тело day-1-worker? В Factor нет никакого способа "досрочного возврата" из рекурсивного вызова, потому что нет способа запустить программу в обратном порядке и нет концепции возврата - это не имеет никакого смысла.

У меня такое чувство, что, возможно, рекурсия не является ответом на этот вопрос в Factor. Если это так, как мне остановить рекурсию?


person cat    schedule 01.07.2016    source источник
comment
adventofcode.com говорит, что создает разные данные для каждого пользователя, поэтому вам лучше просто вставить полученные данные. Я подумал о том, чтобы вставить пример ввода, например ((((())))))(())(((()))(( с последним этажом 2 и подвалом 11, но, возможно, ваш исходный ввод лучше, потому что он сообщает, как длинная входная строка может быть -- однако это не имеет отношения к вопросу, так что решать вам :)   -  person dercz    schedule 02.07.2016
comment
@dercz Ахахаха, мне никогда не приходило в голову, что ссылка предназначена для входа в систему. У меня длина 7009 символов (именно поэтому я использую sys.setrecursionlimit(10000)), поэтому я думаю, что мог бы включить его как одну длинную строку, но дело в том, что данные реализации верны.   -  person cat    schedule 02.07.2016


Ответы (3)


Прежде всего, рекурсия всегда является ответом :) Поскольку это проблема (и я не знаю фактора), просто подсказка: в вашем решении на Python вы использовали побочный эффект для печати первый цокольный уровень. Совсем ненужно! Вы также можете использовать аргумент basemet для хранения номера этажа, например:

    def worker(flr, txt, idx, basement):
        flr += {"(": 1, ")": -1}[ txt[0] ]

        if not (len(txt) - 1): return [flr, basement] # <- return both

        if flr < 0 and not basement:
            #print("first basement floor index: ", idx + 1) # side effects go away!
            basement = idx+1 # <- a number in not False, so that's all
        return worker(flr, txt[1:], idx + 1, basement)

Итак, теперь вы получаете

    final,first_basement = worker(0, txt, 0, False)

Или, в качестве альтернативы, вы можете написать 2 функции: первая ищет индекс первого цокольного этажа, а другая просто вычисляет последний этаж. Наличие ‹2000 дополнительных маленьких шагов не имеет большого значения, даже если вы заботитесь о производительности.

Удачи!

Изменить: что касается вашего вопроса о рекурсии в факторе, взгляните на функцию Аккермана. в Factor и последовательность Фибоначчи в Factor, и вы должны понять, как " разорвать петлю». Собственно проблема только в мышлении (освободитесь от императивной модели :)); в функциональных языках нет «возврата», только конечное значение, а языки на основе стека, которые вы упомянули, являются другой вычислительной моделью того же самого (вместо того, чтобы думать о сворачивании дерева, вы думаете о «нажатии и выталкивании в/из стеков " -- что, кстати, является распространенным способом реализации первого).

Редактировать: (СПОЙЛЕР!) Я установил Factor и начал с ним играть (довольно приятно), для первого вопроса (вычисление окончательного балла) возможное решение:

    : day-1-worker (  string floor -- floor )
      dup length 0 = 
       [ drop ]
       [ dup first 40 =
         [ swap 1 + ]
         [ swap 1 - ]
         if
         swap rest
         day-1-worker ]
       if ;

    : day-1-starter ( string -- floor )
      0 swap day-1-worker ;

Так что теперь вы можете либо написать аналогичный для вычисления индекса подвала, либо (что было бы круче!) изменить его так, чтобы он также управлял индексом и подвалом... (Возможно, использование cond было бы разумнее чем вложенные if).

person dercz    schedule 01.07.2016
comment
Вы абсолютно правы, и это было бы решением, например. Хаскелл. Проблема в том, что Factor на самом деле не имеет концепции возврата, он просто продолжает выполнять одно слово за другим, например Joy, поэтому я не знаю, как остановить рекурсию, когда я найду решение. - person cat; 01.07.2016
comment
но не может ли day1-worker быть чем-то вроде: ‹проверить, пуста ли строка (string -- bool)› [‹оставить результат›] [‹установить новые значения аргументов в стеке› day1-worker], если - person dercz; 01.07.2016
comment
Я проверил, да, это может быть что-то подобное :) еще раз отредактировал ответ. осторожно, это спойлер! это отвечает на ваш вопрос об остановке рекурсии? - person dercz; 02.07.2016
comment
рекурсия — это всегда ответ... да! ^_^ - person Mulan; 02.07.2016

Вы можете использовать комбинатор cum-sum:

: to-ups/downs ( str -- seq )
    [ CHAR: ( = 1 -1 ? ] { } map-as ;

: run-elevator ( str -- first-basement final-floor )
    to-ups/downs cum-sum [ -1 swap index 1 + ] [ last ] bi ;

IN: scratchpad "((())))(())(())(((()))((" run-elevator

--- Data stack:
7
2
person Björn Lindqvist    schedule 03.07.2016

РЕДАКТИРОВАТЬ

Изначально я неправильно понял, как вы вычисляли значение basement. Я обновил ответы ниже


Вот решение JavaScript. Извините, я понятия не имею, как это конвертируется в Factor. reduce — итеративный процесс

const worker = txt=>
  txt.split('').reduce(({floor, basement}, x, i)=> {
    if (x === '(')
      return {floor: floor + 1, basement}
    else if (basement === null && floor === 0)
      return {floor: floor - 1, basement: i}
    else
      return {floor: floor - 1, basement}
  }, {floor: 0, basement: null})
	
let {floor, basement} = worker('((((())(())(((()))((')
console.log(floor)    //=> 6
console.log(basement) //=> null; never reaches basement


Ответ выше зависит от некоторых .split и .reduce, которые могут отсутствовать в вашем языке. Вот еще одно решение, использующее Y-комбинатор и только встроенный substring (который есть в большинстве языков). Этот ответ также зависит от того, имеет ли ваш язык первоклассные функции.

const U = f=> f (f)
const Y = U (h=> f=> f (x=> h (h) (f) (x)))

const strhead = s=> s.substring(0,1)
const strtail = s=> s.substring(1)

const worker = Y (f=> ({floor, basement})=> i=> txt=> {
  // txt is empty string; return answer
  if (txt === '')
    return {floor, basement}

  // first char in txt is '(', increment the floor
  else if (strhead (txt) === '(')
    return f ({floor: floor + 1, basement}) (i+1) (strtail (txt))

  // if basement isn't set and we're on floor 0, we found the basement
  else if (basement === null && floor === 0)
    return f ({floor: floor - 1, basement: i}) (i+1) (strtail (txt))

  // we're already in the basement, go down another floor
  else
    return f ({floor: floor - 1, basement}) (i+1) (strtail (txt))
}) ({floor: 0, basement: null}) (0)

{
  let {floor, basement} = worker('((((())(())(((()))((')
  console.log(floor)    //=> 6
  console.log(basement) //=> null; never reaches basement
}

{
  let {floor, basement} = worker(')(((((')
  console.log(floor)    //=> 4
  console.log(basement) //=> 0
}

{
  let {floor, basement} = worker('((())))')
  console.log(floor)    //=> -1
  console.log(basement) //=> 6
}

person Mulan    schedule 02.07.2016
comment
респект за Ю! Я думаю, было бы еще веселее, если бы вы не называли его работником значения результата, а просто применяли его на месте — так как в этом сила Y. - person dercz; 02.07.2016
comment
@dercz хорошо, потому что я даю каррированную функцию Y, это позволяет мне частично применить ее. Таким образом, worker можно повторно использовать несколько раз с различными входными данными без необходимости повторять все Y(........................) каждый раз, когда вы хотите вычислить другую строку ((()))()(). Гораздо практичнее назвать частично примененный результат worker и иметь возможность использовать/читать/понимать его как обычную функцию. И тот факт, что это возможно, демонстрирует еще большую силу Y. - person Mulan; 02.07.2016
comment
@dercz Я отредактировал свой пост, чтобы продемонстрировать простое повторное использование worker - person Mulan; 02.07.2016