T-SQLを使用して、文字列が回文であるかどうかをテストします


24

私はT-SQLの初心者です。入力文字列が回文であるかどうかを判断します。回文がそうでない場合はoutput = 0、そうでない場合はoutput = 1です。私はまだ構文を理解しています。エラーメッセージも表示されません。T-SQLがどのように機能するかについてのより良い理解と知識を獲得し、より良くなるために、さまざまなソリューションとフィードバックを探しています-私はまだ学生です。

私が見ているように、鍵となるアイデアは、左端と右端の文字を互いに比較し、等しいかどうかを確認し、次に左から2番目の文字と最後から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

常に Aと長さが含まれるcharvarcharの定義。アーロンバートランドはここで詳細に話します。彼は話している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は、ステートメントが複数行の長さ(および複数のコマンドを含む)であっても、単一のステートメントであるためです(ステートメントで実行されるすべてIFELSE部分とステートメントの一部をカバーするため)。

次に、両方のの後にエラーが発生しますRETURNs。変数またはリテラルを返すことができます。変数を設定して同時に返すことはできません。

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

    RETURN @Palindrome

今、私たちはロジックに取り組んでいます。最初に、使用している関数LEFTRIGHT関数は優れているが、要求された方向から渡す文字数を提供することを指摘させてください。「テスト」という言葉に合格したとしましょう。最初のパスで、これを取得します(変数を削除します):

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ステートメントの1つの条件でのみ、ループで使用する変数をインクリメントします。その構造から変数の増分を完全に引き出します。それには追加の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ではテストされません。それは問題ありませんが、1文字の単語を説明する必要があります(肯定的なものとしてカウントしたい場合)。そのため、値を事前に設定することでそれを行うことができます。

そして今、私たちは最終的に持っています:

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が述べたように、私たちのロジックにはまだ欠陥があります。ヒットして0にELSE設定@Palindromeすると、続行する意味がなくなります。実際には、その時点で私たちはちょうどできましたRETURN

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

現在@Palindrome、「これは回文である可能性がある」ためだけに使用していることを考えると、実際にそれを使用する意味はありません。変数を取り除いて、失敗時()および(正の応答)でループを完全に通過した場合のみ、ロジックを短絡に切り替えることができます。これにより、ロジックが多少簡略化されていることに気付くでしょう。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

Numbersテーブルアプローチを使用することもできます。

補助番号テーブルがまだない場合は、次のように作成できます。これには100万行が入力されるため、最大200万文字の文字列の長さに適しています。

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

これは、Martin Smithのセットベースのソリューションのインライン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

楽しみのために、インメモリOLTP機能を備えたSQL Server 2016 Scalarユーザー定義関数を次に示します。

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であるため、+ 1inであることに注意してください((LEN(String) - @n) + 1)

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.