prolog

Prolog Filtering List using `findall` with in-line predicate or "lambda"


I have this data (the data is read from csv and it has to look like this, aka. a list of terms, rather than as plain terms defined in database):

Rows = [row('A', 150), row('B', 300), row('C', 50)].

Now I wish to filter them by value, what I wish to do is like this (directly as query):

?- findall(Row, (member(row(Name, Value), Rows), Value > 50), Filtered).

However this won't work and gives:

?- Rows = [row('A', 150), row('B', 300), row('C', 50)], 
findall(Row, (member(row(Name, Value), Rows), Value > 50), Filtered). 
Rows = [row('A', 150), row('B', 300), row('C', 50)],
Filtered = [_, _].

At the moment the only way to do it is to put them inside a file:

% Code.pl

% Have to provide this as a predicate
filter(Row, Rows) :-
    Row = row(_, Value),
    member(Row, Rows), 
    Value > 50.

% Also wanted to declare the following
% Neither works:
% data = [row('A', 150), row('B', 300), row('C', 50)].
% Rows = [row('A', 150), row('B', 300), row('C', 50)].

Then I can do:

?- Rows = [row('A', 150), row('B', 300), row('C', 50)], findall(Row, filter(Row, data), Filtered).

# Or alternatively, wished to do:
?- findall(Row, filter(Row, data), Filtered).

On the other hand, while drafting this example I came across this problem:

% Wanted to put data directly inside code file, then query directly
% Neither works:
% data = [row('A', 150), row('B', 300), row('C', 50)].
% Rows = [row('A', 150), row('B', 300), row('C', 50)].

I have two questions regarding syntax:

  1. How to put the data directly inside the code file (for the purpose of this example), as a list of terms?
  2. For the query, is there anyway to achieve what I wanted without having to put everything inside a script file? I.e. Rows = [row('A', 150), row('B', 300), row('C', 50)], findall(Row, (member(row(Name, Value), Rows), Value > 50), Filtered).

Remark:


Solution

    1. How to put the data directly inside the code file (for the purpose of this example), as a list of terms?

    Either as a fact:

    rows([row('A', 150), row('B', 300), row('C', 50)]).
    

    then rows(Rows), or as individual facts:

    row('A', 150).
    row('B', 300).
    row('C', 50).
    

    and then a findall to gather those up.


    1. For the query, is there anyway to achieve what I wanted without having to put everything inside a script file?

    Your findall line does not declare what Row is:

    ?- Rows = [row('A', 150), row('B', 300), row('C', 50)], 
       findall(Row, (member(row(Name, Value), Rows), Value > 50), Filtered). 
                ^
                |
                |_ this is an uninitialized variable,
                   so your output is Filtered = [_, _] 
                   a list of two unintialized variables.
    

    so you could fix it as Paulo Moura answers, or you could do:

    ?-Rows = [row('A', 150), row('B', 300), row('C', 50)], 
      findall(row(Name, Value), (member(row(Name, Value), Rows), Value > 50), Filtered).
    
    Filtered = [row('A',150), row('B',300)]
    

    Or you could use include/3 with a test predicate:

    row_value_test(row(_, Value)) :-
        Value > 50.
    
    ?- Rows = [row('A', 150), row('B', 300), row('C', 50)], 
       include(row_value_test, Rows, Filtered).
    
    Filtered = [row('A',150), row('B',300)]
    

    or yes, you could do that inline with a lambda:

    ?- Rows = [row('A', 150), row('B', 300), row('C', 50)], 
       include([row(_,V)]>>(V>50), Rows, Filtered).
    
    Filtered = [row('A',150), row('B',300)]