LINUX.ORG.RU

[Common Lisp] Из assoc list в структуру

 


0

0

Допустим определена у меня структура (defstruct foo x y z), 
точнее похожих структур может быть несколько. 
Разные характерные значения 
(x y z) я бы хотел хранить отдельно, например в файле 
(скажем, это начальные условия для системы уравнений). 

Вроде бы, наиболее логично хранить как assoc list: 
((x . 10) (y . 20) (z 0.01)). 
Возникает вопрос: как проинициализировать 
структуру этими значениями. 

Недолго подумав, пришел к макросу:

(defun symb (&rest args)
  (values (intern (apply #'mkstr args))))

(defmacro alist->struct (name alist)
	   (with-gensyms (res)
	     `(let ((,res (,(symb 'make '- `,name))))
		,@(loop for var in alist collect
		       `(setf (,(symb `,name '- `,(car var)) ,res)
			      ,(cdr var)))
		,res)))

что-то мне говорит, что можно было проще все сделать.

Может, кто что посоветует?

А тебе массив из твоих структур нужен или что?

Ну есть уже стандартный reader macro для структур -- #s

#s(foo :x 10 :y 20 :z 0.01)

Структура foo должна быть определена и иметь стандартный конструктор. Тогда это работает.

Еще можно второй свой конструктор завести через (:constructor make-foo-from-file...) в defstruct, если так хочется другого формата представления.

Макрос излишний тут. Он нужен, если ты хочешь динамически структуры создавать. То есть (defstruct ) новый объявлять, который необъявлен изначально, а структуру полей брать из своего файла, который синтаксически может быть любым.

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

>хочешь динамически структуры создавать

В принципе, так и есть.

Я хочу задавать системы уравнений: 

(defsys lorentz
  :parameters ((beta . 8/3) (sigma . 10) (rho . 28)) ; list of parameters with default values
  :equations ((x (* sigma (- y x)))
	      (y (- (* x (- rho z)) y))
	      (z (- (* x y) (* beta z)))))

например, разложится в:

(PROGN
  (DEFSTRUCT (LORENTZ (:TYPE VECTOR)) X Y Z)
  (DEFUN LORENTZ
      (&OPTIONAL (#:G2180 (QUOTE ((BETA . 8/3) (SIGMA . 10) (RHO . 28)))))
    (LET ((BETA (CDR (ASSOC 'BETA #:G2180)))
	  (SIGMA (CDR (ASSOC 'SIGMA #:G2180)))
	  (RHO (CDR (ASSOC 'RHO #:G2180))))
      (LAMBDA (#:G2178 &OPTIONAL TIME)
	(LET ((#:G2179 (MAKE-LORENTZ)))
	  (SETF (LORENTZ-X #:G2179)
		(* SIGMA (- (LORENTZ-Y #:G2178) (LORENTZ-X #:G2178))))
	  (SETF (LORENTZ-Y #:G2179)
		(- (* (LORENTZ-X #:G2178) (- RHO (LORENTZ-Z #:G2178)))
		   (LORENTZ-Y #:G2178)))
	  (SETF (LORENTZ-Z #:G2179)
		(- (* (LORENTZ-X #:G2178) (LORENTZ-Y #:G2178))
		   (* BETA (LORENTZ-Z #:G2178))))
	  #:G2179)))))

Т.е. я определяю структуру и функцию, которая, получив параметры, 
возвращает функцию (f x t) -> dx/dt, для решения системы уравнений.

С точки зрения функции, которая ту же Рунге-Кутту считает, мы 
используем вектор, а с точки зрения (lorentz) -- структуру.

Просто здесь я пытаюсь найти компромисс между общностью при решении
ODE (функция от вектора нач. значений -> вектор производных) и 
абстракцией при задании системы уравнений.

Наверное, наиболее правильный вариант тут будет
(:constructor make-foo-from-file...)

В случае же #s(foo :x 10 :y 20 :z 0.01)
Как я могу в общем виде написать для разных систем мне не понятно.

P.S. Я в лиспе, да как, собственно и в программировании -- не Копенгаген. Так что надеюсь на помощь зала :)

scabarocci
() автор топика
Ответ на: комментарий от scabarocci

(defmacro defsys (name &key parameters equations)
"Define a system of ODE version 2"
(with-gensyms (v dv params)
  (let* ((variables (variables-from-equations equations))
	 (subst-rules (loop for var in variables
			 collect (cons var `(,(symb name '- var) ,v))))
	 (par-list (if (assoc? parameters)
		       (alist-keys parameters)
		       parameters))
	 (sys-size (length variables)))
    `(progn
       (defstruct (,name (:type vector)) ,@variables)
       (defun ,name (,@(if  (assoc? parameters) ; the function we define
			    `(&optional (,params ',parameters))
			    `(,params)))
	 (let (,@(loop for s in par-list 
		    collect `(,s (cdr (assoc ',s ,params))))) ;; set up parameters
	   (lambda (,v &optional time)
	     (let ((,dv (,(symb 'make '- `,name))))
	       ,@(loop for s in equations collect
		      `(setf (,(symb `,name '- `,(car s)) ,dv) ,(tree-subst subst-rules (cadr s))))
	       ,dv))))))))

scabarocci
() автор топика
Ответ на: комментарий от scabarocci

Сорри, я очень в задачу погружаться не хочу особо. Тяжело я чужие 
задачи воспринимаю. Ну так сделай сперва просто пример:


test.foo:

#s(foo :x 10 :y 20 :z 0.01)
#s(foo :x 10 :y 20 :z 0.02)
#s(foo :x 10 :y 20 :z 0.03)

В программе объяви (defstruct foo x y z), а далее открывай файл и 
делай три раза (read...), присваивая результат чтения (read) новому 
элементу массива. Reader CL свои объекты и так знает.


Тупой иллюстрирующий примерчик:

(with-open-file (s "/home/scabarocci/test.foo" :direction :input) 
  (setq a (read s)) (setq b (read s)) (setq c (read s)))

> a
#S(FOO :X 10 :Y 20 :Z 0.01)
> b
#S(FOO :X 10 :Y 20 :Z 0.02)
> c
#S(FOO :X 10 :Y 20 :Z 0.03)

Вот тебе и три структурки. Можешь их в массив инициализировать.

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

(defstruct foo a b c) #s(foo :a 10 :b 20 :c 30)

работает, а

(defstruct (bar (:type vector)) a b c) #s(bar :a 10 :b 20 :c 30)

не работает, поскольку новый тип данных bar при этом не задается.

Конечно, в своем файле я могу записывать и как (make-bar :a 0.1 :b 0.2 :c 0.3). Можно даже записывать (:a 3.14 :b 2.72 :c 0.0), а потом делать (apply make-bar loaded-list), что лучше.

Другими словами, видимо, мне нужно просто переключиться "в голове" с использования alist на plist и использовать для хранения списки параметров и начальных условий вида (:x 1 :I 10 :beta 120).

Спасибо за помощь :)

scabarocci
() автор топика
Ответ на: комментарий от scabarocci

>(apply make-bar loaded-list)

(apply #'make-bar loaded-list); разумеется

scabarocci
() автор топика
Ответ на: комментарий от scabarocci

Ну ты и alist можешь делать. Только он многословнее. Для этого делаешь новый конструктор помимо стандартного. Скажем, (:construtor make-foo*...), и там хоть иероглифами кодируй. И пусть у конструктора голова болит, как разбирать эти данные. Макрос не нужен. :)

(make-foo* (x . 10) (y . 20) (z . 0.01))

>(:a 3.14 :b 2.72 :c 0.0)

А вот такое тоже можно прочесть ридом, но только в виде list

'(:a 3.14 :b 2.72 :c 0.0)

...

'(:a blah-blah :b blah :c blah)

и потом из этого списка вытащить значения в структуру. Только все-равно макрос не нужен. Это через функцию делается.

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

>(:construtor make-foo*...)

Можно, но пока не соображу как сделать, да и выбор в сторону alistов не так уж важен. Если с plistами проще, то почему нет?

>'(:a 3.14 :b 2.72 :c 0.0)

да, понятно.

>Только все-равно макрос не нужен. Это через функцию делается.

Теперь ясно как сделать функцией, да; то-то у меня и подозрения были: и до гланд, вроде далеко, и неудобно как-то :)

scabarocci
() автор топика
Ответ на: комментарий от scabarocci

>(defstruct (bar (:type vector)) a b c) #s(bar :a 10 :b 20 :c 30)

Почему не работает? Не понял тебя?

> a
#(10 20 0.01)
> b
#(10 20 0.02)
> c
#(10 20 0.03)

из файла test.bar:

#s(bar :a 10 :b 20 :c 0.01)
#s(bar :a 10 :b 20 :c 0.02)
#s(bar :a 10 :b 20 :c 0.03)

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

(defstruct (bar (:type vector)) a b c)

#s(bar :a 10 :b 20 :c 30)

Приводит к:

SB-INT:SIMPLE-READER-ERROR at 25 (line 1, column 25) on

#<SB-IMPL::STRING-INPUT-STREAM {AD06729}>:

BAR is not a defined structure type.

[Condition of type SB-INT:SIMPLE-READER-ERROR]

Restarts:

0: [ABORT] Return to SLIME's top level.

1: [TERMINATE-THREAD] Terminate this thread (#<THREAD "repl-thread"

{AAED071}>)

scabarocci
() автор топика
Ответ на: комментарий от scabarocci

>BAR is not a defined structure type.

Да, странно, хотя и defined как структура. CLISP кушает нормально, а SBCL ругается именно так. Но оба нормально воспринимают (make-bar :a 10 :b 20 :c 30). CLISP еще и #s(bar :a 10 :b 20 :c 30) воспринимает без проблем.

Zubok ★★★★★
()
Ответ на: комментарий от Zubok

Именно так. Странно, конечно. Но что говорит стандарт?

scabarocci
() автор топика
Ответ на: комментарий от scabarocci

Ага, что-то в стандарте сказано об этом.

The #S reader macro might or might not recognize the newly defined structure type name at compile time.

Так что да. Такое могет быть.

Zubok ★★★★★
()
Ответ на: комментарий от scabarocci

>>'(:a 3.14 :b 2.72 :c 0.0)

>да, понятно.

Можно и без квотирования, кстати.

Ты вот спрашивал, как указать, какое уравнение тебе надо выбрать. Я так понимаю, что ты по набору данных хочешь определить, что ты хочешь вызвать? Ну так дай знак в данных. Например, явно укажи, какие уравнения нужны через дополнительный параметр. Разное число данных в списке может дать знак, какое уравнение вызывать. Можно по-разному.

Если будешь делать через список, то, возможно, тебе :x :y и нужны не будут (ну только если ты по этим ключам не определяешь нужное уравнение для применения). Можно и так (10 20 30 :eqtn1), и все.

Zubok ★★★★★
()

А почему просто бы не воспользоваться двойственностью read-print? 
Т.е., что print напечатал, то read и прочитает (в первом приближении).

Соответственно, печатать обычным print-ом, а читать - обычным read-ом.

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

CL-USER> (defstruct (bar (:type vector)) a b c)
BAR
CL-USER> (setf a (make-bar :a 1 :b 2))
#(1 2 NIL)
CL-USER> (read-from-string (prin1-to-string a))
#(1 2 NIL)

Если набор структур меняется от случая к случаю и нужно гарантировать
 тождественность определений, можно попробовать сохранять в файл сами
 определения типов. Что-нибудь типа

(defmacro my-defstruct (&rest args)
  `(progn 
      (with-open-file (o "myfile.lisp" 
                         :direction :output
                         :if-does-not-exist :create 
                         :if-exists :append) 
         (pprint `(defstruct ,@',args) o))
   (defstruct ,@args)))

Этот файл потом нужно загружать с помощью load. 

den73 ★★★★★
()
Ответ на: комментарий от den73

> Я так понимаю, что ты по набору данных хочешь определить, что ты 
> хочешь вызвать? Ну так дай знак в данных. 

Структуры типа :vector можно сделать ещё и :named, см. 

http://www.lispworks.com/documentation/HyperSpec/Body/m_defstr.htm

Тогда в первом элементе будет храниться тип:
CL-USER> (defstruct (primer (:type vector) :named) a b c)
PRIMER
CL-USER> (make-primer :a 1 :b 2)
#(PRIMER 1 2 NIL)

Вообще-то, для быстроты лучше бы использовать не простой вектор, а 
типизированный:

CL-USER> (type-of #(1 2 3))
(SIMPLE-VECTOR 3)
CL-USER> (type-of (make-array '(3) :element-type 'double-float))
(SIMPLE-ARRAY DOUBLE-FLOAT (3))

Такой вектор будет обрабатываться быстрее. 
Но этот подход конфликтует с использованием :named структур. 
Можно сделать 
CL-USER> (defstruct (primer2 (:type (vector single-float))) 
                    (a 0.0) (b 0.0) (c 0.0))
PRIMER2
CL-USER> (type-of (make-primer2 :a 1.0))
(SIMPLE-ARRAY SINGLE-FLOAT (3))
Но нельзя сделать
CL-USER> (defstruct (primer3 (:type (vector single-float)) :named) 
                    (a 0.0) (b 0.0) (c 0.0))
В sbcl выдастся предупреждение при компиляции, а при создании 
экземпляра возникнет ошибка

den73 ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.