ทดสอบว่าสตริงเป็น palindrome โดยใช้ T-SQL


24

ฉันเป็นผู้เริ่มต้นใน T-SQL ฉันต้องการตัดสินใจว่าสตริงอินพุตเป็น palindrome โดยมีเอาต์พุต = 0 หากไม่ใช่และ output = 1 หากเป็น ฉันยังคงหาไวยากรณ์ ฉันไม่ได้รับข้อความแสดงข้อผิดพลาด ฉันกำลังมองหาวิธีแก้ไขปัญหาที่แตกต่างกันและข้อเสนอแนะบางอย่างเพื่อให้ได้ความเข้าใจและความรู้ที่ดีขึ้นเกี่ยวกับวิธีการทำงานของ T-SQL เพื่อให้ทำงานได้ดีขึ้น - ฉันยังเป็นนักเรียนอยู่

ความคิดหลักที่ฉันเห็นคือการเปรียบเทียบอักขระซ้าย - ขวา - ส่วนใหญ่กับแต่ละอื่น ๆ เพื่อตรวจสอบความเท่าเทียมกันจากนั้นไปเปรียบเทียบอักขระที่สองจากด้านซ้ายกับอักขระ 2 จากซ้ายและสุดท้าย เราวนซ้ำ: ถ้าตัวละครมีค่าเท่ากันเราจะดำเนินการต่อ ถ้าเราถึงจุดสิ้นสุดเราจะเอาท์พุท 1 ถ้าไม่เราก็เอาท์พุท 0

คุณช่วยวิจารณ์ได้ไหม:

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

ฉันคิดว่าฉันกำลังอยู่ในเส้นทางที่ถูกต้อง แต่ฉันก็ยังห่างไกล ความคิดใด ๆ


LTRIM(RTRIM(...))ช่องว่าง?
WernerCD

คำตอบ:


60

หากคุณใช้ SQL Server คุณสามารถใช้ฟังก์ชันREVERSE ()เพื่อตรวจสอบได้หรือไม่

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

รวมถึงความคิดเห็นของ Martin Smith หากคุณอยู่ใน SQL Server 2012+ คุณสามารถใช้ฟังก์ชันIIF () :

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

17

เนื่องจากมีโซลูชั่นจำนวนพอสมควรฉันจะไปกับ "บทวิจารณ์" ส่วนหนึ่งของคำถามของคุณ คู่ของบันทึกย่อ: ฉันได้แก้ไขความผิดพลาดบางอย่างและตั้งข้อสังเกตว่าฉันอยู่ที่ไหน ถ้าฉันผิดเกี่ยวกับพวกเขาเป็นตัวพิมพ์ผิดพูดถึงในความคิดเห็นและฉันจะอธิบายสิ่งที่เกิดขึ้น ฉันจะชี้ให้เห็นสิ่งต่าง ๆ ที่คุณอาจรู้อยู่แล้วดังนั้นโปรดอย่าทำผิดถ้าฉันทำ ความคิดเห็นบางอย่างอาจดูพิถีพิถัน แต่ฉันไม่รู้ว่าคุณอยู่ที่ไหนในการเดินทางของคุณดังนั้นต้องคิดว่าคุณเพิ่งจะเริ่ม

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

เสมอรวมถึงความยาวด้วยcharหรือvarcharความหมาย แอรอนเบอร์ทรานด์พูดเกี่ยวกับในในเชิงลึกที่นี่ เขาพูดถึงแต่เดียวกันจะไปสำหรับvarchar charฉันต้องการใช้varchar(255)สำหรับการนี้หากคุณต้องการเพียงสตริงค่อนข้างสั้นหรืออาจจะเป็นคนที่มีขนาดใหญ่หรือแม้กระทั่ง varchar(8000) สำหรับสตริงที่มีความยาวผันแปรได้มีไว้สำหรับสตริงคงที่เท่านั้น เนื่องจากคุณไม่แน่ใจว่าความยาวของสตริงที่ถูกส่งในการใช้งาน นอกจากนี้ก็ไม่ได้varchar(max)Varcharcharvarcharbinarybin

ถัดไปคุณไม่จำเป็นต้องใส่ตัวแปรเหล่านั้นทั้งหมดเป็นพารามิเตอร์ ประกาศในโค้ดของคุณ ใส่บางอย่างในรายการพารามิเตอร์เฉพาะถ้าคุณวางแผนที่จะผ่านเข้าหรือออก (คุณจะเห็นว่าสิ่งนี้มีลักษณะอย่างไรในตอนท้าย) นอกจากนี้คุณยังมี @StringLeftLength แต่ไม่เคยใช้ ดังนั้นฉันจะไม่ประกาศ

สิ่งต่อไปที่ฉันจะทำคือจัดรูปแบบอีกเล็กน้อยเพื่อทำให้บางสิ่งชัดเจน

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

หากคุณดูวิธีที่ฉันทำเยื้องคุณจะสังเกตเห็นว่าฉันมีสิ่งนี้:

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

นั่นเป็นเพราะคำสั่งที่ชอบWHILEและIFมีผลกับบรรทัดแรกของรหัสหลังจากพวกเขา คุณต้องใช้BEGIN .. ENDบล็อกถ้าคุณต้องการหลายคำสั่ง ดังนั้นการแก้ไขที่เราได้รับ:

    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

คุณจะเห็นว่าฉันเพียงเพิ่มบล็อกในBEGIN .. END IFนั่นเป็นเพราะแม้ว่าIFคำสั่งจะมีความยาวหลายบรรทัด (และแม้กระทั่งมีหลายคำสั่ง) แต่ก็ยังคงเป็นคำสั่งเดียว (ครอบคลุมทุกอย่างที่ดำเนินการในIFและELSEส่วนของคำสั่ง)

RETURNsถัดไปคุณจะได้รับข้อผิดพลาดหลังจากทั้งสองของคุณ คุณสามารถคืนค่าตัวแปรหรือตัวอักษร คุณไม่สามารถตั้งค่าตัวแปรและส่งคืนพร้อมกันได้

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

    RETURN @Palindrome

ตอนนี้เราเข้าสู่ตรรกะ ก่อนอื่นให้ฉันชี้ให้เห็นว่าLEFTและRIGHTฟังก์ชั่นที่คุณใช้นั้นยอดเยี่ยม แต่พวกเขาจะให้จำนวนตัวอักษรที่คุณส่งผ่านจากทิศทางที่ร้องขอ สมมติว่าคุณผ่านคำว่า "ทดสอบ" ในรอบแรกคุณจะได้รับสิ่งนี้ (ลบตัวแปร):

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

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

เห็นได้ชัดว่านั่นไม่ใช่สิ่งที่คุณคาดหวัง คุณต้องการใช้substringแทนจริงๆ ซับสตริงช่วยให้คุณผ่านไม่เพียง แต่จุดเริ่มต้น แต่ความยาว ดังนั้นคุณจะได้รับ:

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

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

ถัดไปคุณกำลังเพิ่มตัวแปรที่คุณใช้ในการวนซ้ำในเงื่อนไขเดียวของคำสั่ง IF ดึงตัวแปรที่เพิ่มขึ้นจากโครงสร้างนั้นทั้งหมด นั่นจะต้องใช้BEGIN .. ENDบล็อคเพิ่มเติมแต่ฉันจะลบอันอื่นออก

        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

คุณต้องเปลี่ยนWHILEเงื่อนไขเพื่อให้สามารถทำการทดสอบครั้งสุดท้ายได้

        WHILE @StringLength > @n 

และสุดท้าย แต่ไม่ท้ายสุดตอนนี้เราไม่ได้ทดสอบตัวละครสุดท้ายหากมีจำนวนตัวละครแปลก ๆ ตัวอย่างเช่น 'ana' nไม่ได้ทดสอบ ไม่เป็นไร แต่ฉันคิดว่าเราจำเป็นต้องคำนึงถึงคำตัวอักษรเดียว (ถ้าคุณต้องการให้นับว่าเป็นบวกนั่นคือ) ดังนั้นเราสามารถทำได้โดยการตั้งค่าล่วงหน้า

และในที่สุดเราก็มี:

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

หนึ่งความคิดเห็นล่าสุด ฉันเป็นแฟนตัวยงของการจัดรูปแบบโดยทั่วไป มันสามารถช่วยให้คุณเห็นว่ารหัสของคุณทำงานอย่างไรและช่วยชี้ให้เห็นข้อผิดพลาดที่อาจเกิดขึ้นได้

แก้ไข

ดังที่ Sphinxxx พูดถึงเรายังมีข้อบกพร่องในตรรกะของเรา เมื่อเรากดปุ่มELSEและตั้งค่า@Palindromeเป็น 0 จะไม่มีจุดใดในการดำเนินการต่อ RETURNในความเป็นจริงที่จุดที่เราสามารถทำได้เพียงแค่

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

เนื่องจากขณะนี้เราใช้@Palindromeสำหรับ "ยังคงเป็นไปได้นี่คือ palindrome" ไม่มีจุดที่จะมีได้จริง ๆ เราสามารถกำจัดตัวแปรและเปลี่ยนตรรกะของเราเป็นไฟฟ้าลัดวงจรเมื่อความล้มเหลว ( RETURN 0) และRETURN 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

คุณสามารถใช้วิธีตารางตัวเลขได้เช่นกัน

หากคุณยังไม่มีตารางตัวเลขเสริมคุณสามารถสร้างตารางดังต่อไปนี้ นี่จะมีประชากรเป็นล้านแถวและจะดีสำหรับความยาวสตริงสูงสุด 2 ล้านตัวอักษร

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 

ด้านล่างจะเปรียบเทียบอักขระแต่ละตัวทางด้านซ้ายกับคู่ค้าที่เกี่ยวข้องทางด้านขวาและหากพบความคลาดเคลื่อนใด ๆ สามารถทำให้เกิดการลัดวงจรและส่งคืน 0 ถ้าสตริงมีความยาวคี่อักขระกลางจะไม่ถูกตรวจสอบเนื่องจากจะไม่เปลี่ยนแปลงผลลัพธ์ .

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 

หากคุณไม่แน่ใจว่ามันทำงานอย่างไรคุณสามารถดูได้จากด้านล่าง

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; 

ป้อนคำอธิบายรูปภาพที่นี่

นี่เป็นอัลกอริธึมเดียวกับที่อธิบายไว้ในคำถาม แต่ทำในลักษณะที่เป็นพื้นฐานมากกว่ารหัสขั้นตอนซ้ำ


12

REVERSE()วิธีการ "ดีขึ้น" คือการย้อนกลับเพียงครึ่งหนึ่งของสตริง:

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

ฉันไม่คาดหวังว่าจะมีอะไรแปลก ๆ เกิดขึ้นถ้าสตริงมีจำนวนอักขระแปลก ๆ ไม่ต้องตรวจสอบอักขระกลาง


มีการกล่าวถึง @hvd ซึ่งอาจไม่สามารถจัดการคู่ตัวแทนได้อย่างถูกต้องในการเปรียบเทียบทั้งหมด

แสดงความคิดเห็น @srutzky ที่จะจัดการกับตัวละครเสริม / คู่ตัวแทนในลักษณะเช่นเดียวกับวิธีการในการที่พวกเขาได้ทำงานอย่างถูกต้องเมื่อฐานข้อมูลปัจจุบันเปรียบเทียบค่าเริ่มต้นในปลายREVERSE()_SC


8

โดยไม่ใช้REVERSEซึ่งเป็นสิ่งที่อยู่ในใจในทันที แต่ยังคงใช้ฟังก์ชั่น1 ; ฉันจะสร้างสิ่งต่อไปนี้

ส่วนนี้จะลบฟังก์ชันที่มีอยู่หากมีอยู่แล้ว:

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

นี่คือฟังก์ชั่นของตัวเอง:

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

ที่นี่เราทดสอบฟังก์ชั่น:

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

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

นี่เป็นการเปรียบเทียบช่วงครึ่งแรกของคำกับการย้อนกลับของครึ่งสุดท้ายของคำ (โดยไม่ใช้REVERSEฟังก์ชัน) รหัสนี้จัดการอย่างถูกต้องทั้งคำแปลกและความยาว แทนที่จะวนซ้ำทั้งคำเราเพียงแค่รับLEFTครึ่งแรกของคำจากนั้นวนครึ่งคำสุดท้ายเพื่อรับส่วนที่ตรงกันข้ามของครึ่งขวา หากคำนี้มีความยาวคี่เราจะข้ามตัวอักษรกลางเนื่องจากตามคำจำกัดความมันจะเหมือนกันสำหรับ "ครึ่งหนึ่ง"


1 - ฟังก์ชั่นอาจช้ามาก!


6

โดยไม่ต้องใช้ REVERSE ... มันสนุกเสมอที่จะใช้โซลูชันแบบเรียกซ้ำ;) (ฉันเคยใช้ SQL Server 2012 รุ่นก่อนหน้านี้อาจมีข้อ จำกัด ในการเรียกซ้ำ)

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

นี่เป็นรุ่นที่เหมาะสำหรับ TVF ที่เป็นมิตรกับโซลูชั่นการตั้งค่าของMartin Smithซึ่งได้รับการตกแต่งเพิ่มเติมด้วยการปรับปรุงเพิ่มเติมสองสามประการ:

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

เพื่อความสนุกนี่คือฟังก์ชั่นที่ผู้ใช้กำหนดเองของสเกลาร์ SQL Server 2016 ด้วยคุณสมบัติ In-Memory OLTP:

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

ปัญหาสำคัญที่คุณจะพบคือมีค่ามากกว่า 1 LEFTหรือRIGHTจะส่งคืนอักขระหลายตัวไม่ใช่อักขระที่ตำแหน่งนั้น หากคุณต้องการใช้วิธีการทดสอบต่อไปนี้เป็นวิธีที่ง่ายมากในการแก้ไข

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

สิ่งนี้จะคว้าตัวอักขระขวาสุดของสตริงซ้ายและอักขระซ้ายสุดของสตริงขวา

อย่างไรก็ตามอาจใช้วิธีวงเวียนน้อยกว่าในการตรวจสอบสิ่งนี้SUBSTRING:

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

หมายเหตุที่SUBSTRING1 การจัดทำดัชนีดังนั้นใน+ 1((LEN(String) - @n) + 1)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.