Page 1 of 1

Remove subitem from association list

Posted: Mon Jul 20, 2009 9:19 pm
by cormullion
Given an association list:

Code: Select all

(set 'al '(
  ("A" 123 "fred" 2)
  ("B" 234 "jim" 3)
  ("C" 345 "arthur" 4)
  ("D" 456 "dave" 5)))
how would I remove the "fred" "jim" "arthur" and "dave" strings? In fact, I don't want to remove them by referring to their content, but by specifying index 2. It doesn't seem to be as easy as it could be. (Or is it?!)

Posted: Mon Jul 20, 2009 11:30 pm
by Lutz
There are two ways to do this. The first method pops from each sub-list and is only possible since v.10.0:

Code: Select all

; remove the element at offset 2 in each sublist
(dotimes (i (length al))
	(pop (al i) 2))

al => (("A" 123 2) ("B" 234 3) ("C" 345 4) ("D" 456 5))
The second way treats 'al' as one nested list using two indexes to address the element to be popped from 'al':

Code: Select all

; remove at i 2 of al
(dotimes (i (length al))
  (pop al i 2))

; remove at (list i 2) of al
(dotimes (i (length al))
  (pop al (list i 2)))
The second time the indexes are in a vector, similar to what 'ref' would return in

Code: Select all

(ref "fred" al) => (0 2)

Posted: Tue Jul 21, 2009 1:46 am
by Lutz
... and yet another method addressing the sublists as associations:

Code: Select all

(pop (assoc "A" al) 2) => "fred"

Posted: Tue Jul 21, 2009 4:22 pm
by cormullion
cool. thanks! I had been trying replace and set-ref-all - to avoid looping ...

Posted: Tue Jul 21, 2009 5:34 pm
by Lutz
avoid looping ...
you could use 'replace' with 'match':

Code: Select all

(replace '(*) al (append (0 2 $it) (-1 $it)) match)

al => (("A" 123 2) ("B" 234 3) ("C" 345 4) ("D" 456 5))
you could do similar with 'set-ref-all' but I suspect that this is faster. The match expression will match every top-level list in 'al', which are the sub-lists in $it. $it is not writable, so we have to construct the new sublist appending slices.

Likewise you could do:

Code: Select all

(set 'al (map (fn (e) (append (0 2 e) (-1 e))) al))
but I guess your were looking for an in-place replacement.

Posted: Tue Jul 21, 2009 6:51 pm
by cormullion
Yes, they all work! I like the powerful single line style. The more you look, the more you find:

Code: Select all

(replace '(*) al (list (select $it '(0 1 3))) match)
thanks!

Posted: Tue Jul 21, 2009 7:11 pm
by Lutz
... and you don't need 'list' and the selection indexes don't need to be in a list (but they can be, if you wish):

Code: Select all

(replace '(*) al (select $it 0 1 3) match)
so that is probably the shortest solution, we can come up with.

Posted: Tue Jul 21, 2009 7:26 pm
by newdep
Yes true you have to look because its not that obvious actualy..

something that I would use instantly is a combination of 'map
together with 'pop.. but its not going to work..

but this is..

Code: Select all

(map (fn(x) (replace (x 2) x)) al) 
is a short solution and returns the result but its a non-destructive solution..

Posted: Tue Jul 21, 2009 7:38 pm
by newdep
oke oke and there is the good old loop (hello python)

Code: Select all

(for (x 0 (dec (length al))) (pop (al x) 2) )
probably the fastest..

Posted: Tue Jul 21, 2009 7:54 pm
by Lutz
but we had a better one like this already (first solution in this thread):

Code: Select all

(dotimes (i (length al))
   (pop (al i) 2))