Перевірте, чи є рядок паліндром за допомогою T-SQL


24

Я новачок у T-SQL. Я хочу вирішити, чи є вхідний рядок паліндром, з виходом = 0, якщо він не є, а вихід = 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

Я думаю, що я на вірному шляху, але мені ще далеко. Будь-які ідеї?


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;

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 

І останнє, але не в останню чергу, те, як він стоїть зараз, ми не перевіряємо останнього символу, якщо є непарна кількість символів. Наприклад, "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і встановимо @Palindrome0, немає сенсу продовжувати. Насправді тоді ми могли просто RETURN.

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

Зважаючи на те, що ми зараз використовуємо лише @Palindromeдля "все ще можливо, це паліндром", насправді немає сенсу в ньому. Ми можемо позбутися змінної і переключити свою логіку на коротке замикання на відмову (the 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 для набору рішень Мартіна Сміта , додатково прикрашена парою зайвих вдосконалень:

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

Ось тільки для задоволення, ось функція Slar Server 2016 Scalar, визначена користувачем, із функцією OLTP In-Memory:

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)

Зверніть увагу, що SUBSTRINGце 1-індексація, звідси і + 1в ((LEN(String) - @n) + 1).

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.