Проверьте, является ли строка палиндромом, используя T-SQL

24

Я новичок в T-SQL. Я хочу решить, является ли входная строка палиндромом, с output = 0, если это не так, и output = 1, если это так. Я все еще выясняю синтаксис. Я даже не получаю сообщение об ошибке. Я ищу различные решения и отзывы, чтобы лучше понять и понять, как работает T-SQL, чтобы стать лучше - я еще студент.

Ключевая идея, на мой взгляд, состоит в том, чтобы сравнивать левый и правый символы друг с другом, проверять равенство, затем сравнивать второй символ слева со вторым из последнего и т. Д. Делаем цикл: если символы равны друг другу, мы продолжаем. Если мы достигли конца, мы выводим 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

Я думаю, что я на правильном пути, но мне еще далеко. Любые идеи?

MSIS
источник
LTRIM(RTRIM(...))пробельные?
WernerCD

Ответы:

60

Если вы используете SQL Server, вы можете использовать функцию REVERSE () для проверки?

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

Включая комментарий Мартина Смита, если вы используете SQL Server 2012+, вы можете использовать функцию IIF () :

SELECT IIF(@string = REVERSE(@String),1,0) AS Palindrome;
Shaneis
источник
17

Поскольку существует немало решений, я собираюсь остановиться на «критической» части вашего вопроса. Несколько замечаний: я исправил некоторые опечатки и отметил, где я сделал. Если я ошибаюсь из-за того, что они опечатки, упомяните об этом в комментариях, и я объясню, что происходит. Я собираюсь указать на несколько вещей, которые вы, возможно, уже знаете, поэтому, пожалуйста, не обижайтесь, если я это сделал. Некоторые комментарии могут показаться придирчивыми, но я не знаю, где вы находитесь в вашем путешествии, поэтому я должен предположить, что вы только начинаете.

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

ВСЕГДА указывайте длину с определением charили varchar. Аарон Бертран подробно рассказывает здесь . Он говорит о, varcharно то же самое касается char. Я бы использовал varchar(255)для этого, если вы хотите только относительно короткие строки или, возможно, varchar(8000)для более крупных или даже varchar(max). Varcharдля строк переменной длины char- только для фиксированных. Поскольку вы не уверены в длине строки, передаваемой в использование varchar. И это binaryне так bin.

Далее вам не нужно помещать все эти переменные в качестве параметров. Объявите их в своем коде. Поместите что-либо в список параметров только в том случае, если вы планируете передавать его или нет. (Вы увидите, как это выглядит в конце.) Также у вас есть @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 

И последнее по порядку, но не по значению: как сейчас, мы не проверяем последний символ, если есть нечетное количество символов. Например, с «аной» 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для «это все еще возможно, это палиндром», на самом деле нет смысла иметь его. Мы можем избавиться от переменной и переключить нашу логику на короткое замыкание при сбое ( 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.

ypercubeᵀᴹ
источник
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-версия версия решения Мартина Смита на основе множеств , дополнительно украшенная парой лишних улучшений:

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
wBob
источник
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).

Энди
источник