Вдохновленный этим вопросом StackOverflow:
Найти общий элемент в различных фактах в swi-prolog
У нас есть следующее
Постановка задачи
Приведена база данных "актеров, снимающихся в кино" (например, starsin - это связь актера "bob" с mov ie "a")
starsin(a,bob).
starsin(c,bob).
starsin(a,maria).
starsin(b,maria).
starsin(c,maria).
starsin(a,george).
starsin(b,george).
starsin(c,george).
starsin(d,george).
И дано набор фильмов M , найдите тех актеров, которые снимались во всех фильмах M .
Вопрос изначально был для Пролога.
Решение Prolog
В Prolog элегантное решение включает предикат setof/3
, который собирает возможные экземпляры переменных в набор (который действительно является списком без повторяющихся значений):
actors_appearing_in_movies(MovIn,ActOut) :-
setof(
Ax,
MovAx^(setof(Mx,starsin(Mx,Ax),MovAx), subset(MovIn,MovAx)),
ActOut
).
Я не буду go подробно рассказывать об этом, но давайте посмотрим на тестовый код, который представляет интерес здесь. Вот пять тестовых случаев:
actors_appearing_in_movies([],ActOut),permutation([bob, george, maria],ActOut),!.
actors_appearing_in_movies([a],ActOut),permutation([bob, george, maria],ActOut),!.
actors_appearing_in_movies([a,b],ActOut),permutation([george, maria],ActOut),!.
actors_appearing_in_movies([a,b,c],ActOut),permutation([george, maria],ActOut),!.
actors_appearing_in_movies([a,b,c,d],ActOut),permutation([george],ActOut),!.
Тест - это вызов предиката actors_appearing_in_movies/2
, которому присваивается входной список фильмов (например, [a,b]
) и который захватывает результирующий список актеров в ActOut
.
Впоследствии нам просто нужно проверить, является ли ActOut
перестановкой ожидаемого набора акторов, следовательно, например:
permutation([george, maria],ActOut)`
"Is ActOut
a list, который является перестановкой списка [george,maria]
?.
Если этот вызов завершается успешно (думаю, не возвращается с false
), тест проходит.
Terminal !
является оператором обрезки и используется для того, чтобы подсистема Prolog не пыталась найти другие решения, потому что мы хороши на этом этапе.
Обратите внимание, что для пустого набора фильмов , мы получаем всех актеров . Это возможно правильно: каждый актер снимается во всех фильмах пустого набора ( Vacuous Truth ).
Сейчас в SQL .
Эта проблема прямо в области реляционной алгебры, и есть SQL, поэтому давайте пр. go на этом. Здесь я использую MySQL.
Во-первых, установите факты.
DROP TABLE IF EXISTS starsin;
CREATE TABLE starsin (movie CHAR(20) NOT NULL, actor CHAR(20) NOT NULL);
INSERT INTO starsin VALUES
( "a" , "bob" ),
( "c" , "bob" ),
( "a" , "maria" ),
( "b" , "maria" ),
( "c" , "maria" ),
( "a" , "george" ),
( "b" , "george" ),
( "c" , "george" ),
( "d", "george" );
Относительно набора фильмов, указанных в качестве входных данных, давая их в форма (временной) таблицы звучит естественно. В MySQL «временные таблицы» являются локальными для сеанса. Хорошо.
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
INSERT INTO movies_in VALUES ("a"), ("b");
Подход:
Результаты теперь можно получить, получив для каждого актера пересечение набора фильмов, обозначенного movies_in
, и набора фильмов в какой актер когда-либо появлялся (создавался для каждого актера с помощью внутреннего соединения), а затем подсчитывал (для каждого актера), имеет ли результирующий набор как минимум столько же записей, сколько набор movies_in
.
Обернуть запрос в процедура по практическим соображениям. A разделитель полезен здесь:
DELIMITER $$
DROP PROCEDURE IF EXISTS actors_appearing_in_movies;
CREATE PROCEDURE actors_appearing_in_movies()
BEGIN
SELECT
d.actor
FROM
starsin d, movies_in q
WHERE
d.movie = q.movie
GROUP BY
actor
HAVING
COUNT(*) >= (SELECT COUNT(*) FROM movies_in);
END$$
DELIMITER ;
Запустите его!
Появляется проблема A:
Есть ли лучше чем редактировать + копировать-вставить код создания таблицы, выдать CALL
и проверить результаты "вручную"?
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
CALL actors_appearing_in_movies();
Пустой набор!
Появляется проблема B:
Выше не требуется, я хочу "всех актеров", так же, как для решение Пролог. Поскольку я не хочу добавлять в код странное исключение в крайнем случае, мой подход должен быть неверным. Есть ли тот, который естественно охватывает этот случай, но не становится слишком сложным? T- SQL и PostgreSQL однострочники тоже подойдут!
Другие тестовые примеры дают ожидаемые данные:
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
INSERT INTO movies_in VALUES ("a"), ("b");
CALL actors_appearing_in_movies();
+--------+
| actor |
+--------+
| george |
| maria |
+--------+
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
INSERT INTO movies_in VALUES ("a"), ("b"), ("c");
CALL actors_appearing_in_movies();
+--------+
| actor |
+--------+
| george |
| maria |
+--------+
DROP TABLE IF EXISTS movies_in;
CREATE TEMPORARY TABLE movies_in (movie CHAR(20) NOT NULL);
INSERT INTO movies_in VALUES ("a"), ("b"), ("c"), ("d");
CALL actors_appearing_in_movies();
+--------+
| actor |
+--------+
| george |
+--------+