RosettaCodeData/Task/100-prisoners/Transact-SQL/100-prisoners.sql

178 lines
5.3 KiB
Transact-SQL

USE rosettacode;
GO
SET NOCOUNT ON;
GO
CREATE TABLE dbo.numbers (n INT PRIMARY KEY);
GO
-- NOTE If you want to play more than 10000 games, you need to extend the query generating the numbers table by adding
-- next cross joins. Now the table contains enough values to solve the task and it takes less processing time.
WITH sample100 AS (
SELECT TOP(100) object_id
FROM master.sys.objects
)
INSERT numbers
SELECT ROW_NUMBER() OVER (ORDER BY A.object_id) AS n
FROM sample100 AS A
CROSS JOIN sample100 AS B;
GO
CREATE TABLE dbo.drawers (drawer INT PRIMARY KEY, card INT);
GO
CREATE TABLE dbo.results (strategy VARCHAR(10), game INT, result BIT, PRIMARY KEY (game, strategy));
GO
CREATE PROCEDURE dbo.shuffleDrawers @prisonersCount INT
AS BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT * FROM drawers)
INSERT drawers (drawer, card)
SELECT n AS drawer, n AS card
FROM numbers
WHERE n <= @prisonersCount;
DECLARE @randoms TABLE (n INT, random INT);
DECLARE @n INT = 1;
WHILE @n <= @prisonersCount BEGIN
INSERT @randoms VALUES (@n, ROUND(RAND() * (@prisonersCount - 1), 0) + 1);
SET @n = @n + 1;
END;
WITH ordered AS (
SELECT ROW_NUMBER() OVER (ORDER BY random ASC) AS drawer,
n AS card
FROM @randoms
)
UPDATE drawers
SET card = o.card
FROM drawers AS s
INNER JOIN ordered AS o
ON o.drawer = s.drawer;
END
GO
CREATE PROCEDURE dbo.find @prisoner INT, @strategy VARCHAR(10)
AS BEGIN
-- A prisoner can open no more than 50 drawers.
DECLARE @drawersCount INT = (SELECT COUNT(*) FROM drawers);
DECLARE @openMax INT = @drawersCount / 2;
-- Prisoners start outside the room.
DECLARE @card INT = NULL;
DECLARE @open INT = 1;
WHILE @open <= @openMax BEGIN
-- A prisoner tries to find his own number.
IF @strategy = 'random' BEGIN
DECLARE @random INT = ROUND(RAND() * (@drawersCount - 1), 0) + 1;
SET @card = (SELECT TOP(1) card FROM drawers WHERE drawer = @random);
END
IF @strategy = 'optimal' BEGIN
IF @card IS NULL BEGIN
SET @card = (SELECT TOP(1) card FROM drawers WHERE drawer = @prisoner);
END ELSE BEGIN
SET @card = (SELECT TOP(1) card FROM drawers WHERE drawer = @card);
END
END
-- A prisoner finding his own number is then held apart from the others.
IF @card = @prisoner
RETURN 1;
SET @open = @open + 1;
END
RETURN 0;
END
GO
CREATE PROCEDURE dbo.playGame @gamesCount INT, @strategy VARCHAR(10), @prisonersCount INT = 100
AS BEGIN
SET NOCOUNT ON;
IF @gamesCount <> (SELECT COUNT(*) FROM results WHERE strategy = @strategy) BEGIN
DELETE results
WHERE strategy = @strategy;
INSERT results (strategy, game, result)
SELECT @strategy AS strategy, n AS game, 0 AS result
FROM numbers
WHERE n <= @gamesCount;
END
UPDATE results
SET result = 0
WHERE strategy = @strategy;
DECLARE @game INT = 1;
WHILE @game <= @gamesCount BEGIN
-- A room having a cupboard of 100 opaque drawers numbered 1 to 100, that cannot be seen from outside.
-- Cards numbered 1 to 100 are placed randomly, one to a drawer, and the drawers all closed; at the start.
EXECUTE shuffleDrawers @prisonersCount;
-- A prisoner tries to find his own number.
-- Prisoners start outside the room.
-- They can decide some strategy before any enter the room.
DECLARE @prisoner INT = 1;
DECLARE @found INT = 0;
WHILE @prisoner <= @prisonersCount BEGIN
EXECUTE @found = find @prisoner, @strategy;
IF @found = 1
SET @prisoner = @prisoner + 1;
ELSE
BREAK;
END;
-- If all 100 findings find their own numbers then they will all be pardoned. If any don't then all sentences stand.
IF @found = 1
UPDATE results SET result = 1 WHERE strategy = @strategy AND game = @game;
SET @game = @game + 1;
END
END
GO
CREATE FUNCTION dbo.computeProbability(@strategy VARCHAR(10))
RETURNS decimal (18, 2)
AS BEGIN
RETURN (
SELECT (SUM(CAST(result AS INT)) * 10000 / COUNT(*)) / 100
FROM results
WHERE strategy = @strategy
);
END
GO
-- Simulate several thousand instances of the game:
DECLARE @gamesCount INT = 2000;
-- ...where the prisoners randomly open drawers.
EXECUTE playGame @gamesCount, 'random';
-- ...where the prisoners use the optimal strategy mentioned in the Wikipedia article.
EXECUTE playGame @gamesCount, 'optimal';
-- Show and compare the computed probabilities of success for the two strategies.
DECLARE @log VARCHAR(max);
SET @log = CONCAT('Games count: ', @gamesCount);
RAISERROR (@log, 0, 1) WITH NOWAIT;
SET @log = CONCAT('Probability of success with "random" strategy: ', dbo.computeProbability('random'));
RAISERROR (@log, 0, 1) WITH NOWAIT;
SET @log = CONCAT('Probability of success with "optimal" strategy: ', dbo.computeProbability('optimal'));
RAISERROR (@log, 0, 1) WITH NOWAIT;
GO
DROP FUNCTION dbo.computeProbability;
DROP PROCEDURE dbo.playGame;
DROP PROCEDURE dbo.find;
DROP PROCEDURE dbo.shuffleDrawers;
DROP TABLE dbo.results;
DROP TABLE dbo.drawers;
DROP TABLE dbo.numbers;
GO