group([],L,L).
group([(A,B,L)|T],Acc,Out):-
( append(L1,[(A,B,LL)|L2], Acc)
-> append(LL,L,LLL),
append(L1,[(A,B,LLL)|L2], NewAcc),
group(T,NewAcc,Out)
; append(Acc,[(A,B,L)],NewAcc),
group(T,NewAcc,Out)
).
?- tuples(L), group(L,[],G).
G = [
(a,b,[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23]),
(c,d,[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]),
(e,f,[100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200])
],
L = [
(a,b,[1, 3, 5, 7]),
(a,b,[9, 11, 13, 15]),
(a,b,[17, 19, 21, 23]),
(c,d,[0, 2, 4, 6]),
(c,d,[8, 10, 12, 14]),
(c,d,[16, 18, 20, 22]),
(e,f,[100, 200, 300, 400]),
(e,f,[500, 600, 700, 800]),
(e,f,[900, 1000, 1100, 1200])
].
Basically I'm stripping an element from the (tuple) list ([(A,B,L)|T]) and look if I got a similar entry within my bag-list (2nd argument, append(L1,[(A,B,LL)|L2], Acc)). I use append/3 to do so because it can be used to find an element and at the same time dividing the original list into the list before and after the found element.
If I found an entry in the baglist, I append the two number lists (L and LL) to a new one (LLL) and exchange the values within the bag-list. Then I call the predicate again with the rest list and the altered bag-list.
If no element fits, I just add the element to the bag-list (as last element, append(Acc,[(A,B,L)],Acc2)) and call the predicate again. So each time I call the predicate group/3 it's first argument loses an element. Until there is no element left (group([],L,L).), in this case I state my bag-list is my ouput.
After playing a bit around with group_by/4 and/or bagof/3 as suggested by Guy coder, and using a variant of Davids "extraction" predicate here is another solution:
one_tuple(V,W,Value) :-
tuples(Tuples),
member((V,W,Values), Tuples),
member(Value, Values).
?- findall((A,B,Cs), bagof(C, one_tuple(A, B, C), Cs), O).
O = [(a,b,[1,3,5,7,9,11,13,15,17,19,21,23]),
(c,d,[0,2,4,6,8,10,12,14,16,18,20,22]),
(e,f,[100,200,300,400,500,600,700,800,900,1000,1100,1200])].