How to remove even numbers in List using Prolog

前端 未结 5 1481
隐瞒了意图╮
隐瞒了意图╮ 2021-01-14 16:52

I need to remove all even numbers in first list and save the rest to second list. My first non-working approach was:

remove_even([],[]).
remove_even([H1|T1],         


        
相关标签:
5条回答
  • 2021-01-14 17:16

    I mean maybe I read this wrong but don't you think you all way overthought this problem? So taking out all even elements in the first list and storing them in the second list I was able to do that just by skipping over every even element, like so:

    remove_evens([], Ys).
    remove_evens([X], [X|Ys]).
    remove_evens([X,_|Xs], [X|Ys]):-
      remove_evens(Xs, Ys), !.
    

    which in turn would give this result:

    | ?- remove_evens([1,2,3,4,5,6,7], List2).
    List2 = [1,3,5,7|_]
    
    0 讨论(0)
  • 2021-01-14 17:19

    Let's look at one of your clauses:

    remove_even([El|T],[T]) :- El mod 2 =:= 0.
    

    First, in the notation [El|T] El is a single item, and T is a list. Then [T] will be list inside a list, which probably is not what you want. It should be just "remove_even([El|T],T)".

    Next, your variant of the rule just copies T into the answer, not removing any even numbers from the tail. Only the first number (if it's even) will be removed. remove_even should be applied to the T also.

    In the end we should have something like this:

    remove_even([El|T], NewT) :- 
        El mod 2 =:= 0,
        remove_even(T, NewT).
    
    0 讨论(0)
  • 2021-01-14 17:25

    an handy library builtin is exclude

    1 ?- [user].
    |: even(N) :- N mod 2 =:= 0.
    % user://1 compiled 0.02 sec, 2 clauses
    true.
    
    2 ?- exclude(even, [1,2,3,4], L).
    L = [1, 3].
    
    0 讨论(0)
  • 2021-01-14 17:32

    The fact that you have singleton variable warnings is a clue that something isn't right. Your clause header implies you care about a particular variable, but the clause logic doesn't instantiate it or otherwise use it.

    Analyzing the rules (clauses) you provided

    remove_even([],[]).
    

    Good rule. An empty list with the even numbers removed is the empty list.

    remove_even([H1|T1],[H2|T2]):- 
        H1 mod 2 =:= 0,
        remove_even(T1,_).
    

    This rule says, [H2|T2] is the list [H1|T1] with even numbers removed if H1 is even, and if I remove the evens from T1 and discard them. That doesn't sound right. It also doesn't say how you are to obtain H2. Note: you may not want to split the result list in this clause into head and tail if the logic doesn't dictate.

    remove_even([H1|T1],[H2|T2]):-
        remove_even(T1,T2).
    

    This rule says that [H2|T2] is [H1|T1] with even numbers removed if T2 is T1 with the even numbers removed. That sounds partially correct, but the rule doesn't indicate how to handle H1 and H2.

    UPDATE: in your update, the new second clause:

    remove_even([El|T],[T]):- El mod 2 =:= 0.
    

    This is closer. One problem is that T is already a list, so you don't want [T] but just T. Then it becomes:

    remove_even([E1|T], T) :- E1 mod 2 =:= 0.
    

    Which says: The list [E1|T] with even elements removed is the list T if E1 is even. This is a correct statement but isn't complete logic. It doesn't make any stipulation about T. What if the list T has even elements? See @Sergey's answer for a corrected version of this specific clause.

    Your updated third clause has some new issues:

    remove_even([H1|T1],[H2|T2]):-
        remove_even(T1,T1).
    

    There are three singleton variables. The rule says, [H1|T1] with even elements removed yields [H2|T2] if T1 is itself with even elements removed (i.e., T1 has no even elements). That doesn't sound logical at all. So you need to rethink that rule. I assume you are intending the case where the head is odd (since clause 2 deals with an even head). In that case, you are just copying the head over to the result list. Your clause heading should then look like:

    remove_even([H|T1], [H|T2]) :-   % case where head is odd
         % put rules here that ensure head is odd, and define how T1 and T2 are related
    

    So you would ultimately have 3 clauses: (1) removing the even elements from an empty list, (2) removing even elements from a list whose head is even, and (3) removing the even elements from a list whose head is odd. That all sounds complete. Just follow the logic.

    UPDATE4 Response:

    The new 3rd clause eliminates a singleton by introducing some issues:

    remove_even([El|T1], NewT):-         
        El mod 2 =\= 0,                     
        remove_even(T1, [NewT|T1]). 
    

    Reading through: [E1|T1] with even elements removed is NewT if E1 is odd and [NewT|T1] is a list with even elements removed from T1. A BIG problem here is that you are using NewT (a list) as the head of another list [NewT|T1], so it's now a list of lists, which will not match anything. See the prior hint for clause 3 above. In addition, there's no longer a part of the rule that says E1 is part of NewT. If E1 is odd, it should be part of the the other list when even elements are removed.

    UPDATE5 Response (why does it now work?):

    So the final working version looks like this:

    remove_even([],[]).
    

    As before: if you remove the even elements of an empty list, you get an empty list.

    remove_even([El|T], NewT):- 
        El mod 2 =:= 0,
        remove_even(T, NewT).
    

    NewT is the list [E1|T] with even elements removed if E1 is even and NewT is T (the "tail list" of the original list) with the even elements removed from T. In other words, we dropped E1 (an even element, head of the first list - we don't want it any more since it's even) and are left with T, the rest of the list which we want to "process" and find the list that's like T but with the even elements removed.

    remove_even([H|T1], [H|T2]):-         
        H mod 2 =\= 0,                     
        remove_even(T1, T2).
    

    We've covered this one before, but for completeness: [H|T1] with even elements removed is [H|T2] if H is not even and T1 with its even elements removed is T2. Your description of this clause reads, [H|T2] is a list with even elements removed from [H|T1] IFF Heads of both Lists is odd AND Tail T2 of destination list is a Tail T1 with all even elements removed. This isn't quite accurate. You're saying, "...IFF heads of both lists is odd...", whereas in the clause, we are saying the heads are identical and odd (it's the same number), not just both odd.

    All of the cases are covered as I described further before in my answer. If you think about it logically, it makes sense. :)

    0 讨论(0)
  • 2021-01-14 17:33

    If your Prolog system offers clpfd, then this answer is for you. (If not, please read on, too!)

    By using meta-predicate tfilter/3 and the reified predicate zodd_t/2, simply write:

    ?- tfilter(zodd_t,[1,2,3,4],Zs). % remove even integers = keep odd integers
    Zs = [1,3].
    

    As we only use monotone code here, we get sound answers for more general queries, too:

    ?- tfilter(zodd_t,[A,B,C],Zs).
    Zs = [     ], A mod 2 #= 0, B mod 2 #= 0, C mod 2 #= 0 ;
    Zs = [    C], A mod 2 #= 0, B mod 2 #= 0, C mod 2 #= 1 ;
    Zs = [  B  ], A mod 2 #= 0, B mod 2 #= 1, C mod 2 #= 0 ;
    Zs = [  B,C], A mod 2 #= 0, B mod 2 #= 1, C mod 2 #= 1 ;
    Zs = [A    ], A mod 2 #= 1, B mod 2 #= 0, C mod 2 #= 0 ;
    Zs = [A,  C], A mod 2 #= 1, B mod 2 #= 0, C mod 2 #= 1 ;
    Zs = [A,B  ], A mod 2 #= 1, B mod 2 #= 1, C mod 2 #= 0 ;
    Zs = [A,B,C], A mod 2 #= 1, B mod 2 #= 1, C mod 2 #= 1.
    

    We can use above code even without clpfd, simply by using different reified predicates: eveninteger_t/2 → zeven_t/2, and oddinteger_t/2 → zodd_t/2.

    Sample query:

    ?- tfilter(oddinteger_t,[1,2,3,4,5,6,7],Xs).
    Xs = [1,3,5,7].
    

    Edit 2015-06-06

    Let's try the following variation!

    Instead of using zodd_t/2 directly, rather use zeven_t/2 and not_t/3:

    truth_negated(true,false).
    truth_negated(false,true).
    
    not_t(Goal,Param,Truth1) :-       % meta-predicate, negate truth value
       call(Goal,Param,Truth0),
       truth_negated(Truth0,Truth1).
    

    Let's see it if works with a head-to-head comparison:

    ?- tfilter(      zodd_t  ,[1,2,3,4],Zs).
    Zs = [1,3].
    
    ?- tfilter(not_t(zeven_t),[1,2,3,4],Zs).
    Zs = [1,3].
    
    0 讨论(0)
提交回复
热议问题