web-dev-qa-db-ja.com

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

私はT-SQLの初心者です。入力文字列が回文かどうかを判断したいのですが、そうでない場合は出力= 0、そうであれば出力= 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

私は正しい方向に進んでいると思いますが、まだ長い道のりです。何か案は?

24
MSIS

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;
60
Shaneis

かなりの数の解決策があるので、私はあなたの質問の「批評」の部分に行きます。いくつかのメモ:タイプミスを修正し、どこに修正したかを書き留めました。それらがタイプミスであることについて私が間違っている場合は、コメントでそれを言及し、何が起こっているのかを説明します。既にご存じかもしれないことをいくつか指摘しておきますので、気にせずに怒らないでください。いくつかのコメントはうるさいように見えるかもしれませんが、私はあなたがあなたの旅のどこにいるのかわからないので、あなたがあなたが始まったばかりだと仮定しなければなりません。

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

[〜#〜]常に[〜#〜]は、charまたはvarcharの定義に長さを含めます。 Aaron Bertrand ここで詳しく説明します 。彼は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
_

これは、WHILEIFのようなコマンドは、それらの後のコードの最初の行にのみ影響を与えるためです。複数のコマンドが必要な場合は、_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
_

IFに_BEGIN .. END_ブロックを追加しただけであることがわかります。これは、IFステートメントが複数行であっても(複数のコマンドが含まれていても)単一のステートメントであるためです(IFおよびELSE部分で実行されたすべてをカバーしています)ステートメントの)。

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

_                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ステートメントの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文字のWordを説明する必要があります(肯定的なものとしてカウントする場合)。したがって、前もって値を設定することでそれを行うことができます。

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

_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_のみを使用していることを考えると、実際にそれを使用しても意味がありません。変数を取り除き、失敗した場合にロジックをshort circuitに切り替えます(_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
_
17
Kenneth Fisher

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; 

enter image description here

これは基本的に質問で説明したのと同じアルゴリズムですが、反復的な手続き型コードではなく、セットベースの方法で行われます。

15
Martin Smith

REVERSEを使用せずに、すぐに頭に浮かぶが、それでもfunctionを使用する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';

これは、Wordの前半と後半の逆を比較します(REVERSE関数を使用しない場合)。このコードは、奇数長と偶数長の両方のワードを適切に処理します。 Word全体をループする代わりに、Wordの前半のLEFTを取得し、Wordの後半をループして右半分の逆の部分を取得します。 Wordが奇数の長さの場合、中間の文字はスキップします。これは、定義上、両方の「半分」で同じになるためです。


1-関数が非常に遅くなる可能性があります!

8
Max Vernon

これはインライン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)
;
6
Andriy M

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
Kevin Suchlicki

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

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
5
wBob

遭遇する主な問題は、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 in ((LEN(String) - @n) + 1)

4
Andy