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

57 Views Asked by At

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:

2

There are 2 best solutions below

0
TessellatingHeckler On BEST ANSWER
  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)]
0
Paulo Moura On

No need for auxiliary predicates or lambda expressions:

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