12
votes

I tried to create something what would work like this:

?- unpacking([[1], [1,2], [3]], Lst1, NewLst).
NewLst=[1,3]

I wrote it like this:

unpacking([], Lst1, Lst1).
unpacking([[H]|T], Lst1, NewLst):-
    append([H], Lst2),
    unpacking(T, Lst2, NewLst).
unpacking([_|T], Lst1, NewLst):-
    unpacking(T, Lst1, NewLst).

and I know that I am doing something wrong. I am starting in Prolog so, need to learn from my mistakes :)

5
What do you expect for [[],[1],[2]]?false
What does this do? What is it supposed to do? What is your problem?...user1812457
@false actually never thought about that input :) because im working with lists that normally have something inside.Bl4ckCoding
@Boris what this does is unpacks a list into another list, but only keep the one-elements list, so instead of a list of lists is a list with non-list elements, which only keeps the one-elements list, kinda confusing i see.Bl4ckCoding
But why do you have three arguments? Two arguments should be enough I have the feeling....user1812457

5 Answers

6
votes

You probably meant:

unpacking([], []).
unpacking([[E]|T], [E|L]) :-
   unpacking(T, L).
unpacking([[]|T], L) :-
   unpacking(T, L).
unpacking([[_,_|_]|T], L) :-
   unpacking(T, L).

There are more concise ways to write this - and more efficient, too.

6
votes

What about this :

%?-unpacking([[a,b,c],[a],[b],[c,d]],Items).
unpacking(Lists,Items):-
 my_tpartition(length_t(1),Lists,Items,Falses).

my_tpartition(P_2,List,Ts,Fs) :- my_tpartition_ts_fs_(List,Ts,Fs,P_2).

my_tpartition_ts_fs_([],[],[],_).
my_tpartition_ts_fs_([X|Xs0],Ts,Fs,P_2) :-
 if_(call(P_2,X), (X=[NX],Ts = [NX|Ts0], Fs = Fs0),
                (Ts = Ts0,     Fs = [X|Fs0])),
my_tpartition_ts_fs_(Xs0,Ts0,Fs0,P_2).

length_t(X,Y,T):-
 length(Y,L1),
 =(X,L1,T).

This is based on Most general higher-order constraint describing a sequence of integers ordered with respect to a relation

* Update*

You could change to

length_t(X,Y,T):-
 L1 #=< X,
 fd_length(Y,L1),
 =(X,L1,T),!.

length_t(_X,_Y,false).

fd_length(L, N) :-
 N #>= 0,
 fd_length(L, N, 0).

fd_length([], N, N0) :-
 N #= N0.
fd_length([_|L], N, N0) :-
 N1 is N0+1,
 N #>= N1,
 fd_length(L, N, N1).

giving:

?-unpacking([[1],[2,3],[4],[_,_|_]],U).
U= [1,4].

but:

?-unpacking([X],Xs).
X = Xs, Xs = [].
6
votes

Based on @coder's solution, I made my own attempt using if_ and DCGs:

one_element_([], true).
one_element_([_|_],false).

one_element([], false).
one_element([_|Xs], T) :-
    one_element_(Xs, T).

f([]) -->
    [].
f([X|Xs]) -->
    { if_(one_element(X), Y=X, Y=[]) },
    Y,
    f(Xs).

unpack(Xs,Ys) :-
    phrase(f(Xs),Ys).

I only tried for about 30s, but the queries:

?- Xs = [[] | Xs], unpack(Xs,Ys).
?- Xs = [[_] | Xs], unpack(Xs,Ys).
?- Xs = [[_, _ | _] | Xs], unpack(Xs,Ys).

didn't stop with a stack overflow. In my opinion, the critical one should be the last query, but apparently, SWI Prolog manages to optimize:

?- L = [_,_|_], one_element(L,T).
L = [_3162, _3168|_3170],
T = false.

Edit: I improved the solution and gave it a shot with argument indexing. According to the SWI Manual, indexing happens if there is exactly a case distinction between the empty list [] and the non-empty list [_|_]. I rewrote one_element such that it does exactly that and repeated the trick with the auxiliary predicate one_element_. Now that one_element is pure again, we don't lose solutions anymore:

?- unpack([A,B],[]).
A = [_5574, _5580|_5582],
B = [_5628, _5634|_5636] ;
A = [_5574, _5580|_5582],
B = [] ;
A = [],
B = [_5616, _5622|_5624] ;
A = B, B = [].

but

?- unpack([[a,b,c],[a],[b],[c,d]],Items).
Items = [a, b].

is still deterministic. I have not tried this solution in other Prologs, which might be missing the indexing, but it seems for SWI, this is a solution.

Update: Apparently GNU Prolog does not do this kind of indexing and overflows on cyclic lists:

| ?- Xs = [[] | Xs], unpack(Xs,Ys).

Fatal Error: global stack overflow (size: 32770 Kb, reached: 32768 Kb, environment variable used: GLOBALSZ)
5
votes

After some thought, here is my implementation using if_/3:

unpacking(L,L1):-if_( =(L,[]), L1=[], unpack(L,L1)).

unpack([H|T],L):-if_(one_element(H), (H = [X],L=[X|T1],unpacking(T,T1)), unpacking(T,L)).

one_element(X, T) :-
   (  var(X) ->(T=true,X=[_]; T=false,X=[])
    ;  X = [_] -> T = true
    ;  X \= [_] -> T = false).

Some testcases:

?- unpacking([Xss],[]).

Xss = [].

?- unpacking([[1],[2,3],[4],[_,_|_]],U).
U = [1, 4].

?- unpacking([[1],[2,3],[4]],U).
U = [1, 4].

?- unpacking([[E]],[1]), E = 2.
false.

?- unpacking(non_list, []).
false.

?- unpacking([Xs],Xs).
Xs = [_G6221] ;
Xs = [].

UPDATE
To fix the case that @false referred in the comment we could define:

one_element([],false).
one_element([_],true).
one_element([_,_|_],false).

But this leaves some choice points...

1
votes

One way to do it is with a findall I dont think its what the bounty is for though ;)

unpacking(Lists,L1):-
   findall(I,(member(M,Lists),length(M,1),M=[I]),L1).

or 

unpacking2(Lists,L1):-
   findall(I,member([I],Lists),L1).