dc unix: сохранение начального значения даже после сохранения нового значения по тому же индексу массива

Вопрос или проблема

Я не могу понять этот пример, приведенный в руководстве dc:

$ dc  
 1 0:a 0Sa 2 0:a La 0;ap  
 1  

На мой взгляд, ответ должен быть 2, потому что:

  1. 1 0:a
    Здесь мы сохраняем 1 на нулевой позиции массива a.

  2. 0Sa
    Теперь мы добавляем 0 в стек регистра a.

  3. 2 0:a
    Теперь снова сохраняем 2 на нулевой позиции массива a, тем самым перезаписывая предыдущее значение 1, сохраненное в этом месте.

  4. La
    Теперь мы удаляем 0, сохраненное в стеке регистра a, и добавляем его в основной стек.

  5. 0;a
    Теперь мы снова добавляем 0 в основной стек, а затем используем его как индекс массива и добавляем 2, сохраненное на нулевой позиции массива a, в основной стек.

  6. p
    Теперь мы выводим на печать верхнее значение основного стека, которое равно 2. Следовательно, ответ должен быть 2.

Что я упускаю?

PS- Я хотел использовать dc как тег, но, похоже, его не существует, и обязательно нужно использовать хотя бы один тег, поэтому я использовал debian (операционная система моего рабочего места).

Как в смешивании массивов и стеков. В примере регистр a используется как массив и как стек.

1 0:a 
0 Sa 
2 0:a 
La 
0;a p
  1. Сначала :a – регистр a рассматривается как массив.
  2. Затем Sa – регистр a рассматривается как стек. По сути, мы сдвигаем массив из первого пункта вниз и создаем новый массив. Как отмечается в man: Обратите внимание, что каждый экземпляр регистра имеет свой собственный связанный массив.
  3. Затем :a – регистр a снова рассматривается как массив. Мы сдвигаем предыдущее значение и первое значение массива вниз.
  4. Затем La – регистр a рассматривается как стек. Чтобы добраться до первого сложенного массива, выбрасываем a[0]=2, так как это массив.
  5. Затем ;a – регистр a снова рассматривается как массив. Теперь осталось только одно значение, первое значение массива, добавленное в a, которое равно 1.

Смотрите внизу ответа для некоторых дополнительных примеров.


По комментарию:

«Сколько массивов и стеков у каждого регистра? Я думал, что у одного регистра есть один стек и один отдельный массив.»

Стек:

Стек в dc – это стек с последним пришедшим, первым вышедшим (LIFO). То же, что и тарелки в ресторане.

      ,------ pop / push - взять / оставить 
    /
   |
   v
[-----] верх
[-----]           ...                 ...                .
[-----]          [-----]             [-----]            ..
 (основной)        (регистр-a)        (регистр-b)        ...

У нас есть основной стек или рабочий стек, который используется, если не указана операция, требующая регистра. Каждый регистр имеет свой собственный стек.’

Основные операции с регистрами:

  • Sr: извлечение одного значения из основного стека и добавление его в стек,specified by register r. Оба стека модифицируются.
  • Lr: извлечение одного значения из стека регистра, указанного в r, и добавление его в основной стек. Оба стека модифицируются.
  • sr: извлечение одного значения из основного стека и запись его в регистр r. По сути, изменяет верхнее значение в стеке, указанном регистром r. Если в этом стеке нет значения – добавляет его. Основной стек модифицируется. Стек регистра сохраняется, кроме измененного значения.
  • lr: чтение значения из регистра r и добавление его в основной стек. По сути, читать верхнее значение, если есть несколько. Основной стек изменен. Стек регистра сохраняется.
  • :r: извлечение двух значений из основного стека и использование первого, верхнего, как индекса для сохранения второго значения в массиве, указанном в регистре r. Основной стек изменен. Стек регистра сохраняется.
  • ;r: извлечение одного значения из основного стека и использование его как индекса для чтения текущего массива в регистре, указанном в r. Основной стек изменен. Стек регистра сохраняется.

Смешивание стеков и массивов

На это можно посмотреть парами. Когда вы начинаете, все регистры пусты. Когда вы добавляете элемент в стек с помощью Sr, вы скрываете любые основные элементы в этом стеке. Скажем, вы делаете:

1 Sx
2 Sx
4 Sx

x = (S)  4     ВИДИМ
    (S)  2     СКРЫТО
    (S)  1     СКРЫТО

Теперь вы можете изменить значение в регистре x, т.е. изменить верхний элемент, с помощью sx, и вы можете читать с помощью lx – без изменения количества элементов в стеке:

lx p  # Чтение значения в регистре x - по сути чтение верхнего элемента из стека.
4     # Напечатанное значение с помощью p.
3 sx  # Изменение значения в регистре x - по сути изменение верхнего элемента в стеке.

x = (S)  3     ВИДИМ
    (S)  2     СКРЫТО
    (S)  1     СКРЫТО

Если вы решите добавить элементы массива, дело начинает принимать более сложный оборот.

4 1:x
5 2:x
    
x = [A] 
        [2]=5  ВИДИМ
        [1]=4  ВИДИМ
    (S)  3     ВИДИМ
    (S)  2     СКРЫТО
    (S)  1     СКРЫТО

Теперь мы добавили значения в текущий массив в стеке. Мы можем читать и изменять любые ВИДИМЫЕ элементы.

44 1:x
55 2:x
33 sx

1;x p # Чтение и печать индекса 1
44

lx p  # Чтение и печать верхнего стека.
33

x = [A] 
        [2]=55  ВИДИМ
        [1]=44  ВИДИМ
    (S)  33     ВИДИМ
    (S)  2      СКРЫТО
    (S)  1      СКРЫТО

Если затем мы добавим элемент стека, можно сказать, что рамка стека нераздвижна, так как мы добавили значения в массив выше. Таким образом добавляется новая рамка стека.

6 Sx
7 Sx

x = (S)  7      ВИДИМ
    (S)  6      СКРЫТО
    [A] 
        [2]=55  СКРЫТО
        [1]=44  СКРЫТО
    (S)  33     СКРЫТО
    (S)  2      СКРЫТО
    (S)  1      СКРЫТО

Если теперь мы попробуем получить доступ к последнему массиву, он будет скрыт. По сути, мы читаем из пустого массива, и результатом будет значение по умолчанию 0. Мы можем модифицировать значение регистра 7 с помощью sr, но не можем получить доступ к массиву на два уровня вниз, если не избавимся от двух элементов стека выше.

Если мы теперь решим добавить несколько элементов массива, они добавляются в новый массив, расположенный (как парный массив) с верхним элементом стека.

8 1:x
9 2:x

x = [A]
        [2]=9   ВИДИМ
        [1]=8   ВИДИМ
    (S)  7      ВИДИМ
    (S)  6      СКРЫТО
    [A] 
        [2]=55  СКРЫТО
        [1]=44  СКРЫТО
    (S)  33     СКРЫТО
    (S)  2      СКРЫТО
    (S)  1      СКРЫТО

Теперь если мы сделаем извлечение стека, мы удалим 7, но так как между ними есть массив (так сказать), он также удаляется.

Lx p  # Извлечение + печать верхнего элемента стека.
7     # Напечатанное значение.

x = (S)  6      ВИДИМ
    [A] 
        [2]=55  СКРЫТО
        [1]=44  СКРЫТО
    (S)  33     СКРЫТО
    (S)  2      СКРЫТО
    (S)  1      СКРЫТО

Массив с 8 и 9 исчез. Элемент стека со значением 6 видим. Но подлежащий массив заблокирован. Чтение через 1;x p даст 0.

В некотором смысле можно сказать, что элементы стека блокируют, а массивы непрозрачны. Массивы как бы висят на элементах стека.

Нам нужно сделать еще одно извлечение из стека, чтобы раскрыть нижний элемент стека + массив.

Lx p  # Извлечение + печать верхнего элемента стека.
6     # Напечатанное значение.

x = [A] 
        [2]=55  ВИДИМ
        [1]=44  ВИДИМ
    (S)  33     ВИДИМ
    (S)  2      СКРЫТО
    (S)  1      СКРЫТО

В заключение можно сказать, что количество массивов и стеков не ограничивается одним на каждый регистр.

Сколько массивов и стеков у каждого регистра?
– Зависит от того, сколько чередующихся Sr и :r операций вы выполняете с этим регистром.

Другой способ посмотреть на это – что существует всего один стек, но множество массивов если мы добавим элементы стека между добавлением элементов массива …

Еще один способ – сказать, что в любой момент времени текущий массив не является

[регистр][массив]

а

[регистр][элемент стека][массив]

что дает:

[регистр][элемент стека][массив][...]
[регистр][элемент стека][массив][1]
[регистр][элемент стека][массив][0]

и что часть элемента стека непрозрачна, только для чтения и т.д. Хотя в этом случае нам также нужно помнить, что нам не нужно значение для элемента стека. Можно добавлять только значения массива в регистр.

Или каждый элемент стека парный с массивом, заполненным нулями, который мы можем изменять:

1 Sx
2 Sx
3 Sx
4 1:x
5 2:x
6 Sx
7 Sx
8 1:x
9 2:x

x = (S)  7   +   A[0]=0   A[1]=8   A[2]=9   A[3]=0  ...  A[2048]=0
    (S)  6   +   A[0]=0             ...                  A[2048]=0
    (S)  3   +   A[0]=0   A[1]=4   A[2]=5   A[3]=0  ...  A[2048]=0
    (S)  2   +   A[0]=0             ...                  A[2048]=0
    (S)  1   +   A[0]=0             ...                  A[2048]=0

Надеюсь, это немного прояснит ситуацию.


Некоторые примеры


$ dc
[ein]  0:a
[zwei] Sa
[drei] 0:a

0;ap   # Копировать + напечатать индекс 0 самого верхнего массива в регистре a
drei

Lap    # Выбрасывает [drei] и извлекает + печатает первый элемент в стеке*
zwei

0;ap   # Копировать + напечатать индекс 0 первого массива 
ein

$ dc
[uno]    0:a  # Массив a(1)
[dos]    1:a  # Массив a(1)
[tres]   2:a  # Массив a(1)

[cuatro] Sa   # Массив a(2)
[cinco]  Sa   # Массив a(2)
[seis]   Sa   # Массив a(2)

[siete]  0:a  # Массив a(3)
[ocho]   1:a  # Массив a(3)
[nueve]  2:a  # Массив a(3)

Laf      # Выбрасывает массив 3, чтобы добраться до первого массива стека, массива 2.
seis

Laf
cinco
seis

Laf
cuatro
cinco
seis

2;af      # Теперь мы на первом массиве, массиве 1.
tres
cuatro
cinco
seis

1;af
dos
tres
cuatro
cinco
seis

0;af
uno
dos
tres
cuatro
cinco
seis

Вы должны помнить, что dc – это компилятор – и довольно старый. Это своего рода машинный язык – калькулятор с ориентированным стеком. Он довольно мощный, но его интерфейс не предназначен для вас – он предназначен для эффективной обработки инструкций, которые вы пишете на каком-то другом языке после того, как вся пользовательская дружелюбность была отмашен с него.

dc не хранит данные интуитивно. dc нажимает вещи. Когда он читает новый ввод, если он немедленно не выполняется, значение просто сбрасывается в стек. Все, что уже в стеке, сдвигается вниз – самое новое наверху и самое старое внизу. Вам нужно обработать самые новые элементы стека, чтобы извлечь самые старые.

Многие люди могут понять это. Стек не так уж странен, в конце концов – это как корзина для белья. Но это довольно однорядно, и dc уходит гораздо дальше.

Итак, существует стек ввода – основной стек. Это то, куда по умолчанию попадают все инструктивные ввод и вывод в dc. Это то, с чем вы работаете, когда даете ему команды. Но также есть все остальные регистры – по крайней мере 256 из них. Каждый из них также является стеком сам по себе.

Вы обычно работаете с регистрами с помощью команд [Ss]охранить и [Ll]агрузить. Чтобы сохранить верхнее значение на основном стеке как скалярное значение в регистре a, вы выполняете sa. Затем вы можете l загрузить это скалярное значение обратно в верхнюю часть основного стека в любое время с помощью la. Ну, вы можете, пока текущий экземпляр регистра a остается текущим, то есть.

Чтобы создать новый скалярный экземпляр регистра a поверх старого — где ваше скалярное значение остается — вы можете использовать Sa. Эта команда извлекает основной стек и складывает стек регистра a. В отличие от la, команда La является разрушающей Lагрузкой регистра – когда скалярное значение извлекается в основной стек с помощью этой команды, все в этом экземпляре регистра уничтожается, и любой предыдущий экземпляр регистра снова становится доступным.

Этого тоже достаточно легко понять с небольшим опытом – это просто как основной стек, но один для каждого регистра. Но есть еще одно измерение для каждого регистра – их массивы.

Каждый экземпляр каждого регистра получает массив. Я думаю, что размер по умолчанию составляет 2048 индексов на каждый – хотя я часто задавался вопросом, насколько глубоки стеки, и могу только сказать, что это довольно глубоко. Когда вы создаете новый скалярный экземпляр для регистра, вы не просто добавляете его скалярное значение, но также добавляете его массив. Новый экземпляр имеет новый массив, а массив старого экземпляра остается нетронутым и будет так же доступен вам, как и скалярное значение после того, как вы извлечете новый экземпляр.

Доступ к индексам массива немного сложен. В первую очередь почти все операции со стеком основного являются разрушительными. И единственный способ получить доступ к индексу массива – это извлечь его значение из основного стека, а затем вызвать его. Поскольку ваша ссылка на индекс на этом этапе уничтожается, ее может быть трудно вспомнить. o и O могут быть полезны для сохранения счетчика индекса – но не пытайтесь использовать их с индексами 1 или 0.

В любом случае, массив регистра и его стек не независимы, а взаимозависимы и многомерны. С практикой, терпением и немного сверхчеловеческой решимостью можно делать странные и крутые вещи с этим. Удачи.

Я столкнулся с тем же вопросом, но внимательно перечитать текст вокруг примера в самом руководстве было достаточно:

   :r     Извлечет  верхние два значения  из стека.  Старое
          второе по высоте значение будет сохранено в массиве  r,
          индексируемом старым верхним значением стека.

   ;r     Извлекает верхнее значение стека и использует его как индекс
          в массиве r. Затем выбранное значение добавляется в стек.

   Обратите внимание, что каждый сложенный экземпляр  регистра  имеет
   свой  собственный  массив, связанный  с  ним.   Таким образом,  1  0:a 0Sa 2 0:a La 0;ap распечатает 1,
   потому что 2 было сохранено в экземпляре 0:a, который позже
   был извлечен.

Последний абзац здесь полностью отвечает на ваш вопрос. 2 было сохранено в экземпляре 0:a, который позже был извлечен (и тихо отбрасывался, когда La положила скалярное значение из регистра a в основной стек).

Ответ или решение

Вопрос, который вы подняли, касается работы с массивами и стеком в утилите dc (долгосрочный калькулятор с произвольной точностью). На первый взгляд, поведение этой утилиты может показаться запутанным, особенно в ситуации, когда нужно сохранить исходное значение в массиве после перезаписи по тому же индексу.

Давайте разберем это на примере из мануала:

1 0:a 0Sa 2 0:a La 0;ap

Пошаговый разбор

  1. 1 0:a

    • Здесь мы записываем значение 1 по индексу 0 в массив a. Массив a теперь выглядит следующим образом: a[0] = 1.
  2. 0Sa

    • Эта команда сохраняет верхнее значение стека, равное 0, в стек для массива a. Теперь внутри a добавилось дополнительное значение, связанное с текущей сессией стека.
  3. 2 0:a

    • Повторная запись по индексу 0, где мы перезаписываем значение на 2. Теперь массив a выглядит так: a[0] = 2.
  4. La

    • Похоже, в этот момент мы извлекаем верхнее значение из стека массива a, равное 0, и помещаем его на основной стек. После выполнения этой команды значение a[0], равное 2, было вытолкнуто из стека, и теперь мы можем работать с последней версией массива.
  5. 0;ap

    • Сначала мы помещаем 0 на основной стек и затем используем его для доступа к массиву a по индексу 0. На этом этапе мы ищем значение a[0], который равен 2, и выводим его через команду p.

Ключевые моменты

Секрет, который вы, кажется, упустили, заключается в том, что La был разрушительным для текущей инстанции массива a и удалил 2, в то время как извлекло 0 в основной стек. Однако сама структура массивов в dc позволяет обеспечить сохранность данных между вызовами.

Каждая инстанция регистра a создаёт новый массив, поэтому новый массив, который был создан с помощью команды 0:a, создаёт ситуацию, при которой более ранние значения массива больше не доступны после извлечения.

Если разбирать детали:

  • Когда вы вызываете 0:a, вы создаете новый массив, который перекрывает предыдущий, но значения по индексу 0 в предыдущем экземпляре не просто теряются.
  • Поскольку после команды La мы вновь "попадаем" в более раннюю версию массива, когда на самом верхнем уровне стека было значение 1, вывод будет именно таковым.

Заключение

В результате, ответ 1 следует откуда-то из ранее сохранённого значения. Вы действительно не потеряли данные, так как утилита dc хранит состояние и значения в своей многомерной структуре, которая позволяет вам выполнять сложные операции.

Если у вас возникли ещё вопросы об использовании dc или других возможностей утилиты, не стесняйтесь спрашивать!

Оцените материал
Добавить комментарий

Капча загружается...