Domanda esecuzione dell'operazione per ciascun elemento dell'elenco in swi-prolog e altri


Come faccio un'operazione per ogni elemento di una lista, in ordine?

Basato su queste due risorse:

  1. http://www.swi-prolog.org/pldoc/doc/swi/library/lists.pl
  2. http://www.swi-prolog.org/pldoc/doc_for?object=foreach/2

Immagino di poter sempre contare su:

  • foreach(member(X, [1,2]), write(X)).

È deterministico e posso avvolgere il predicato membro / 2 come mi pare nei miei predicati e continuare a ripetere sempre in ordine?


10
2017-09-24 08:03


origine


risposte:


Sì, ma devi preoccuparti del fallimento del tuo predicato. Se possibile, gli elementi rimanenti nell'elenco non verranno elaborati perché producono una congiunzione piuttosto che un ciclo guidato da errori.

Sarei più desideroso di usare maplist/2 poiché penso che sia più ampiamente usato di foreach/2 ma non avevo ancora visto questa opzione prima. :)

modificare: Parliamo di cosa intendo riguardo ai loop guidati da errori.

Esistono due metodi di iterazione primitivi in ​​Prolog: ricorsioni e loop basati su errori. Diciamo che voglio stampare ogni elemento in una lista. Il metodo ricorsivo assomiglierà a questo:

print_all([]).
print_all([X|Rest]) :- write(X), nl, print_all(Rest).

Quindi dato un elenco come [1,2,3], questo si espanderà in questo modo:

print_all([1,2,3])
  write(1), nl, print_all([2,3])
    write(1), nl, write(2), nl, print_all([3])
      write(1), nl, write(2), nl, write(3), nl, print_all([])
        write(1), nl, write(2), nl, write(3), nl.

Questo è come member/2 di solito è implementato:

member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).

Quindi puoi vedere che il metodo ricorsivo è piuttosto semplice e generale.

Un altro metodo semplice ma un po 'accigliato è quello di simulare un fallimento nel coinvolgere il meccanismo di backtracking. Questo è chiamato un ciclo guidato da errori e assomiglia a questo:

print_all(List) :- member(X, List), write(X), nl, fail.
print_all(_).

Quando si esegue questa versione di print_all/1, quello che succede è un po 'più complesso della semplice espansione.

print_all([1,2,3])
  member([1,2,3], 1)
    write(1), nl
      fail
  retry member([1,2,3], 2)
    write(2), nl
      fail
  retry member([1,2,3], 3)
    write(3), nl
      fail
retry print_all(_)
  true

Verbalmente, il fail costringe il Prolog a eseguire il backup fino all'ultimo punto di scelta ha fatto e provare a utilizzare la prossima soluzione. Bene, write/1 e nl/0 non producono punti di scelta perché hanno solo una soluzione, ma member/2  fa avere più soluzioni, una per ciascun elemento nell'elenco. Quindi Prolog prende ogni elemento dalla lista e lo stampa. Finalmente quando member/2 esaurito le soluzioni, Prolog esegue il backup fino al punto di scelta precedente, che è il secondo corpo del print_all/1 predicato, che riesce sempre. Quindi l'output sembra lo stesso. Penso che oggigiorno le persone preferiscono generalmente non utilizzare loop basati su errori, ma non capisco abbastanza bene le argomentazioni per pappaggerli utilmente.

Una cosa che può aiutarti a vedere cosa sta succedendo è usare il trace predicare e passare attraverso l'espansione di entrambe le versioni, e vedere se è possibile dare un senso alle differenze. La mia notazione di cui sopra è completamente compensata per questa risposta e potrebbe non essere chiara.

Ripercorrendo ciò che ho scritto in origine e la tua domanda effettiva:

  • foreach sarà deterministico
  • member sarà sempre iterare in ordine, poiché le liste sono definite in modo tale che è necessario accedere a ciascun elemento a turno

Inoltre, in questi giorni almeno su S.O. avrai un sacco di persone che ti diranno di usare maplist e il suo tipo, quindi probabilmente non sta andando solo a lavorare, ma anche una buona idea.


18
2017-09-24 18:12