Bir dizenin T-SQL kullanarak bir palindrome olup olmadığını test edin


24

Ben T-SQL'de acemiyim. Bir giriş dizesinin bir palindrom olup olmadığına karar vermek istiyorum, eğer değilse output = 0 ve eğer output = 1 ise. Hala sözdizimini çözüyorum. Hata mesajı bile alamıyorum. T-SQL'in nasıl çalıştığı hakkında daha iyi bir anlayış ve bilgi edinmek, daha iyi olmak için farklı çözümler ve bazı geribildirimler arıyorum - hala bir öğrenciyim.

Gördüğüm gibi, ana fikir sol ve en sağdaki karakterleri birbirleriyle karşılaştırmak, eşitliği kontrol etmek, ardından soldaki ikinci karakteri, soldan ikinci karakterle karşılaştırmak, vb. Bir döngü yaparız: Karakterler birbirine eşitse, devam ederiz. Sona ulaştıysak, 1 çıkar, yoksa 0 çıkar.

Lütfen eleştirir misiniz:

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

Sanırım doğru yoldayım, ama hala çok uzaktayım. Herhangi bir fikir?


LTRIM(RTRIM(...))Beyaz boşluk?
WernerCD

Yanıtlar:


60

SQL Server kullanıyorsanız, kontrol etmek için REVERSE () işlevini kullanabilirsiniz.

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

Martin Smith'in yorumu dahil, eğer SQL Server 2012+ kullanıyorsanız IIF () işlevini kullanabilirsiniz:

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

17

Oldukça fazla sayıda çözüm bulunduğundan, sorunuzun “eleştirel” kısmına gideceğim. Birkaç not: Bazı yazım hataları düzelttim ve yaptığım yeri not aldım. Yazım hatası yapma konusunda yanılıyorsam yorumlarda belirtin ve neler olduğunu açıklayacağım. Bildiğiniz birkaç şeyi işaret edeceğim, öyleyse lütfen yapmayın. Bazı yorumlar seçici görünebilir ama yolculuğunuzda nerede olduğunuzu bilmiyorum bu yüzden yeni başladığınızı varsaymalıyım.

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

HER ZAMAN uzunluğu charveya varchartanımını içerir. Aaron Bertrand burada derinlemesine konuşur . O hakkında konuşuyor varcharama aynı şey için geçerli char. Ben kullanmak istiyorum varchar(255)sadece nispeten kısa dizeleri veya belki istiyorsanız bu için varchar(8000)daha da büyük olanları veya varchar(max). VarcharDeğişken uzunluklu dizgiler charsadece sabit olanlar içindir. Kullanımda geçirilen ipin uzunluğundan emin olamadığınızdan varchar. Ayrıca binarydeğil bin.

Daha sonra tüm bu değişkenleri parametre olarak koymanız gerekmez. Onları kodunuzda bildirin. Parametre listesine yalnızca bir şeyi içeri veya dışarı aktarmayı planlıyorsanız koyun. (Bunun sonunda nasıl göründüğünü göreceksiniz.) Ayrıca @StringLeftLength var ama asla kullanmayın. Bu yüzden bunu ilan etmeyeceğim.

Yapacağım bir sonraki şey, birkaç şeyi açıklığa kavuşturmak için biraz yeniden biçimlendirmek.

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

Girintiyi yaptığım şekle bakarsanız şunu hissedeceksiniz:

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

Bunun nedeni, komutların yalnızca bunlardan sonraki ilk kod satırını sevmesi WHILEve IFetkilemesidir. BEGIN .. ENDBirden fazla komut istiyorsanız, bir blok kullanmanız gerekir . Yani biz bunu düzeltmek:

    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

İçinde sadece bir BEGIN .. ENDblok eklediğimi fark edeceksiniz IF. Bunun nedeni, IFifadenin çok satırlı olmasına rağmen (ve hatta birden çok komut içermesine rağmen), yine de tek bir ifadedir (ifadenin içinde IFve içinde yapılan her şeyi kapsayan ELSE).

Sonra ikisinden de sonra bir hata ile karşılaşacaksınız RETURNs. Bir değişkeni VEYA değişmezi döndürebilirsiniz. Değişkeni ayarlayıp aynı anda döndüremezsiniz.

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

    RETURN @Palindrome

Şimdi mantık içindeyiz. Öncelikle, kullandığınız LEFTve RIGHTişlevlerinin harika olduğunu belirtmeme izin verin , ancak istenen yönden geçirdiğiniz karakter sayısını size verecekler. Diyelim ki "test" kelimesini geçtiniz. İlk geçişte bunu alacaksınız (değişkenleri kaldırarak):

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

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

Açıkçası beklediğiniz gibi değil. Bunun substringyerine gerçekten kullanmak istersiniz . Altlık, yalnızca başlangıç ​​noktasını değil, uzunluğu da geçmenizi sağlar. Böylece alırsınız:

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

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

Daha sonra, döngünüzde kullandığınız değişkenleri yalnızca IF ifadesinin bir koşulunda artıracaksınız. Değişkeni bu yapıdan tamamen dışarı çekerek çıkarın. Bu ek bir BEGIN .. ENDblok gerektirecek , ama diğerini de kaldırıyorum.

        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

WHILESon teste izin vermek için durumunuzu değiştirmeniz gerekir .

        WHILE @StringLength > @n 

Ve sonuncusu ama en önemlisi, şu anki haliyle, tek sayıda karakter varsa son karakteri test etmiyoruz. Mesela 'ana' ile ntest edilmedi. Sorun değil, ancak tek harfli bir kelimeyi hesaba katmamız gerekiyor (eğer bunun pozitif olarak sayılmasını istiyorsanız). Böylece değeri ön ayarlayarak yapabiliriz.

Ve şimdi nihayet biz var:

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

Son bir yorum. Genel olarak formatın büyük bir hayranıyım. Kodunuzun nasıl çalıştığını görmenize ve olası hataları göstermenize yardımcı olabilir.

Düzenle

Sphinxxx'in dediği gibi, mantığımızda hala bir kusur var. Bir kere vurup 0'a ELSEayarladığımızda @Palindromedevam etmenin bir anlamı yok. Aslında tam da bu noktada yapabiliriz RETURN.

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

Şimdi sadece @Palindrome"hala bu bir palindrom olabilir" için kullandığımız göz önüne alındığında, buna sahip olmanın hiçbir anlamı yoktur. Değişkenden kurtulup mantığımızı başarısızlık (the ) ve (olumlu bir cevap) durumunda kısa devreye geçirebiliriz, ancak bunu tüm döngü boyunca gerçekleştirirse. Bunun aslında mantığımızı bir şekilde basitleştirdiğini fark edeceksiniz.RETURN 0RETURN 1

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

Sayılar tablosu yaklaşımını da kullanabilirsiniz.

Zaten bir yardımcı sayılar tablonuz yoksa aşağıdaki gibi bir tane oluşturabilirsiniz. Bu, bir milyon satırla doldurulur ve bu nedenle 2 milyon karaktere kadar dize uzunlukları için iyi olur.

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 

Aşağıdaki, soldaki her bir karakteri sağdaki karşılık gelen eşi ile karşılaştırır ve herhangi bir tutarsızlık bulunursa kısa devre yapabilir ve 0 döndürür. Dize tuhaf bir uzunluk ise, orta karakter bu sonucu değiştirmeyeceği için kontrol edilmez .

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 

Nasıl çalıştığından emin değilseniz aşağıdan görebilirsiniz.

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; 

görüntü tanımını buraya girin

Bu temelde soruda tarif edilen algoritma ile aynıdır, ancak yinelemeli prosedür kodundan ziyade küme bazında yapılır.


12

REVERSE()Yöntem yani dize sadece yarısı geri, "gelişmiş":

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

Dize tek sayıda karakter varsa, garip bir şey olmasını beklemiyorum; orta karakter kontrol edilmek zorunda değildir.


@ Hvd tarafından vekil çiftleri tüm harmanlama işlemlerinde doğru şekilde kullanamayacağına dair bir açıklama geldi.

@srutzky, Tamamlayıcı Karakterler / Vekil Çiftleri'ni REVERSE()yöntemle aynı şekilde kullandığını, yalnızca geçerli veritabanının varsayılan Harmanlama işlemi sona erdiğinde düzgün çalıştıklarını belirtti _SC.


8

Kullanmadan REVERSE, derhal akla gelen, ancak yine de bir fonksiyon 1 kullanarak ; Aşağıdaki gibi bir şey inşa ediyorum.

Zaten varsa, bu bölüm var olan işlevi kaldırdı:

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

Bu işlevin kendisidir:

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

Burada, işlevi test ediyoruz:

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

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

Bu, kelimenin ilk yarısını ( REVERSEişlev kullanmadan ) kelimenin son yarısının tersiyle karşılaştırır . Bu kod, hem garip hem de uzunluktaki kelimeleri düzgün bir şekilde işler. Tüm sözcüğü ilmeklemek yerine, kelimenin LEFTilk yarısından sadece bir kısmını alırız , sonra sağ yarının tersine çevrilmiş kısmını almak için kelimenin son yarısını geçersiniz. Eğer kelime bir tuhaf uzunlukta ise, ortadaki harfi atlarız, çünkü tanımı gereği her iki "yarım" için de aynı olacaktır.


1 - fonksiyonlar çok yavaş olabilir!


6

REVERSE kullanmadan ... Özyinelemeli bir çözüm kullanmak her zaman eğlenceli;

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

Martin Smith’in set-tabanlı çözümünün sıra dışı TVF dostu bir sürümü.

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

Sadece eğlence için, işte Bellek OLTP özelliğine sahip bir SQL Server 2016 Skaler Kullanıcı Tanımlı işlev:

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

Karşılaştığınız en büyük sorun, 1'den büyük herhangi bir değeri olan LEFTveya RIGHTo konumdaki karakteri değil, birden çok karakter döndürecek olmasıdır. Bu test yöntemine uymak isteseydiniz, değiştirmenin gerçekten basit bir yolu

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

Bu her zaman sol dizenin en sağdaki karakterini ve sağ dizenin en soldaki karakterini alır.

Belki de bunu kontrol etmek için daha az dolambaçlı bir yol kullanmak olsa da SUBSTRING:

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

SUBSTRING1 indeksli olduğuna dikkat edin , bu nedenle + 1in ((LEN(String) - @n) + 1).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.