Domanda

Voglio iterare su un elenco, eseguire un'azione con gli elementi e sulla base di alcuni criteri, voglio sbarazzarmi dell'elemento attivo. Tuttavia, quando l'uso della funzione di seguito finisco in un ciclo infinito.

 (defun foo (list action test)
   (do ((elt (car list) (car list)))
       ((null list))
     (funcall action elt)
     (when (funcall test elt)
       (delete elt list))))

(setq list '(1 2 3 4))
(foo list #'pprint #'oddp)
-> infinite loop

Non è possibile in quanto indica se stesso? Alla fine, elt è (car list) Certo.

È una valutazione corretta? E come potrei risolverlo in modo efficiente?

È stato utile?

Soluzione

In realtà puoi alterare lo stato della tua lista mentre ti late. Dovrai solo usare rplacd inoltre delete, e controlla il progresso lungo l'elenco non nella clausola di iterazione, ma all'interno del corpo DO:

 (defun nfoo (lst action test)
   (do* ((list (cons 1 lst))
         (elt (cadr list) (cadr list)))
        ((null (cdr list))
         (if (funcall test (car lst)) (cdr lst) lst))
     (funcall action elt)
     (if (funcall test elt)
       (rplacd list (delete elt (cddr list)))
       (setf list (cdr list)))))  

Dovresti chiamarlo tramite copy-list Se non vuoi che distrugga l'elenco degli argomenti.

Se vuoi rimuovere dall'elenco non tutti gli elementi uguali a elt Ciò ha superato il test, ma piuttosto tutto ciò volere superare il test, quindi il delete La chiamata dovrà essere superata il test funzionare come il :test discussione.

(modificare:) e anche molto più semplice e diretto, come questo (non distruttivo) Versione:

(defun foo (list action test)
   (do* ((elt (car list) (car list)))
        ((null list))
     (funcall action elt)
     (if (funcall test elt)
       (setf list (delete elt list))
       (setf list (cdr list)))))

Altri suggerimenti

Il ciclo è infinito poiché non stai iterato su nulla, applichi il action ripetutamente, ma se non muta l'elemento, come pprint Ovviamente no, quindi se il test Il risultato è negativo, quindi rimarrà così e l'elenco non si svuoterebbe anche se la cancellazione funzionava mentre la provi.

ELIMINA è una funzione distruttiva. In comune le operazioni distruttive LISP possono distruggere il loro argomento. Dovresti scartare qualsiasi riferimento all'argomento e utilizzare solo il valore di ritorno. Dopo il completamento dell'operazione non ci sono garanzie sullo stato dell'argomento. In particolare, potrebbe esserci alcun effetto poiché le implementazioni possono anche agire in modo identico a una controparte non distruttiva, ma di solito le parti componenti della sequenza saranno riassemblate in qualche modo difficile da prevedere. Stai anche distruggendo un letterale nel tuo esempio, che ha un comportamento indefinito e dovrebbe essere evitato.

È generalmente meglio trattare gli elenchi in comune LISP come operazioni immutabili e distruttive come una microoptizzazione che dovrebbe essere usata solo quando sei sicuro che non romperanno nulla. Per questo problema potresti voler iterare nell'elenco utilizzando l'assemblaggio del loop l'elenco dei risultati con condizionale COLLECT. Vedere il Capitolo loop di PCL.

Sono un po 'nuovo per Lisp, quindi forse mi manca qualcosa nella tua domanda. Comunque, io pensare Capisco cosa stai chiedendo e mi chiedo perché non stai usando alcune strutture esistenti per questo ... vale a dire remove-if-not (o remove-if Se ho le cose all'indietro) e mapcar...

(mapcar #'pprint (remove-if-not #'oddp '(1 2 3 4))

Le stampe di cui sopra 1 e 3 (e ritorna (nil nil), ma presumibilmente puoi ignorarlo ... o potresti fare un defun che fa quanto sopra e finisce (values)). (Se volevi gli Evens, cambia remove-if-not a remove-if.)

Questo mi colpisce come forse un modo più sensato per andare in giro, a meno che tu non lo stia facendo per motivi pedagogici o mi manca qualcosa ... uno dei quali ammetto è del tutto possibile. :)

Ps Iperspec Info On remove-if, remove-if-not, eccetera.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top