newLISP Fan Club

Forum => newLISP in the real world => Topic started by: cormullion on July 20, 2009, 02:19:31 PM

Title: Remove subitem from association list
Post by: cormullion on July 20, 2009, 02:19:31 PM
Given an association list:


(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?!)
Title:
Post by: Lutz on July 20, 2009, 04:30:11 PM
There are two ways to do this. The first method pops from each sub-list and is only possible since v.10.0:


; 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':


; 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


(ref "fred" al) => (0 2)
Title:
Post by: Lutz on July 20, 2009, 06:46:04 PM
... and yet another method addressing the sublists as associations:


(pop (assoc "A" al) 2) => "fred"
Title:
Post by: cormullion on July 21, 2009, 09:22:45 AM
cool. thanks! I had been trying replace and set-ref-all - to avoid looping ...
Title:
Post by: Lutz on July 21, 2009, 10:34:59 AM
Quoteavoid looping ...


you could use 'replace' with 'match':


(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:


(set 'al (map (fn (e) (append (0 2 e) (-1 e))) al))

but I guess your were looking for an in-place replacement.
Title:
Post by: cormullion on July 21, 2009, 11:51:34 AM
Yes, they all work! I like the powerful single line style. The more you look, the more you find:


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

thanks!
Title:
Post by: Lutz on July 21, 2009, 12:11:08 PM
... 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):


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

so that is probably the shortest solution, we can come up with.
Title:
Post by: newdep on July 21, 2009, 12:26:50 PM
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..


(map (fn(x) (replace (x 2) x)) al)

is a short solution and returns the result but its a non-destructive solution..
Title:
Post by: newdep on July 21, 2009, 12:38:06 PM
oke oke and there is the good old loop (hello python)


(for (x 0 (dec (length al))) (pop (al x) 2) )

probably the fastest..
Title:
Post by: Lutz on July 21, 2009, 12:54:56 PM
but we had a better one like this already (first solution in this thread):


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