Números primos en un rango dado


10

Recientemente, me dieron la tarea de imprimir todos los números primos (1-100). Fallé drásticamente allí. Mi código:

Create Procedure PrintPrimeNumbers
@startnum int,
@endnum int
AS 
BEGIN
Declare @a INT;
Declare @i INT = 1
(
Select a = @startnum / 2;
WHILE @i<@a
BEGIN
@startnum%(@a-@i)
i=i+1;
)
END

Aunque terminé sin completarlo, me pregunto si es factible hacer dichos programas en la base de datos (SQL Server 2008 R2).

En caso afirmativo, cómo puede terminar.


2
Para no quitarme ninguna de las respuestas dadas, pero este es el mejor artículo que he visto sobre el tema: sqlblog.com/blogs/hugo_kornelis/archive/2006/09/23/…
Erik Darling

¿El objetivo es hacer 1 - 100, o cualquier rango y 1 - 100 fue solo un rango de ejemplo?
Solomon Rutzky

En mi pregunta, era de 1 a 100. Sería bueno tener un enfoque generalista, luego uno específico.
ispostback

Respuestas:


11

Con mucho, la forma más rápida y fácil de imprimir "todos los números primos (1-100)" es aceptar plenamente el hecho de que los números primos son un conjunto de valores conocido, finito e inmutable ("conocido" y "finito" dentro de un rango particular, por supuesto). A esta pequeña escala, ¿por qué desperdiciar CPU cada vez para calcular un montón de valores que se conocen desde hace mucho tiempo, y apenas ocupa memoria para almacenar?

SELECT tmp.[Prime]
FROM   (VALUES (2), (3), (5), (7), (11), (13),
        (17), (19), (23), (29), (31), (37), (41),
        (43), (47), (53), (59), (61), (67), (71),
        (73), (79), (83), (89), (97)) tmp(Prime)

Por supuesto, si necesita calcular los números primos entre 1 y 100, lo siguiente es bastante eficiente:

;WITH base AS
(
    SELECT tmp.dummy, ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS [num]
    FROM   (VALUES (0), (0), (0), (0), (0), (0), (0)) tmp(dummy)
), nums AS
(
    SELECT  (ROW_NUMBER() OVER (ORDER BY (SELECT 1)) * 2) + 1 AS [num]
    FROM        base b1
    CROSS JOIN  base b2
), divs AS
(
    SELECT  [num]
    FROM        base b3
    WHERE   b3.[num] > 4
    AND     b3.[num] % 2 <> 0
    AND     b3.[num] % 3 <> 0
)
SELECT  given.[num] AS [Prime]
FROM        (VALUES (2), (3)) given(num)
UNION ALL
SELECT  n.[num] AS [Prime]
FROM        nums n
WHERE   n.[num] % 3 <> 0
AND     NOT EXISTS (SELECT *
                    FROM divs d
                    WHERE d.[num] <> n.[num]
                    AND n.[num] % d.[num] = 0
                    );

Esta consulta solo prueba números impares ya que los números pares no serán primos de todos modos. También es específico para el rango de 1 a 100.

Ahora, si necesita un rango dinámico (similar a lo que se muestra en el código de ejemplo en la pregunta), la siguiente es una adaptación de la consulta anterior que aún es bastante eficiente (calculó el rango de 1 - 100,000 - 9592 entradas - en menos de 1 segundo):

DECLARE  @RangeStart INT = 1,
         @RangeEnd INT = 100000;
DECLARE  @HowMany INT = CEILING((@RangeEnd - @RangeStart + 1) / 2.0);

;WITH frst AS
(
    SELECT  tmp.thing1
    FROM        (VALUES (0), (0), (0), (0), (0), (0), (0), (0), (0), (0)) tmp(thing1)
), scnd AS
(
    SELECT  0 AS [thing2]
    FROM        frst t1
    CROSS JOIN frst t2
    CROSS JOIN frst t3
), base AS
(
    SELECT  TOP( CONVERT( INT, CEILING(SQRT(@RangeEnd)) ) )
            ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS [num]
    FROM        scnd s1
    CROSS JOIN  scnd s2
), nums AS
(
    SELECT  TOP (@HowMany)
            (ROW_NUMBER() OVER (ORDER BY (SELECT 1)) * 2) + 
                (@RangeStart - 1 - (@RangeStart%2)) AS [num]
    FROM        base b1
    CROSS JOIN  base b2
), divs AS
(
    SELECT  [num]
    FROM        base b3
    WHERE   b3.[num] > 4
    AND     b3.[num] % 2 <> 0
    AND     b3.[num] % 3 <> 0
)
SELECT  given.[num] AS [Prime]
FROM        (VALUES (2), (3)) given(num)
WHERE   given.[num] >= @RangeStart
UNION ALL
SELECT  n.[num] AS [Prime]
FROM        nums n
WHERE   n.[num] BETWEEN 5 AND @RangeEnd
AND     n.[num] % 3 <> 0
AND     NOT EXISTS (SELECT *
                    FROM divs d
                    WHERE d.[num] <> n.[num]
                    AND n.[num] % d.[num] = 0
                    );

Mi prueba (usando SET STATISTICS TIME, IO ON;) muestra que esta consulta funciona mejor que las otras dos respuestas dadas (hasta ahora):

ALCANCE: 1-100

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Solomon      0                 0                   0
Dan        396                 0                   0
Martin     394                 0                   1

ALCANCE: 1 - 10,000

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Solomon        0                   47                170
Dan        77015                 2547               2559
Martin       n/a

ALCANCE: 1 - 100,000

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Solomon            0                 984                996
Dan        3,365,469             195,766            196,650
Martin           n/a

ALCANCE: 99,900 - 100,000

NOTA : Para ejecutar esta prueba, tuve que corregir un error en el código de Dan; @startnumno se incluyó en la consulta, por lo que siempre comenzó en 1. Reemplacé la Dividend.num <= @endnumlínea con Dividend.num BETWEEN @startnum AND @endnum.

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Solomon       0                   0                   1
Dan           0                 157                 158
Martin      n/a

ALCANCE: 1 - 100,000 (nueva prueba parcial)

Después de arreglar la consulta de Dan para la prueba 99,900 - 100,000, noté que no había más lecturas lógicas en la lista. Así que volví a probar este rango con esa corrección aún aplicada y descubrí que las lecturas lógicas habían desaparecido nuevamente y los tiempos eran ligeramente mejores (y sí, se devolvió el mismo número de filas).

Query      Logical Reads       CPU Milliseconds    Elapsed Milliseconds
-------    ----------------    ----------------    -----------------

Dan                0             179,594            180,096

¿Para qué sirve ROW_NUMBER() OVER (ORDER BY (SELECT 1))? ¿No ROW_NUMBER() OVER ()sería equivalente?
Lennart

Hola @Lennart .Si intenta utilizar OVER (), obtendrá el siguiente error: The function 'ROW_NUMBER' must have an OVER clause with ORDER BY.. Y, con ORDER BY, no puede ser una constante, de ahí la subconsulta para devolver una constante.
Solomon Rutzky

1
Gracias, no estaba al tanto de esta limitación en el servidor SQL. Tiene sentido ahora
Lennart

¿Por qué si lo uso DECLARE @RangeStart INT = 999900, @RangeEnd INT = 1000000;funciona pero tan pronto como lo configuro DECLARE @RangeStart INT = 9999999900, @RangeEnd INT = 10000000000;dice Msg 8115, Level 16, State 2, Line 1 Arithmetic overflow error converting expression to data type int. Msg 1014, Level 15, State 1, Line 5 A TOP or FETCH clause contains an invalid value.?
Francesco Mantovani

1
@FrancescoMantovani Ese error está diciendo que sus valores están fuera del rango de INT. El valor máximo que INTpuede contener es 2,147,483,647, que es menor que su valor inicial de 9,999,999,900. Obtiene ese error incluso si ejecuta solo el DECLARE. Puede intentar cambiar los tipos de datos variables para que sean BIGINTy ver cómo funciona. Es posible que se necesiten otros cambios menores para respaldar eso. Para los rangos de tipo de datos, consulte: int, bigint, smallint y tinyint .
Solomon Rutzky

7

Sería una forma simple pero no muy eficiente de devolver los números primos en el rango 2-100 (1 no es primo)

WITH Ten AS (SELECT * FROM (VALUES(0),(1),(2),(3),(4),(5),(6),(7),(8),(9)) V(N)),
     Hundred(N) AS (SELECT T1.N * 10 + T2.N + 1 FROM Ten T1, Ten T2)
SELECT H1.N
FROM   Hundred H1
WHERE  H1.N > 1
       AND NOT EXISTS(SELECT *
                      FROM   Hundred H2
                      WHERE  H2.N > 1
                             AND H1.N > H2.N
                             AND H1.N % H2.N = 0);

También podría materializar los números del 2 al 100 en una tabla e implementar el Tamiz de Eratóstenes mediante actualizaciones o eliminaciones repetidas.


4

Me pregunto si es factible hacer tales programas en la base de datos.

Sí, es factible, pero no creo que T-SQL sea la herramienta adecuada para el trabajo. A continuación se muestra un ejemplo de un enfoque basado en conjuntos en T-SQL para este problema.

CREATE PROC dbo.PrintPrimeNumbers
    @startnum int,
    @endnum int
AS 
WITH 
     t4 AS (SELECT n FROM (VALUES(0),(0),(0),(0)) t(n))
    ,t256 AS (SELECT 0 AS n FROM t4 AS a CROSS JOIN t4 AS b CROSS JOIN t4 AS c CROSS JOIN t4 AS d)
    ,t16M AS (SELECT ROW_NUMBER() OVER (ORDER BY (a.n)) AS num FROM t256 AS a CROSS JOIN t256 AS b CROSS JOIN t256 AS c)
SELECT num
FROM t16M AS Dividend
WHERE
    Dividend.num <= @endnum
    AND NOT EXISTS(
        SELECT 1
        FROM t16M AS Divisor
        WHERE
            Divisor.num <= @endnum
            AND Divisor.num BETWEEN 2 AND SQRT(Dividend.num)
            AND Dividend.num % Divisor.num = 0
            AND Dividend.num <= @endnum
    );
GO
EXEC dbo.PrintPrimeNumbers 1, 100;
GO

0

Podemos escribir el siguiente código y funciona:

CREATE procedure sp_PrimeNumber(@number int)
as 
begin
declare @i int
declare @j int
declare @isPrime int
set @isPrime=1
set @i=2
set @j=2
while(@i<=@number)
begin
    while(@j<=@number)
    begin
        if((@i<>@j) and (@i%@j=0))
        begin
            set @isPrime=0
            break
        end
        else
        begin
            set @j=@j+1
        end
    end
    if(@isPrime=1)
    begin
        SELECT @i
    end
    set @isPrime=1
    set @i=@i+1
    set @j=2
end
end

Arriba he creado un Procedimiento almacenado para obtener números primos.

Para conocer los resultados, ejecute el procedimiento almacenado:

EXECUTE sp_PrimeNumber 100

0
DECLARE @UpperLimit INT, @LowerLimit INT

SET @UpperLimit = 500
SET @LowerLimit = 100

DECLARE @N INT, @P INT
DECLARE @Numbers TABLE (Number INT NULL)
DECLARE @Composite TABLE (Number INT NULL)

SET @P = @UpperLimit

IF (@LowerLimit > @UpperLimit OR @UpperLimit < 0 OR @LowerLimit < 0 )
    BEGIN
        PRINT 'Incorrect Range'
    END 
ELSE
    BEGIN
        WHILE @P > @LowerLimit
            BEGIN
                INSERT INTO @Numbers(Number) VALUES (@P)
                SET @N = 2
                WHILE @N <= @UpperLimit/2
                    BEGIN
                        IF ((@P%@N = 0 AND @P <> @N) OR (@P IN (0, 1)))
                            BEGIN
                                INSERT INTO @Composite(Number) VALUES (@P)
                                BREAK
                            END
                        SET @N = @N + 1
                    END
                SET @P = @P - 1
            END
        SELECT Number FROM @Numbers
        WHERE Number NOT IN (SELECT Number FROM @Composite)
        ORDER BY Number
        END
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.