From: Cecil Westerhof on 2 Jan 2010 19:51 I am still experimenting with CL. At the moment I am working with recipes. To get the ingredient list for my brownies recipe, I use: (getf (get-recipe "Brownies") :ingredients) and get: ((:QUANTITY "5" :TYPE "stuk" :INGREDIENT "eieren") (:QUANTITY "350" :TYPE "gram" :INGREDIENT "suiker") (:QUANTITY "175" :TYPE "gram" :INGREDIENT "boter (gesmolten)") (:QUANTITY "1" :TYPE "eetlepel" :INGREDIENT "vanille essence") (:QUANTITY "175" :TYPE "gram" :INGREDIENT "bloem") (:QUANTITY "100" :TYPE "gram" :INGREDIENT "cacao") (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(walnoten)") (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(kokos)") (:QUANTITY "2" :TYPE "stuk" :INGREDIENT "(bananen)") (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(hazelnoten)")) This I want to display and this can be done with: (dolist (this-ingredient (getf (get-recipe "Brownies") :ingredients)) (format t "~3@a ~8a ~a~%" (getf this-ingredient :quantity) (getf this-ingredient :type) (getf this-ingredient :ingredient))) and this gives: 5 stuk eieren 350 gram suiker 175 gram boter (gesmolten) 1 eetlepel vanille essence 175 gram bloem 100 gram cacao 100 gram (walnoten) 100 gram (kokos) 2 stuk (bananen) 100 gram (hazelnoten) At the moment this is hard coded, but of course this is not what I want. I want only to use the space that is needed. The way I am thinking about it, is to do two dolist and in the first I could find the longest lengths for :quantity and :type and use those in the format. I was just wondering if there is not a more efficient way to do this. -- Cecil Westerhof Senior Software Engineer LinkedIn: http://www.linkedin.com/in/cecilwesterhof
From: Cecil Westerhof on 2 Jan 2010 20:15 Cecil Westerhof <Cecil(a)decebal.nl> writes: > I am still experimenting with CL. At the moment I am working with > recipes. > > To get the ingredient list for my brownies recipe, I use: > (getf (get-recipe "Brownies") :ingredients) > > and get: > ((:QUANTITY "5" :TYPE "stuk" :INGREDIENT "eieren") > (:QUANTITY "350" :TYPE "gram" :INGREDIENT "suiker") > (:QUANTITY "175" :TYPE "gram" :INGREDIENT "boter (gesmolten)") > (:QUANTITY "1" :TYPE "eetlepel" :INGREDIENT "vanille essence") > (:QUANTITY "175" :TYPE "gram" :INGREDIENT "bloem") > (:QUANTITY "100" :TYPE "gram" :INGREDIENT "cacao") > (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(walnoten)") > (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(kokos)") > (:QUANTITY "2" :TYPE "stuk" :INGREDIENT "(bananen)") > (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(hazelnoten)")) > > This I want to display and this can be done with: > (dolist (this-ingredient (getf (get-recipe "Brownies") :ingredients)) > (format t "~3@a ~8a ~a~%" > (getf this-ingredient :quantity) > (getf this-ingredient :type) > (getf this-ingredient :ingredient))) > > and this gives: > 5 stuk eieren > 350 gram suiker > 175 gram boter (gesmolten) > 1 eetlepel vanille essence > 175 gram bloem > 100 gram cacao > 100 gram (walnoten) > 100 gram (kokos) > 2 stuk (bananen) > 100 gram (hazelnoten) > > At the moment this is hard coded, but of course this is not what I want. > I want only to use the space that is needed. The way I am thinking about > it, is to do two dolist and in the first I could find the longest > lengths for :quantity and :type and use those in the format. I was just > wondering if there is not a more efficient way to do this. I already made a function for it: (defun display-ingredients (recipe) (let ((quantity-length 0) (temp) (type-length 0)) (dolist (this-ingredient (getf recipe :ingredients)) (setq temp (length (getf this-ingredient :quantity))) (when (> temp quantity-length) (setq quantity-length temp)) (setq temp (length (getf this-ingredient :type))) (when (> temp type-length) (setq type-length temp))) (dolist (this-ingredient (getf recipe :ingredients)) (format t "~v@a ~va ~a~%" quantity-length (getf this-ingredient :quantity) type-length (getf this-ingredient :type) (getf this-ingredient :ingredient))))) With 'Brownies' I get: (display-ingredients (get-recipe "Brownies")) 5 stuk eieren 350 gram suiker 175 gram boter (gesmolten) 1 eetlepel vanille essence 175 gram bloem 100 gram cacao 100 gram (walnoten) 100 gram (kokos) 2 stuk (bananen) 100 gram (hazelnoten) With 'Salate de vinete' I get: (display-ingredients (get-recipe "Salate de vinete")) 10 stuk grote aubergines 30 stuk gedroogde en gemalen pepers 3 stuk ui 1 eetlepel knoflook 2 eetlepels zout olie So it does what I want, but I was just wondering if there was a better way? In this particular case it is not very important, because the lists will not be very big, but I like to make things for the general case, so that when the data set changes there are no nasty surprises. -- Cecil Westerhof Senior Software Engineer LinkedIn: http://www.linkedin.com/in/cecilwesterhof
From: Mirko on 2 Jan 2010 20:49 On Jan 2, 8:15 pm, Cecil Westerhof <Ce...(a)decebal.nl> wrote: > Cecil Westerhof <Ce...(a)decebal.nl> writes: > > I am still experimenting with CL. At the moment I am working with > > recipes. > > > To get the ingredient list for my brownies recipe, I use: > > (getf (get-recipe "Brownies") :ingredients) > > > and get: > > ((:QUANTITY "5" :TYPE "stuk" :INGREDIENT "eieren") > > (:QUANTITY "350" :TYPE "gram" :INGREDIENT "suiker") > > (:QUANTITY "175" :TYPE "gram" :INGREDIENT "boter (gesmolten)") > > (:QUANTITY "1" :TYPE "eetlepel" :INGREDIENT "vanille essence") > > (:QUANTITY "175" :TYPE "gram" :INGREDIENT "bloem") > > (:QUANTITY "100" :TYPE "gram" :INGREDIENT "cacao") > > (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(walnoten)") > > (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(kokos)") > > (:QUANTITY "2" :TYPE "stuk" :INGREDIENT "(bananen)") > > (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(hazelnoten)")) > > > This I want to display and this can be done with: > > (dolist (this-ingredient (getf (get-recipe "Brownies") :ingredients)) > > (format t "~3@a ~8a ~a~%" > > (getf this-ingredient :quantity) > > (getf this-ingredient :type) > > (getf this-ingredient :ingredient))) > > > and this gives: > > 5 stuk eieren > > 350 gram suiker > > 175 gram boter (gesmolten) > > 1 eetlepel vanille essence > > 175 gram bloem > > 100 gram cacao > > 100 gram (walnoten) > > 100 gram (kokos) > > 2 stuk (bananen) > > 100 gram (hazelnoten) > > > At the moment this is hard coded, but of course this is not what I want.. > > I want only to use the space that is needed. The way I am thinking about > > it, is to do two dolist and in the first I could find the longest > > lengths for :quantity and :type and use those in the format. I was just > > wondering if there is not a more efficient way to do this. > > I already made a function for it: > (defun display-ingredients (recipe) > (let ((quantity-length 0) > (temp) > (type-length 0)) > (dolist (this-ingredient (getf recipe :ingredients)) > (setq temp (length (getf this-ingredient :quantity))) > (when (> temp quantity-length) > (setq quantity-length temp)) > (setq temp (length (getf this-ingredient :type))) > (when (> temp type-length) > (setq type-length temp))) > (dolist (this-ingredient (getf recipe :ingredients)) > (format t "~v@a ~va ~a~%" > quantity-length (getf this-ingredient :quantity) > type-length (getf this-ingredient :type) > (getf this-ingredient :ingredient))))) > > With 'Brownies' I get: > (display-ingredients (get-recipe "Brownies")) > 5 stuk eieren > 350 gram suiker > 175 gram boter (gesmolten) > 1 eetlepel vanille essence > 175 gram bloem > 100 gram cacao > 100 gram (walnoten) > 100 gram (kokos) > 2 stuk (bananen) > 100 gram (hazelnoten) > > With 'Salate de vinete' I get: > (display-ingredients (get-recipe "Salate de vinete")) > 10 stuk grote aubergines > 30 stuk gedroogde en gemalen pepers > 3 stuk ui > 1 eetlepel knoflook > 2 eetlepels zout > olie > > So it does what I want, but I was just wondering if there was a better > way? In this particular case it is not very important, because the lists > will not be very big, but I like to make things for the general case, so > that when the data set changes there are no nasty surprises. > > -- > Cecil Westerhof > Senior Software Engineer > LinkedIn:http://www.linkedin.com/in/cecilwesterhof Instead of looping over the lists, maybe `reduce' will work: (reduce #'> :key #'fifth ingredients) ;; untested (see http://www.lispworks.com/documentation/HyperSpec/Body/f_reduce.htm) Mirko
From: Tim X on 2 Jan 2010 22:46 Cecil Westerhof <Cecil(a)decebal.nl> writes: > Cecil Westerhof <Cecil(a)decebal.nl> writes: > >> I am still experimenting with CL. At the moment I am working with >> recipes. >> >> To get the ingredient list for my brownies recipe, I use: >> (getf (get-recipe "Brownies") :ingredients) >> >> and get: >> ((:QUANTITY "5" :TYPE "stuk" :INGREDIENT "eieren") >> (:QUANTITY "350" :TYPE "gram" :INGREDIENT "suiker") >> (:QUANTITY "175" :TYPE "gram" :INGREDIENT "boter (gesmolten)") >> (:QUANTITY "1" :TYPE "eetlepel" :INGREDIENT "vanille essence") >> (:QUANTITY "175" :TYPE "gram" :INGREDIENT "bloem") >> (:QUANTITY "100" :TYPE "gram" :INGREDIENT "cacao") >> (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(walnoten)") >> (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(kokos)") >> (:QUANTITY "2" :TYPE "stuk" :INGREDIENT "(bananen)") >> (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(hazelnoten)")) >> >> This I want to display and this can be done with: >> (dolist (this-ingredient (getf (get-recipe "Brownies") :ingredients)) >> (format t "~3@a ~8a ~a~%" >> (getf this-ingredient :quantity) >> (getf this-ingredient :type) >> (getf this-ingredient :ingredient))) >> >> and this gives: >> 5 stuk eieren >> 350 gram suiker >> 175 gram boter (gesmolten) >> 1 eetlepel vanille essence >> 175 gram bloem >> 100 gram cacao >> 100 gram (walnoten) >> 100 gram (kokos) >> 2 stuk (bananen) >> 100 gram (hazelnoten) >> >> At the moment this is hard coded, but of course this is not what I want. >> I want only to use the space that is needed. The way I am thinking about >> it, is to do two dolist and in the first I could find the longest >> lengths for :quantity and :type and use those in the format. I was just >> wondering if there is not a more efficient way to do this. > > I already made a function for it: > (defun display-ingredients (recipe) > (let ((quantity-length 0) > (temp) > (type-length 0)) > (dolist (this-ingredient (getf recipe :ingredients)) > (setq temp (length (getf this-ingredient :quantity))) > (when (> temp quantity-length) > (setq quantity-length temp)) > (setq temp (length (getf this-ingredient :type))) > (when (> temp type-length) > (setq type-length temp))) > (dolist (this-ingredient (getf recipe :ingredients)) > (format t "~v@a ~va ~a~%" > quantity-length (getf this-ingredient :quantity) > type-length (getf this-ingredient :type) > (getf this-ingredient :ingredient))))) > <snip> > > So it does what I want, but I was just wondering if there was a better > way? In this particular case it is not very important, because the lists > will not be very big, but I like to make things for the general case, so > that when the data set changes there are no nasty surprises. I can see a couple of places where things could be improved, both with respect to code style and algorithm. However, keep in mind that I'm still learning this CL stuff as well! I do think there are better data abstractions/structures to represent your data, but as this is a learning exercise and it is only a simple app, I'll leave that for now. However, once you have the basic blocks in place, I would consider trying different data representations, such as an association list, nested lists/trees or structs to start with. Keeping that in mind, it may also help structure your code and decide when to creat a function - think about what would need to be changed if you moved from a property list to an association list or a tree or array of structs etc and abstract that away with a function. The two main things that jump out for me straight away are that you ar traversing the same list twice and you are performing the same getf operations twice. I'd be thinking about ways this could be avoided or reduced. for example, maybe in the first loop, you could be creating a new structure/list that is easier to process with format. While you would still be looping over a list, you may be able to eliminate one set of getf operations. (remember to consider what getf needs to do to return the value!). I remember you were asking before if lists were too inefficient and best avoided. the response I gave was that lists in lisp are probably the most efficient list implementation around and that for many tasks, they are quite suitable. However, it is also important to use the lists well. for example, the practice of pushing elements onto the front of a list an then reversing it rather than appending new elements to reduce list traversals is a common example. Lists can be quite efficient, but you do still need to consider what they are and what various list operations involve. Any operation that involves retrieving data from a list which is not from the head/car of the list will involve list traversal and therefore, could be expensive. Minimising how often you need to traverse the list will improve efficiency. On a style note, consider the difference between > (let ((quantity-length 0) > (temp) > (type-length 0)) > (dolist (this-ingredient (getf recipe :ingredients)) > (setq temp (length (getf this-ingredient :quantity))) > (when (> temp quantity-length) > (setq quantity-length temp)) > (setq temp (length (getf this-ingredient :type))) > (when (> temp type-length) > (setq type-length temp))) > (dolist (this-ingredient (getf recipe :ingredients)) and > (let ((quantity-length 0) > (type-length 0)) > (dolist (this-ingredient (getf recipe :ingredients)) (setf quantity-length (max (getf this-ingredient :quantity) quantity-length)) (setf type-length (max (getf this-ingredient :type) type-length)) > (dolist (this-ingredient (getf recipe :ingredients)) which removes the need for the tmp variable and the two 'when' blocks. It also reveals a clearer pattern, which wold normally indicate a possible further refinement. for example, maybe a function which accepts a plist and a property name and returns the length of the longest property for that property name. However, in this case, assuming we stick with the plist, I'd be tempted to use a loop that builds up the list of quantities and types with initial elements representing the length of the longest quantity and type and then passing that list to format for processing. The loop macro is hated by some and loved by others. In this particular case, I think it wold be worth investigating as it provides constructs that would both collect the elements into a list and which could calculate the maximum length for both quantity and type. Not necessarily more efficient, but would likely be clearer code. In addition to reducing the probability of subtle bugs and being easier to maintain, clearer code also tends to reveal patterns better and helps to identify where refactoring will further reduce the code and clarify its intention etc. HTH Tim -- tcross (at) rapttech dot com dot au
From: Kenneth Tilton on 3 Jan 2010 00:34
Tim X wrote: > Cecil Westerhof <Cecil(a)decebal.nl> writes: > >> Cecil Westerhof <Cecil(a)decebal.nl> writes: >> >>> I am still experimenting with CL. At the moment I am working with >>> recipes. >>> >>> To get the ingredient list for my brownies recipe, I use: >>> (getf (get-recipe "Brownies") :ingredients) >>> >>> and get: >>> ((:QUANTITY "5" :TYPE "stuk" :INGREDIENT "eieren") >>> (:QUANTITY "350" :TYPE "gram" :INGREDIENT "suiker") >>> (:QUANTITY "175" :TYPE "gram" :INGREDIENT "boter (gesmolten)") >>> (:QUANTITY "1" :TYPE "eetlepel" :INGREDIENT "vanille essence") >>> (:QUANTITY "175" :TYPE "gram" :INGREDIENT "bloem") >>> (:QUANTITY "100" :TYPE "gram" :INGREDIENT "cacao") >>> (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(walnoten)") >>> (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(kokos)") >>> (:QUANTITY "2" :TYPE "stuk" :INGREDIENT "(bananen)") >>> (:QUANTITY "100" :TYPE "gram" :INGREDIENT "(hazelnoten)")) >>> >>> This I want to display and this can be done with: >>> (dolist (this-ingredient (getf (get-recipe "Brownies") :ingredients)) >>> (format t "~3@a ~8a ~a~%" >>> (getf this-ingredient :quantity) >>> (getf this-ingredient :type) >>> (getf this-ingredient :ingredient))) >>> >>> and this gives: >>> 5 stuk eieren >>> 350 gram suiker >>> 175 gram boter (gesmolten) >>> 1 eetlepel vanille essence >>> 175 gram bloem >>> 100 gram cacao >>> 100 gram (walnoten) >>> 100 gram (kokos) >>> 2 stuk (bananen) >>> 100 gram (hazelnoten) >>> >>> At the moment this is hard coded, but of course this is not what I want. >>> I want only to use the space that is needed. The way I am thinking about >>> it, is to do two dolist and in the first I could find the longest >>> lengths for :quantity and :type and use those in the format. I was just >>> wondering if there is not a more efficient way to do this. Not worth worrying about, or even close to worth worrying about. It's not even close to close to worth worrying about. >> I already made a function for it: >> (defun display-ingredients (recipe) >> (let ((quantity-length 0) >> (temp) >> (type-length 0)) >> (dolist (this-ingredient (getf recipe :ingredients)) >> (setq temp (length (getf this-ingredient :quantity))) >> (when (> temp quantity-length) >> (setq quantity-length temp)) >> (setq temp (length (getf this-ingredient :type))) >> (when (> temp type-length) >> (setq type-length temp))) >> (dolist (this-ingredient (getf recipe :ingredients)) >> (format t "~v@a ~va ~a~%" >> quantity-length (getf this-ingredient :quantity) >> type-length (getf this-ingredient :type) >> (getf this-ingredient :ingredient))))) >> > <snip> >> So it does what I want, but I was just wondering if there was a better >> way? In this particular case it is not very important, because the lists >> will not be very big, but I like to make things for the general case, so >> that when the data set changes there are no nasty surprises. I suggest you write the code you are writing now and not the code you might write someday. Someday you can have a recipes container with slots for max length of each column, and maintain it as you add new elements. Using a plist is not the lay to go. Use defstruct. Simpler than a plist, really, and massively more efficient. > > I can see a couple of places where things could be improved, both with > respect to code style and algorithm. However, keep in mind that I'm > still learning this CL stuff as well! > > I do think there are better data abstractions/structures to represent > your data, but as this is a learning exercise and it is only a simple > app, I'll leave that for now. However, once you have the basic blocks in > place, I would consider trying different data representations, such as > an association list, nested lists/trees or structs to start with. > Keeping that in mind, it may also help structure your code and decide > when to creat a function - think about what would need to be changed if > you moved from a property list to an association list or a tree or array > of structs etc and abstract that away with a function. > > The two main things that jump out for me straight away are that you ar > traversing the same list twice and you are performing the same getf > operations twice. I'd be thinking about ways this could be avoided or > reduced. for example, maybe in the first loop, you could be creating a > new structure/list that is easier to process with format. While you > would still be looping over a list, you may be able to eliminate one set > of getf operations. (remember to consider what getf needs to do to > return the value!). > > I remember you were asking before if lists were too inefficient and best > avoided. the response I gave was that lists in lisp are probably the most > efficient list implementation around and that for many tasks, they are > quite suitable. However, it is also important to use the lists well. for > example, the practice of pushing elements onto the front of a list an > then reversing it rather than appending new elements to reduce list > traversals is a common example. Lists can be quite efficient, but you do > still need to consider what they are and what various list operations > involve. Any operation that involves retrieving data from a list which > is not from the head/car of the list will involve list traversal and > therefore, could be expensive. Minimising how often you need to traverse > the list will improve efficiency. > > On a style note, consider the difference between > >> (let ((quantity-length 0) >> (temp) >> (type-length 0)) >> (dolist (this-ingredient (getf recipe :ingredients)) >> (setq temp (length (getf this-ingredient :quantity))) >> (when (> temp quantity-length) >> (setq quantity-length temp)) >> (setq temp (length (getf this-ingredient :type))) >> (when (> temp type-length) >> (setq type-length temp))) >> (dolist (this-ingredient (getf recipe :ingredients)) > > and > >> (let ((quantity-length 0) >> (type-length 0)) >> (dolist (this-ingredient (getf recipe :ingredients)) > (setf quantity-length (max (getf this-ingredient :quantity) uh, ya lost the length operator... > quantity-length)) > (setf type-length (max (getf this-ingredient :type) > type-length)) >> (dolist (this-ingredient (getf recipe :ingredients)) > > which removes the need for the tmp variable and the two 'when' blocks. > It also reveals a clearer pattern, which wold normally indicate a > possible further refinement. for example, maybe a function which accepts > a plist and a property name and returns the length of the longest > property for that property name. However, in this case, assuming we > stick with the plist, I'd be tempted to use a loop that builds up the > list of quantities and types with initial elements representing the > length of the longest quantity and type and then passing that list to format > for processing. > > The loop macro is hated by some and loved by others. In this particular > case,... (loop for i in (getf recipe :ingredients) maximizing (length (getf i :quantity)) into quantity-length maximixing (length (getf i :type)) into type-length.... Yeah, hateful. kt -- http://thelaughingstockatpngs.com/ http://www.facebook.com/pages/The-Laughingstock/115923141782?ref=nf |