Verifica se una stringa è un palindromo usando T-SQL


24

Sono un principiante in T-SQL. Voglio decidere se una stringa di input è un palindromo, con output = 0 se non lo è e output = 1 se lo è. Sto ancora cercando di capire la sintassi. Non ricevo nemmeno un messaggio di errore. Sto cercando diverse soluzioni e alcuni feedback, per ottenere una migliore comprensione e conoscenza di come funziona T-SQL, per migliorarmi - Sono ancora uno studente.

L'idea chiave, per come la vedo io, è confrontare i caratteri sinistro e destro tra loro, controllare l'uguaglianza, quindi continuare a confrontare il secondo carattere da sinistra con il 2 ° dall'ultimo, ecc. Facciamo un ciclo: se i personaggi sono uguali tra loro, continuiamo. Se abbiamo raggiunto la fine, abbiamo prodotto 1, in caso contrario, abbiamo prodotto 0.

Per favore, critica:

CREATE function Palindrome(
    @String  Char
    , @StringLength  Int
    , @n Int
    , @Palindrome BIN
    , @StringLeftLength  Int
)
RETURNS Binary
AS
BEGIN
SET @ n=1
SET @StringLength= Len(String)

  WHILE @StringLength - @n >1

  IF
  Left(String,@n)=Right(String, @StringLength)

 SET @n =n+1
 SET @StringLength =StringLength -1

 RETURN @Binary =1

 ELSE RETURN @Palindrome =0

END

Penso di essere sulla buona strada, ma sono ancora molto lontano. Qualche idea?


LTRIM(RTRIM(...))spazio bianco?
WernerCD,

Risposte:


60

Se si utilizza SQL Server è possibile utilizzare la funzione REVERSE () per verificare?

SELECT CASE WHEN @string = REVERSE(@String) THEN 1 ELSE 0 END AS Palindrome;

Compreso il commento di Martin Smith, se si utilizza SQL Server 2012+ è possibile utilizzare la funzione IIF () :

SELECT IIF(@string = REVERSE(@String),1,0) AS Palindrome;

17

Dato che ci sono un discreto numero di soluzioni, vado con la parte "critica" della tua domanda. Un paio di note: ho corretto alcuni errori di battitura e ho notato dove ho fatto. Se sbaglio sul fatto che sono un errore di battitura, menzionalo nei commenti e spiegherò cosa sta succedendo. Sottolineerò diverse cose che potresti già sapere, quindi per favore non offenderti se l'ho fatto. Alcuni commenti possono sembrare schizzinosi ma non so dove ti trovi nel tuo viaggio, quindi devi presumere che stai appena iniziando.

CREATE function Palindrome (
    @String  Char
    , @StringLength  Int
    , @n Int
    , @Palindrome BIN
    , @StringLeftLength  Int

Includere SEMPRE la lunghezza con a charo varchardefinizione. Aaron Bertrand ne parla in modo approfondito qui . Sta parlando varcharma lo stesso vale per char. Userei a varchar(255)per questo se vuoi solo stringhe relativamente corte o forse una varchar(8000)per quelle più grandi o anche varchar(max). Varcharè per stringhe a lunghezza variabile charè solo per quelle fisse. Dato che non sei sicuro della lunghezza della stringa che viene passata in uso varchar. Inoltre non lo binaryè bin.

Successivamente non è necessario inserire tutte queste variabili come parametri. Dichiarali nel tuo codice. Inserisci qualcosa nell'elenco dei parametri solo se prevedi di passarlo dentro o fuori. (Vedrai come appare alla fine.) Inoltre hai @StringLeftLength ma non lo usi mai. Quindi non lo dichiarerò.

La prossima cosa che farò è riformattare un po 'per rendere ovvie alcune cose.

BEGIN
    SET @n=1
    SET @StringLength = Len(@String) -- Missed an @

    WHILE @StringLength - @n >1 
        IF Left(@String,@n)=Right(@String, @StringLength) -- More missing @s
            SET @n = @n + 1 -- Another missing @

    SET @StringLength = @StringLength - 1  -- Watch those @s :)

    RETURN @Palindrome = 1 -- Assuming another typo here 

    ELSE 
        RETURN @Palindrome =0

END

Se guardi come ho fatto il rientro noterai che ho questo:

    WHILE @StringLength - @n >1 
        IF Left(@String,@n)=Right(@String, @StringLength)
            SET @n = @n + 1

Questo perché i comandi gradiscono WHILEe IFinfluenzano solo la prima riga di codice dopo di essi. Devi usare un BEGIN .. ENDblocco se vuoi più comandi. Così riparando che otteniamo:

    WHILE @StringLength - @n > 1 
        IF Left(@String,@n)=Right(@String, @StringLength)
            BEGIN 
                SET @n = @n + 1
                SET @StringLength = @StringLength - 1
                RETURN @Palindrome = 1 
            END
        ELSE 
            RETURN @Palindrome = 0

Noterai che ho aggiunto solo un BEGIN .. ENDblocco in IF. Questo perché anche se l' IFistruzione è lunga più righe (e contiene anche più comandi) è comunque una singola istruzione (che copre tutto ciò che viene eseguito nelle parti IFe ELSEdell'istruzione).

Successivamente riceverai un errore dopo entrambi RETURNs. È possibile restituire una variabile o un valore letterale. Non è possibile impostare la variabile e restituirla contemporaneamente.

                SET @Palindrome = 1 
            END
        ELSE 
            SET @Palindrome = 0

    RETURN @Palindrome

Ora siamo nella logica. Prima di tutto, vorrei sottolineare che le funzioni LEFTe RIGHTche stai utilizzando sono fantastiche, ma ti daranno il numero di caratteri che passi dalla direzione richiesta. Supponiamo che tu abbia passato la parola "test". Al primo passaggio otterrai questo (rimuovendo le variabili):

LEFT('test',1) = RIGHT('test',4)
    t          =      test

LEFT('test',2) = RIGHT('test',3)
    te         =      est

Ovviamente non è quello che ti aspettavi. Si vorrebbe davvero usare substringinvece. Il sottostringa ti consente di passare non solo il punto iniziale ma la lunghezza. Quindi otterresti:

SUBSTRING('test',1,1) = SUBSTRING('test',4,1)
         t            =         t

SUBSTRING('test',2,1) = SUBSTRING('test',3,1)
         e            =         s

Successivamente si stanno incrementando le variabili utilizzate nel proprio ciclo solo in una condizione dell'istruzione IF. Estrai la variabile incrementando completamente da quella struttura. Ciò richiederà un BEGIN .. ENDblocco aggiuntivo , ma riesco a rimuovere l'altro.

        WHILE @StringLength - @n > 1 
            BEGIN
                IF SUBSTRING(@String,@n,1) = SUBSTRING(@String, @StringLength,1)
                    SET @Palindrome = 1 
                ELSE 
                    SET @Palindrome = 0

                SET @n = @n + 1
                SET @StringLength = @StringLength - 1
            END

È necessario modificare le WHILEcondizioni per consentire l'ultimo test.

        WHILE @StringLength > @n 

E, ultimo ma non meno importante, il modo in cui si trova ora non testiamo l'ultimo personaggio se c'è un numero dispari di personaggi. Ad esempio con "ana" nnon è stato testato. Va bene, ma a me serve tenere conto di una parola di una sola lettera (se vuoi che sia considerata positiva). Quindi possiamo farlo impostando il valore in anticipo.

E ora finalmente abbiamo:

CREATE FUNCTION Palindrome (@String  varchar(255)) 
RETURNS Binary
AS

    BEGIN
        DECLARE @StringLength  Int
            , @n Int
            , @Palindrome binary

        SET @n = 1
        SET @StringLength = Len(@String)
        SET @Palindrome = 1

        WHILE @StringLength > @n 
            BEGIN
                IF SUBSTRING(@String,@n,1) = SUBSTRING(@String, @StringLength,1)
                    SET @Palindrome = 1 
                ELSE 
                    SET @Palindrome = 0

                SET @n = @n + 1
                SET @StringLength = @StringLength - 1
            END
        RETURN @Palindrome
    END

Un ultimo commento Sono un grande fan della formattazione in generale. Può davvero aiutarti a vedere come funziona il tuo codice e ad evidenziare possibili errori.

modificare

Come menzionato da Sphinxxx, abbiamo ancora un difetto nella nostra logica. Una volta premuto il tasto ELSEe impostato @Palindromesu 0, non ha senso continuare. In effetti a quel punto potremmo solo RETURN.

                IF SUBSTRING(@String,@n,1) = SUBSTRING(@String, @StringLength,1)
                    SET @Palindrome = 1 
                ELSE 
                    RETURN 0

Dato che ora stiamo usando solo @Palindromeper "è ancora possibile che questo sia un palindromo" non ha davvero senso averlo. Possiamo liberarci della variabile e cambiare la nostra logica in corto circuito in caso di guasto (il RETURN 0) e RETURN 1(una risposta positiva) solo se riesce a superare il ciclo. Noterai che questo in realtà semplifica in qualche modo la nostra logica.

CREATE FUNCTION Palindrome (@String  varchar(255)) 
RETURNS Binary
AS

    BEGIN
        DECLARE @StringLength  Int
            , @n Int

        SET @n = 1
        SET @StringLength = Len(@String)

        WHILE @StringLength > @n 
            BEGIN
                IF SUBSTRING(@String,@n,1) <> SUBSTRING(@String, @StringLength,1)
                    RETURN 0

                SET @n = @n + 1
                SET @StringLength = @StringLength - 1
            END
        RETURN 1
    END

15

È inoltre possibile utilizzare un approccio di tabella Numbers.

Se non disponi già di una tabella di numeri ausiliari, puoi crearne una come segue. Questo è popolato con un milione di righe e quindi sarà buono per lunghezze di stringa fino a 2 milioni di caratteri.

CREATE TABLE dbo.Numbers (number int PRIMARY KEY);

INSERT INTO dbo.Numbers
            (number)
SELECT TOP 1000000 ROW_NUMBER() OVER (ORDER BY @@SPID)
FROM   master..spt_values v1,
       master..spt_values v2 

Quanto segue confronta ogni carattere a sinistra con il suo partner corrispondente a destra, e se si riscontrano discrepanze può cortocircuitare e restituire 0. Se la stringa ha una lunghezza dispari, il carattere centrale non viene controllato in quanto ciò non altera il risultato .

DECLARE @Candidate VARCHAR(MAX) = 'aibohphobia'; /*the irrational fear of palindromes.*/

SET @Candidate = LTRIM(RTRIM(@Candidate)); /*Ignoring any leading or trailing spaces. 
                      Could use `DATALENGTH` instead of `LEN` if these are significant*/

SELECT CASE
         WHEN EXISTS (SELECT *
                      FROM   dbo.Numbers
                      WHERE  number <= LEN(@Candidate) / 2
                             AND SUBSTRING(@Candidate, number, 1) 
                              <> SUBSTRING(@Candidate, 1 + LEN(@Candidate) - number, 1))
           THEN 0
         ELSE 1
       END AS IsPalindrome 

Se non sei sicuro di come funzioni, puoi vedere da sotto

DECLARE @Candidate VARCHAR(MAX) = 'this is not a palindrome';

SELECT SUBSTRING(@Candidate, number, 1)                       AS [Left],
       SUBSTRING(@Candidate, 1 + LEN(@Candidate) - number, 1) AS [Right]
FROM   dbo.Numbers
WHERE  number <= LEN(@Candidate) / 2; 

inserisci qui la descrizione dell'immagine

Questo è sostanzialmente lo stesso algoritmo descritto nella domanda, ma fatto in un modo basato su set piuttosto che in codice procedurale iterativo.


12

Il REVERSE()metodo "migliorato", ovvero invertendo solo la metà della stringa:

SELECT CASE WHEN RIGHT(@string, LEN(@string)/2) 
                 = REVERSE(LEFT(@string, LEN(@string)/2)) 
            THEN 1 ELSE 0 END AS Palindrome;

Non mi aspetto che accada qualcosa di strano se la stringa ha un numero dispari di caratteri; il personaggio medio non deve essere controllato.


@Hvd ha fatto notare che questo potrebbe non gestire correttamente le coppie surrogate in tutte le regole di confronto.

@srutzky ha commentato che gestisce i caratteri supplementari / coppie surrogate allo stesso modo del REVERSE()metodo, in quanto funzionano correttamente solo quando termina il confronto predefinito del database corrente _SC.


8

Senza usare REVERSE, che è quello che mi viene subito in mente, ma comunque usando una funzione 1 ; Costruirò qualcosa di simile al seguente.

Questa parte ha semplicemente rimosso la funzione esistente, se esiste già:

IF OBJECT_ID('dbo.IsPalindrome') IS NOT NULL
DROP FUNCTION dbo.IsPalindrome;
GO

Questa è la funzione stessa:

CREATE FUNCTION dbo.IsPalindrome
(
    @Word NVARCHAR(500)
) 
RETURNS BIT
AS
BEGIN
    DECLARE @IsPalindrome BIT;
    DECLARE @LeftChunk NVARCHAR(250);
    DECLARE @RightChunk NVARCHAR(250);
    DECLARE @StrLen INT;
    DECLARE @Pos INT;

    SET @RightChunk = '';
    SET @IsPalindrome = 0;
    SET @StrLen = LEN(@Word) / 2;
    IF @StrLen % 2 = 1 SET @StrLen = @StrLen - 1;
    SET @Pos = LEN(@Word);
    SET @LeftChunk = LEFT(@Word, @StrLen);

    WHILE @Pos > (LEN(@Word) - @StrLen)
    BEGIN
        SET @RightChunk = @RightChunk + SUBSTRING(@Word, @Pos, 1)
        SET @Pos = @Pos - 1;
    END

    IF @LeftChunk = @RightChunk SET @IsPalindrome = 1;
    RETURN (@IsPalindrome);
END
GO

Qui, testiamo la funzione:

IF dbo.IsPalindrome('This is a word') = 1 
    PRINT 'YES'
ELSE
    PRINT 'NO';

IF dbo.IsPalindrome('tattarrattat') = 1 
    PRINT 'YES'
ELSE
    PRINT 'NO';

Questo confronta la prima metà della parola con il contrario dell'ultima metà della parola (senza usare la REVERSEfunzione). Questo codice gestisce correttamente le parole pari e dispari. Invece di scorrere l'intera parola, otteniamo semplicemente LEFTla prima metà della parola, quindi passiamo attraverso l'ultima metà della parola per ottenere la parte inversa della metà destra. Se la parola ha una lunghezza dispari, saltiamo la lettera di mezzo, poiché per definizione sarà la stessa per entrambe le "metà".


1 - le funzioni possono essere molto lente!


6

Senza usare REVERSE ... È sempre divertente usare una soluzione ricorsiva;) (Ho fatto il mio in SQL Server 2012, le versioni precedenti potrebbero avere limitazioni sulla ricorsione)

create function dbo.IsPalindrome (@s varchar(max)) returns bit
as
begin
    return case when left(@s,1) = right(@s,1) then case when len(@s) < 3 then 1 else dbo.IsPalindrome(substring(@s,2,len(@s)-2)) end else 0 end;
end;
GO

select dbo.IsPalindrome('a')
1
select dbo.IsPalindrome('ab')
0
select dbo.IsPalindrome('bab')
1
select dbo.IsPalindrome('gohangasalamiimalasagnahog')
1

6

Questa è una versione in-line per TVF della soluzione basata su set di Martin Smith , ulteriormente decorata con un paio di miglioramenti superflui:

WITH Nums AS
(
  SELECT
    N = number
  FROM
    dbo.Numbers WITH(FORCESEEK) /*Requires a suitably indexed numbers table*/
)
SELECT
  IsPalindrome =
    CASE
      WHEN EXISTS
      (
        SELECT *
        FROM Nums
        WHERE N <= L / 2
          AND SUBSTRING(S, N, 1) <> SUBSTRING(S, 1 + L - N, 1)
      )
      THEN 0
      ELSE 1
    END
FROM
  (SELECT LTRIM(RTRIM(@Candidate)), LEN(@Candidate)) AS v (S, L)
;

5

Solo per divertimento, ecco una funzione scalare definita dall'utente di SQL Server 2016 con la funzionalità OLTP in memoria:

ALTER FUNCTION dbo.IsPalindrome2 ( @inputString NVARCHAR(500) )
RETURNS BIT
WITH NATIVE_COMPILATION, SCHEMABINDING
AS
BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL = SNAPSHOT, LANGUAGE = N'English')

    DECLARE @i INT = 1, @j INT = LEN(@inputString)

    WHILE @i < @j
    BEGIN
        IF SUBSTRING( @inputString, @i, 1 ) != SUBSTRING( @inputString, @j, 1 )
        BEGIN
            RETURN(0)
        END
        ELSE
            SELECT @i+=1, @j-=1

    END

    RETURN(1)

END
GO

4

Un grosso problema che incontrerai è quello con qualsiasi valore maggiore di 1, LEFTo RIGHTrestituirà più caratteri, non il personaggio in quella posizione. Se volessi continuare con questo metodo di test, sarebbe un modo davvero semplice per modificarlo

RIGHT(LEFT(String,@n),1)=LEFT(RIGHT(String, @StringLength),1)

Questo afferrerà sempre il carattere più a destra della stringa sinistra e il carattere più a sinistra della stringa destra.

Forse un modo meno indiretto per verificare questo, però, sarebbe usare SUBSTRING:

SUBSTRING(String, @n, 1) = SUBSTRING(String, ((LEN(String) - @n) + 1), 1)

Si noti che SUBSTRINGè 1-indicizzato, quindi + 1in ((LEN(String) - @n) + 1).

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.