Showing posts with label haskell. Show all posts
Showing posts with label haskell. Show all posts

Wednesday, 18 May 2011

Проверка списка на наличие цикла в O(1) по памяти

Есть произвольный односвязный список, в котором может быть петля, необходимо предложить алгоритм который бы позволил определить, есть ли цикл в O(1) по памяти.

Не то, что бы эта задача была сложной, но она по своему довольно интересна. Ниже приведено два подхода к решению этой задачки. Первое из них является классическим решением, второй подход пораждён мои больным сознанием по мотивам решения задачки по Обходу бинарного дерева с константным количеством памяти.

Подход правильный.
При анализе того, как решить эту задачу очень быстро приходишь к мысли, что необходимо где-то в списке оставить маркер, который позволит понять, что мы в этом узле уже были. Очень быстро становится очевидно, что нет никакой возможности оставить маркер гарантированно на одном из улов петли. Решить эту проблему можно следующим образом: пустить по систем в параллель несколько маркеров с разной скоростью. Таким образом, более быстрый маркер должен дойти дойти либо до конца списка, либо рано или поздно догнать медленный маркер.

Реализация на С:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

struct List {
  struct List *next;
  int value;
};

struct List* makeList(int all, int inLoop) {
  struct List *head = malloc(sizeof(struct List));
  struct List *current = head;
  int marker = 1;
  for (int i = 0; i < all - inLoop; ++i) {
    current = current->next = malloc(sizeof(struct List));
    current->value = marker++;
  }
  if (inLoop > 0) {
    struct List *loopStart = current;
    for (int i = 0; i < inLoop; ++i) {
      current = current->next = malloc(sizeof(struct List));
      current->value = marker++;
    }
    current->next = loopStart->next;
  }
  return head;
}

bool loopInList(struct List *head) {
  struct List *fastMarker, *slowMarker;
  fastMarker = slowMarker = head;
  if (fastMarker->next == NULL) return true;
  else fastMarker = fastMarker->next;

  while (true) {
    if (fastMarker->next == NULL ||
        fastMarker->next->next == NULL)
      return false;
    if (fastMarker->next == slowMarker ||
        fastMarker->next->next == slowMarker)
      return true;
    fastMarker = fastMarker->next->next;
    slowMarker = slowMarker->next;
  }
}

int main(int argc, char** argv) {
  struct List * head = makeList(10, 0);
  printf("%s", loopInList(head) ? "true" : "false");
  return 0;
}

Реализация на Haskell. Собственно как и практически всегда у меня этим языком - первая мысль: "А как это вообще сделать?", вторая: "Так очевидно же как...".
makeList all inLoop = [1 .. (all - inLoop)] ++ loopListPart
    where loopListPart | inLoop > 0 = [(all - inLoop + 1) .. all] ++ loopListPart
                       | otherwise = []

list = makeList 5000000 500000

loopInList list@(x:xs) = fastMarker list xs
    where slowMarker (_:slows) fast = fastMarker slows fast
          fastMarker _ [] = False
          fastMarker _ (_:[]) = False
          fastMarker allSlow@(slow:_) (fast1:fast2:fasts)
              | fast1 == slow || fast2 == slow = True
              | otherwise = slowMarker allSlow fasts

main = putStrLn $ show $ loopInList list

Подход не правильный, но работающий.
Этот подход основывается на предпосылки - данные можно изменять. Идея следующая, проходим список вперёд разворачивая его. Если конец списка совпадёт с головой - значит была петля. Повторная проходка позволит восстановить исходную структуру данных. Реализация на C (только функция проверки):
bool loopInList(struct List *head) {
  struct List *current = head, *last = head, *next;
  current = last = head;
  if (current->next == NULL) return true;
  else {
    current = current->next;
    last->next = NULL;
  }
  while (current->next != NULL) {
    printf("%d ", current->value);
    next = current->next;
    current->next = last;
    last = current;
    current = next;
  }
  if (current == head) return true;
  else return false;
}

Thursday, 29 July 2010

Модель простейшего процессора на Haskell

Относительно недавно (немногим больше полугода), в университете было получено задание - написать модель простейшего процессора на не синтезируемом подмножестве Verilog, но в связи с личной неприязнью к этому языку, модель была написана на Haskell (в связи с не нулевой вероятностью, что меня попросят переписать, писалась по тактовая модель, что бы можно было скриптами сгенерировать часть кода на Verilog). По скольку тестировать модель как-то надо, а писать в маш кодах довольно уныло, модель писалась вместе с компилятором асм подобного языка, как следствие, в описаниях машинных команд есть фрагменты, необходимые для компилятора.




Описание процессора:

Сплошной линией обозначены линии данных. Пунктиром обозначены управляющие сигналы.




Основные характеристики:

Разрядность процессора: 8 бит. 
Организация памяти: гарвардская, с отдельными блоками памяти для команд и данных. 
Подсистема обработки прерываний и команды вызова подпрограмм отсутствует. 
Внешние устройства (для данной работы это светодиоды, двухпозиционные переключатели и тактовые кнопки) отображаются в адресное пространство данных. Работа с ними выполняется по опросу. 
Команды выполняются за 2 или 3 такта (в зависимости от типа команды): 1) Выборка команды 2) Выборка операндов 3) Выполнение команды. 




Регистры:

PC — счетчик команд
IR — регистр инструкций
AR — регистр адреса операнда
C — флаг переноса/заёма
Z — флаг нуля

Описание системы команд можно найти в файле Cmd.hs, функция cmd. Функциональность описана в комментариях с использование C синтаксиса. pmem и dmem - память программ и данных соответственно. arg - аргумент команды.




Модель.

Модель создавалась с учётом того, что бы в случае необходимости её можно было относительно легко перетащить в Verilog, а значит вариант с наивным case по командам отпадал вроде такого:
command 0x01 state = someFunctiom1 state
command 0x02 state = someFunctiom2 state
или в без точечном стиле (в основном будет использовать именно он):
command 0x01 = someFunctiom1
command 0x02 = someFunctiom2
Не подходит, так как в данном случае пришлось бы переписывать весь код полностью. Разбираясь с принципами работы процесса, довольно быстро становится очевидно, что наиболее простой способ описания работы команд - потактово задать функции, определяющие изменения состояния процессора после каждого из них. Функции на каждый такт можно получить при помощи композиции нескольких простейших функций, которые были бы в реальном процессоре представлены управляющими сигналами (назовём их микрокомандами, хотя ими они и не являются). К примеру, первый такт программы представляет из себя выборку команд, который может быть описан через композицию сигнала для инкриминирования регистра PC и чтения команды в регистр IR. В моделе это выглядит так:
nextCommand = incPC . pmem2IR




К сожалению, модель вычислений языка Haskell довольно сильно отличается от модели SDF, которая присутствует в Verilog (или отчасти присутствует), и мы вынуждены следить за соблюдением правильной последовательности операций при композиции функций. Использую этот подход, вся система команд может быть описана в одной таблице следующим образом:
cmd = [(0x01, Nop, NoneArg, [nop]), -- nope
       (0x00, Hlt, NoneArg, [hlt]), -- halt model
       (0x02, Ldm, DmemArg, [incPC . pmem2AR, dmem2acc]), -- acc = dmem[arg]; c = const; z = (dmem[arg] == 0)
       (0x03, Ldi, ValueArg, [incPC . pmem2acc]), -- acc = arg; c = const; z = (arg == 0)
...
В данном случае важен только первый (код команды) и последний (поведение) элемент кортежа (остальные необходимы для компилятора). Поведение команды задаётся списком функций, каждая из которых выполняется за такт. Значительная часть функций задаётся композицией микрокоманд которые имеют тип (State -> State).
Таким образом, описание микропроцессора сводится к определению: 
  • типа данных State - хранит состояние процессора в текущий момент; 
  • таблицы cmd, в которой описываются все команды; 
  • микрокоманды, путём композиции которых получаются собственно сами команды; 
Само моделирование производится следующим небольшим кусочком кода:




step (State {on = False}) = return ()
step state = step $ executeCommand state' commandFlow
    where 
      state'@(State{ir = hexCommand}) = nextCommand state
      commandFlow = commandFlowFromInfo $ getCommandInfo hexCommand
      executeCommand state [] = state
     executeCommand state (f:fs) = executeCommand (f state) fs




В нём происходит следующее, производится выборка команды (PC инкриминируется), из таблицы cmd выбираются команды, список функций команд по очереди применяется к текущему состоянию и далее по рекурсии. 




Явные недочёты: 

  • Внешние устройства "зашиты" в структуру модели. По хорошему их необходимо вынести из модели, и обрабатывать в step. (тут же использование Trace, для функциональности). 
  • Использование неправильных структур данных (в частности для cmd). 
  • В модуле отвечающем за моделирование захардкодено всё, что только можно. 
В ближайшее время будет описан разработанный в параллель компилятор, тогда же и будет полный код и продемонстрирована его работоспособность. 

Cmd.hs - http://gist.github.com/492325
SimpleRiscModel - http://gist.github.com/492327