From: Cecil Westerhof on
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
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
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
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
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