Kiểm tra xem một chuỗi có phải là một palindrom bằng T-SQL không


24

Tôi là người mới bắt đầu học T-SQL. Tôi muốn quyết định xem một chuỗi đầu vào có phải là một palindrom hay không, với output = 0 nếu nó không và output = 1 nếu có. Tôi vẫn đang tìm ra cú pháp. Tôi thậm chí không nhận được một thông báo lỗi. Tôi đang tìm kiếm các giải pháp khác nhau và một số phản hồi, để hiểu rõ hơn và hiểu biết về cách T-SQL hoạt động, để trở nên tốt hơn về nó - Tôi vẫn còn là một sinh viên.

Ý tưởng chính, như tôi thấy, là so sánh các ký tự bên trái và bên phải với nhau, để kiểm tra sự bằng nhau, sau đó tiếp tục so sánh ký tự thứ hai từ bên trái với ký tự thứ 2 - từ cuối cùng, v.v. Chúng tôi thực hiện một vòng lặp: Nếu các ký tự bằng nhau, chúng tôi tiếp tục. Nếu chúng ta đạt đến cuối, chúng ta xuất 1, nếu không, chúng ta xuất 0.

Bạn vui lòng phê bình:

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

Tôi nghĩ rằng tôi đang đi đúng hướng, nhưng tôi vẫn còn một chặng đường dài. Có ý kiến ​​gì không?


LTRIM(RTRIM(...))khoảng trắng?
WernerCD

Câu trả lời:


60

Nếu bạn đang sử dụng SQL Server, bạn có thể sử dụng hàm REVERSE () để kiểm tra không?

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

Bao gồm cả nhận xét của Martin Smith, nếu bạn đang sử dụng SQL Server 2012+, bạn có thể sử dụng hàm IIF () :

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

17

Vì có một số lượng lớn các giải pháp tôi sẽ thực hiện với phần "phê bình" trong câu hỏi của bạn. Một vài lưu ý: Tôi đã sửa một số lỗi chính tả và ghi chú nơi tôi đã làm. Nếu tôi sai về việc họ là một lỗi đánh máy đề cập đến nó trong các bình luận và tôi sẽ giải thích những gì đang xảy ra. Tôi sẽ chỉ ra một số điều mà bạn có thể đã biết, vì vậy xin đừng xúc phạm nếu tôi đã làm. Một số ý kiến ​​có vẻ kén chọn nhưng tôi không biết bạn đang ở đâu trong hành trình của mình nên phải cho rằng bạn chỉ mới bắt đầu.

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

LUÔN LUÔN bao gồm độ dài với một charhoặc varcharđịnh nghĩa. Aaron Bertrand nói về chiều sâu ở đây . Anh ấy đang nói về varcharnhưng điều tương tự cũng xảy ra char. Tôi sẽ sử dụng một varchar(255)cái này nếu bạn chỉ muốn các chuỗi tương đối ngắn hoặc có thể là một chuỗi varchar(8000)lớn hơn hoặc thậm chí varchar(max). Varchardành cho các chuỗi có độ dài thay đổi chỉ dành cho các chuỗi charcố định. Vì bạn không chắc chắn về độ dài của chuỗi được sử dụng varchar. Ngoài ra binarykhông phải vậy bin.

Tiếp theo, bạn không cần đặt tất cả các biến đó làm tham số. Khai báo chúng trong mã của bạn. Chỉ đặt một cái gì đó trong danh sách tham số nếu bạn dự định chuyển nó vào hoặc ra. (Bạn sẽ thấy cái này trông như thế nào ở cuối.) Ngoài ra, bạn có @StringLeftLpm nhưng không bao giờ sử dụng nó. Vì vậy, tôi sẽ không tuyên bố nó.

Điều tiếp theo tôi sẽ làm là định dạng lại một chút để làm cho một vài điều rõ ràng.

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

Nếu bạn nhìn vào cách tôi thực hiện việc thụt lề, bạn sẽ nhận thấy rằng tôi có điều này:

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

Đó là bởi vì các lệnh như WHILEIFchỉ ảnh hưởng đến dòng mã đầu tiên sau chúng. Bạn phải sử dụng một BEGIN .. ENDkhối nếu bạn muốn nhiều lệnh. Vì vậy, sửa chữa mà chúng tôi nhận được:

    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

Bạn sẽ nhận thấy rằng tôi chỉ thêm một BEGIN .. ENDkhối trong IF. Đó là bởi vì mặc dù IFcâu lệnh dài nhiều dòng (và thậm chí chứa nhiều lệnh), nó vẫn chỉ là một câu lệnh (bao gồm mọi thứ được thực hiện trong IFvà các ELSEphần của câu lệnh).

Tiếp theo bạn sẽ gặp lỗi sau cả hai RETURNs. Bạn có thể trả về một biến HOẶC một nghĩa đen. Bạn không thể đặt biến và trả lại cùng một lúc.

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

    RETURN @Palindrome

Bây giờ chúng tôi đi vào logic. Trước tiên, hãy để tôi chỉ ra rằng LEFTvà các RIGHTchức năng bạn đang sử dụng là tuyệt vời, nhưng chúng sẽ cung cấp cho bạn số lượng ký tự bạn chuyển từ hướng được yêu cầu. Vì vậy, hãy nói rằng bạn đã vượt qua trong từ "kiểm tra". Trên đường chuyền đầu tiên, bạn sẽ nhận được điều này (loại bỏ các biến):

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

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

Rõ ràng đó không phải là những gì bạn mong đợi. Bạn thực sự sẽ muốn sử dụng substringthay thế. Chuỗi con cho phép bạn vượt qua không chỉ điểm bắt đầu mà cả chiều dài. Vì vậy, bạn sẽ nhận được:

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

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

Tiếp theo, bạn sẽ tăng các biến bạn sử dụng trong vòng lặp của mình chỉ trong một điều kiện của câu lệnh IF. Kéo hoàn toàn biến tăng ra khỏi cấu trúc đó. Điều đó sẽ yêu cầu một BEGIN .. ENDkhối bổ sung , nhưng tôi phải loại bỏ khối kia.

        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

Bạn cần thay đổi WHILEđiều kiện của bạn để cho phép thử nghiệm cuối cùng.

        WHILE @StringLength > @n 

Và cuối cùng nhưng không kém phần quan trọng, hiện tại chúng ta không kiểm tra ký tự cuối cùng nếu có số lượng ký tự lẻ. Ví dụ với 'ana' nkhông được thử nghiệm. Điều đó tốt nhưng đối với tôi, chúng ta cần tính đến một từ đơn (nếu bạn muốn nó được tính là tích cực). Vì vậy, chúng ta có thể làm điều đó bằng cách đặt giá trị lên phía trước.

Và bây giờ cuối cùng chúng ta cũng có:

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

Một bình luận cuối cùng. Tôi là một fan hâm mộ lớn của định dạng nói chung. Nó thực sự có thể giúp bạn xem cách mã của bạn hoạt động và giúp chỉ ra những sai lầm có thể xảy ra.

Chỉnh sửa

Như Sphinxxx đã đề cập, chúng tôi vẫn có một lỗ hổng trong logic của chúng tôi. Khi chúng tôi nhấn ELSEvà đặt @Palindromethành 0, không có điểm nào để tiếp tục. Trong thực tế tại thời điểm đó chúng ta có thể chỉ RETURN.

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

Cho rằng hiện tại chúng tôi chỉ đang sử dụng @Palindromecho "vẫn có thể đây là một bảng màu" thực sự không có lý do gì để có nó. Chúng ta có thể loại bỏ biến và chuyển logic của chúng ta thành ngắn mạch khi thất bại ( RETURN 0và) và RETURN 1(một phản ứng tích cực) chỉ khi nó thực hiện được tất cả các cách trong vòng lặp. Bạn sẽ nhận thấy điều này thực sự đơn giản hóa phần nào logic của chúng tôi.

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

Bạn cũng có thể sử dụng một cách tiếp cận bảng số.

Nếu bạn chưa có bảng số phụ, bạn có thể tạo bảng như sau. Điều này được phổ biến với một triệu hàng và do đó sẽ tốt cho độ dài chuỗi lên tới 2 triệu ký tự.

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 

Dưới đây so sánh mỗi ký tự bên trái với đối tác tương ứng của nó ở bên phải và nếu tìm thấy bất kỳ sự khác biệt nào có thể bị đoản mạch và trả về 0. Nếu chuỗi có độ dài lẻ thì ký tự giữa không được kiểm tra vì điều này sẽ không làm thay đổi kết quả .

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 

Nếu bạn không chắc nó hoạt động như thế nào bạn có thể nhìn thấy từ bên dưới

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; 

nhập mô tả hình ảnh ở đây

Về cơ bản, đây là thuật toán giống như được mô tả trong câu hỏi, nhưng được thực hiện theo cách dựa trên tập hợp thay vì mã thủ tục lặp.


12

Các REVERSE()phương pháp "cải thiện", tức là đảo ngược chỉ là một nửa của chuỗi:

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

Tôi không mong đợi bất cứ điều gì kỳ lạ xảy ra nếu chuỗi có số lượng ký tự lẻ; ký tự giữa không cần phải kiểm tra.


Một nhận xét đã được đưa ra bởi @hvd rằng điều này có thể không xử lý các cặp thay thế chính xác trong tất cả các đối chiếu.

@srutzky nhận xét rằng nó xử lý các cặp ký tự bổ sung / cặp thay thế theo cách tương tự như REVERSE()phương thức, theo đó chúng chỉ hoạt động chính xác khi Collation mặc định của cơ sở dữ liệu hiện tại kết thúc _SC.


8

Không sử dụng REVERSE, đó là những gì bạn nghĩ ngay lập tức, nhưng vẫn sử dụng chức năng 1 ; Tôi sẽ xây dựng một cái gì đó như sau.

Phần này chỉ đơn giản là loại bỏ chức năng hiện có, nếu nó đã tồn tại:

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

Đây là chức năng của chính nó:

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

Ở đây, chúng tôi kiểm tra chức năng:

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

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

Điều này so sánh nửa đầu của từ với đảo ngược của nửa cuối của từ (không sử dụng REVERSE chức năng). Mã này xử lý đúng cả hai từ lẻ và chẵn. Thay vì lặp qua toàn bộ từ, chúng ta chỉ cần lấy LEFTnửa đầu của từ, sau đó lặp qua nửa cuối của từ để lấy phần đảo ngược của nửa bên phải. Nếu từ có độ dài lẻ, chúng ta bỏ qua chữ cái giữa, vì theo định nghĩa, nó sẽ giống nhau cho cả hai "nửa".


1 - chức năng có thể rất chậm!


6

Không sử dụng REVERSE ... Thật thú vị khi sử dụng giải pháp đệ quy;) (Tôi đã khai thác trong SQL Server 2012, các phiên bản trước có thể có giới hạn về đệ quy)

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

Đây là phiên bản thân thiện với TVF của giải pháp dựa trên bộ của Martin Smith , được trang trí thêm với một số cải tiến không cần thiết:

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

Để giải trí, đây là chức năng Xác định người dùng vô hướng của SQL Server 2016 với tính năng OLTP trong bộ nhớ:

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

Một vấn đề lớn mà bạn sẽ gặp phải là với bất kỳ giá trị nào lớn hơn 1 LEFThoặc RIGHTsẽ trả về nhiều ký tự, không phải ký tự ở vị trí đó. Nếu bạn muốn thực hiện theo phương pháp thử nghiệm này, một cách thực sự đơn giản để sửa đổi nó sẽ là

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

Điều này sẽ luôn lấy ký tự ngoài cùng bên phải của chuỗi bên trái và ký tự ngoài cùng bên trái của chuỗi bên phải.

Tuy nhiên, có lẽ cách ít vòng hơn để kiểm tra điều này sẽ là sử dụng SUBSTRING:

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

Lưu ý rằng SUBSTRINGlà 1 chỉ mục, do đó, + 1trong ((LEN(String) - @n) + 1).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.