challenge 文字列の反転

与えられた文字列sを前後逆転したものを返す関数reverse_stringを作成してください。 ただし、sはShift-JISでエンコードされている文字列だと仮定して構いません。

サンプル入出力

>>> print reverse_string("Hello")
olleH
>>> print reverse_string("こんにちは")
はちにんこ
>>> print reverse_string("濁点(だくてん)")
)んてくだ(点濁

Posted feedbacks - Flatten

Nested Hidden
普通に。
1
2
3
4
5
6
7
8
9
# coding: shift_jis

def reverse_string(s, coding="shift_jis"):
    return "".join(reversed(s.decode(coding))).encode(coding)

if __name__ == '__main__':
    print reverse_string("Hello")
    print reverse_string("こんにちは")
    print reverse_string("濁点(だくてん)")

もうちょっとシンプルにしました。
1
2
3
4
5
6
7
8
9
# coding: shift_jis

def reverse_string(s, coding="shift_jis"):
	return unicode(s, coding)[::-1].encode(coding)

if __name__ == '__main__':
    print reverse_string("Hello")
    print reverse_string("こんにちは")
    print reverse_string("濁点(だくてん)")

こんなのでいいのかどうかよく分かりませんが、
とりあえず期待通りの結果が返ってきます。
1
2
3
4
5
(setf (symbol-function 'reverse-string) #'reverse)

(print (reverse-string "Hello"))
(print (reverse-string "こんにちは"))
(print (reverse-string "濁点(だくてん)"))

Squeak Smalltalk では、順序付きコレクションの反転と同じように、メッセージ「reversed」の送信が使えます。
1
2
3
4
5
'Hello' reversed   "=> 'olleH' "

'こんにちは' reversed   "=> 'はちにんこ' "

'濁点(だくてん)' reversed   "=> ')んてくだ(点濁' "

Haskellでは文字列は文字のリストにすぎませんので
「文字列が構築されているなら」、reverse で反転できます。
1
2
reverseString :: String -> String
reverseString = reverse


	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <string.h>

int main()
{
    int  i, j, len;
    unsigned char in[BUFSIZ];
    unsigned char out[BUFSIZ];

    scanf("%s", in);
    len = strlen(in);

    j = len - 1;
    i = 0;

    while (i < len) {
        /* Shift-JISか判定 */
        if ((in[i] >= 0x80 && in[i] <= 0x9F) ||
            (in[i] >= 0xE0 && in[i] <= 0xFF)) {
            out[j-1] = in[i++];
            out[j] = in[i++];
            j--;
        }
        else {
            out[j] = in[i++];
        }
        j--;
    }
    out[len] = '\0';

    printf("%s\n", out);
}

scalaではStringがRichStringに暗黙に変換されるので、reverseが使えます。

1
def reverse_string(s:String) = s.reverse.mkString("")

 ひねり無し。
1
function reverse_string(s){ return s.split('').reverse().join('') }

SML#のLMLMLライブラリを使って。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
fun reverse_string str =
let
  open ShiftJISCodec
  val s = String.fromString str
in
  String.toString (String.implode (rev (String.explode str)))
end;

println (reverse_string "Hello");
println (reverse_string "こんにちは");
println (reverse_string "濁点(だくてん)")


	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using System;
class Program {
    static string reverse_string(string s) {
        char []a = s.ToCharArray();
        Array.Reverse(a);
        return new string(a);
    }
    static void Main(string[] args) {
        Console.WriteLine(reverse_string("Hello"));
        Console.WriteLine(reverse_string("こんにちは"));
        Console.WriteLine(reverse_string("濁点(だくてん)"));
    }
}

間違えた。正しくはこう。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
--- 2961.orig   2007-09-10 22:23:00.000000000 +0900
+++ 2961.sml    2007-09-10 22:23:22.000000000 +0900
@@ -3,7 +3,7 @@
   open ShiftJISCodec
   val s = String.fromString str
 in
-  String.toString (String.implode (rev (String.explode str)))
+  String.toString (String.implode (rev (String.explode s)))
 end;

 println (reverse_string "Hello");

WindowsAPIのCharNextExを使用。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <windows.h>
#include <iostream>
#include <string>

std::string reverse_string(const char* s, WORD codepage = 932)
{
    std::string ret;

    while (*s)
    {
        const char* p = ::CharNextExA(codepage, s, 0);

        ret.insert(0, s, p - s);

        s = p;
    }

    return ret;
}

int main()
{
    std::cout << reverse_string("Hello") << std::endl;
    std::cout << reverse_string("こんにちは") << std::endl;
    std::cout << reverse_string("濁点(だくてん)") << std::endl;
}

補足です

ghcではソースコードはUTF-8で書かれていることを前提とします.
したがって,日本語文字列リテラルはUTF-8で書かなければなりません.
コメントについても,日本語でのコメントを書きたければ,UTF-8で
書いておく必要があります.

さらにghcでは文字は内部的にはUCS4で表現されているといってよいのですが,
エンコーディングを変換する機構が標準では提供されていません.
外部から文字列データを読み込み,それを逆転し,外部へ出力するためには
エンコーディングを変換する機構を自前で用意するか,それ用のライブラリ
モジュールを使う必要があります.

ひねり無し2。
1
2
3
def reverse_string(s)
  s.split(//).reverse.join
end

文字列はShiftJISではありません(ちなみにソースコードはShiftJISで書きました)。その代わりと言ってはなんですが、サロゲートペアのサンプルをつけてあります。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
public class Sample {
    public static String reverseString(String arg) {
        return new StringBuffer(arg).reverse().toString();
    }
    public static void main(String[] args) {
        System.out.println(reverseString("Hello"));
        System.out.println(reverseString("こんにちは"));
        System.out.println(reverseString("濁点(だくてん)"));
        System.out.println(reverseString("¥uD842¥uDFB7野家"));
    }
}

太古の昔(40年位昔?)は、文字列の代りにシンボルを
使っていたとどっかで目にした記憶があったので、
そういう感じを想像して作ってみました。

(reverse-string '|濁点(だくてん)|)
=> |)んてくだ(点濁|
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
(defun reverse-string (x)
  (prog (s rev)
        (setq s (explode x))
    l	(cond ((null s) (return (implode rev))))
        (setq rev (cons (car s) rev))
	(setq s (cdr s))
	(go l)))

(defun implode (lst)
  (intern (coerce lst 'string)))

(defun explode (sym)
  (coerce (string sym) 'list))


	
1
2
3
Function reverse_string(ByVal s As String) As String
    Return New String(s.ToCharArray.Reverse.ToArray)
End Function

マルチバイト文字列関数に丁度いい関数がなかったので
一度UTF16に変換して逆順にして元に戻してみた。
逆順にするとエンディアンが変わるってのがミソ。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
function mb_strrev($str,$encoding)
{
	return mb_convert_encoding(strrev(mb_convert_encoding($str,"UTF-16BE",$encoding)),$encoding,"UTF-16LE");
}

echo mb_strrev("Hello","SJIS"),"\n";
echo mb_strrev("こんにちは","SJIS"),"\n";
echo mb_strrev("濁点(だくてん)","SJIS"),"\n";
?>

HackageDBにある utf8-stringというライブラリを使う。
ソースコードはUTF8,U.putStrLnは文字列をUTF8バイト列に変換して出力する
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
module Main where
import qualified System.IO.UTF8 as U

reverseString :: String -> String
reverseString = reverse

main :: IO ()
main = do { U.putStrLn $ reverseString "Hello"
          ; U.putStrLn $ reverseString "こんにちは"
          ; U.putStrLn $ reverseString "濁点(だくてん)"
	  }

{-
*Main> :main
Loading package utf8-string-0.1 ... linking ... done.
olleH
はちにんこ
)んてくだ(点濁
-}

SRFI-13 に string-reverse があります。
1
2
3
4
5
6
7
8
gosh> (use srfi-13)
#<undef>
gosh> (string-reverse "Hello")
"olleH"
gosh> (string-reverse "こんにちは")
"はちにんこ"
gosh> (string-reverse "濁点(だくてん)")
")んてくだ(点濁"

Wide character in printっていわれる
1
2
3
4
use Encode;
sub reverse_string {
  return join '', reverse split //, decode('shiftjis',$_[0]);
}

一応、補足しておくとJavaではStringは内部的にUTF-16で処理される。したがって1バイト/2バイトの区別はなくBMPの範囲内では単純に逆順にすれば良い事になる。しかし、UTF-16は可変長の符号のため、サロゲートペア(BMPの範囲外の文字)が含まれる場合、単純に逆転しては文字(コードポイント)が不正になる。StringBuffer#reverseはコードポイント単位の逆転を実現している。

#しかし、可変長になった時点でUTF-16の存在価値はほとんどなくなってるような……

Emacs Lispにはそれ用の関数はないです。
1
2
(defun reverse-string (str)
  (concat (nreverse (append str nil))))

シェルスクリプト的発想で外から持って来い!
"rev" があります。

マルチバイトには対応しているのですが、
Shift-JISが読めるとは限らないので適当にnkfかませつつ。

$ ./rev.sh "$(echo "濁点(だくてん)" |nkf -s)"
)んてくだ(点濁
1
2
3
#!/bin/bash
n="$1"
echo "$n" |nkf --unix |rev

PHPって文字列処理得意そうな印象があったのですけど、ビッグエンディアンでエンコードしてひっくり返してからリトルエンディアンで戻すとはなかなかすごいウルトラCですね…。



	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <string.h>

void swap(char *str, int i, int j)
{
    char tmp = str[i];
    str[i] = str[j];
    str[j] = tmp;
}

char *reverse_string(char *str)
{
    int len, i, j;

    len = strlen(str);

    for (i = 0, j = len-1; j > i; ++i, --j) {
        swap(str, i, j);
    }

    for (i = len-1; i > 0; --i) {
        if (str[i] & 0x80) {
            swap(str, i, i-1);
            --i;
        }
    }

    return str;
}

プラットフォーム依存かもしれませんが、
Windows版Rはデフォルトの文字コードがSJISになっています。

> reverse_string("Hello")
[1] "olleH"
> reverse_string("こんにちは")
[1] "はちにんこ"
> reverse_string("濁点(だくてん)")
[1] ")んてくだ(点濁"
1
reverse_string <- function(s) paste(rev(unlist(strsplit(s, ''))), collapse='')

phpはネイティブでは日本語等のマルチバイト文字列には対応していません。
(ソースをjisやsjisで書くと問題起きるし…)
日本語等のマルチバイト文字列を扱う関数は通常の文字列関数とは別にあるの
ですが、必要最小限しか用意されていません。
逆順に並べ替えたり、文字列を配列に分割する関数もなかったので
思いつきでこんな事しましたが、実用に供するなら配列に分割する
関数を作ると思います…(^^; むやみに文字空間変換するのもアレですし…。

UTF16にしてますが入力がsjisだとサロゲートペアの問題は起きないはず…?
"はしごだか"や"たちざき"等のIBM拡張文字はエンコーディングに"SJIS"では
なく"sjis-win"を指定すれば通るデス。
UTF32にしとけよと言われれば…全くその通りです。

WIndowsのコマンドプロンプトで試してみたところ
下のように化け化けになってしまいました。
何がいけないのでしょうか?

C:\>ruby
def reverse_string(s)
  s.split(//).reverse.join
end

print reverse_string("濁点(だくてん)")
^D
・トくだ・_肉

printのかわりにpを使うとこうなりました

C:\>ruby
def reverse_string(s)
  s.split(//).reverse.join
end

p reverse_string("濁点(だくてん)")
^D
・)\361\202\304\202\255\202\276\202(_\223\367\221"

シフトJISをバイト単位でひっくり返してしまってますかね?

エンコードの判定をさぼってしまっているのでShift-JIS環境だと$KCODE='s'がないと化けてしまいますね…

これで大丈夫ですよね?
1
2
3
def reverse_string(s)
return s.reverse
end


	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <string>

bool isLeadChar(std::string::value_type c)
{
    unsigned char uc = c;
    return ((0x80u <= uc) && (uc <= 0x9fu)) || ((0xa1u <= uc) && (uc <= 0xfeu));
}

class CharReader
{
public:
    explicit CharReader(const std::string& src) : src_(src), pos_(0) {}

    std::string next()
    {
        std::string result;
        if((pos_ < src_.length()) && isLeadChar(src_[pos_]))
        {
            result = src_.substr(pos_, 2);
            pos_ += 2;
        }
        else
        {
            result = src_.substr(pos_, 1);
            ++pos_;
        }
        return result;
    }

    bool isEmpty() const
    {
        return src_.length() <= pos_;
    }

private:
    const std::string      src_;
    std::string::size_type pos_;
};

std::string reverse_string(const std::string& src)
{
    CharReader  reader(src);
    std::string result;
    while( ! reader.isEmpty())
    {
        result = reader.next() + result;
    }
    return result;
}

int main(int, char* [])
{
    std::cout << reverse_string("Hello") << std::endl;
    std::cout << reverse_string("こんにちは") << std::endl;
    std::cout << reverse_string("濁点(だくてん)") << std::endl;

    return 0;
}

Haskellの解答


	
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
bool is_sjis(unsigned char c) {
    return ((0x81 <= c) && (c <= 0x9F)) || ((0xE0 <= c) && (c <= 0xEF));
}
string reverse_string(const string &_s) {
	string s(_s.rbegin(), _s.rend());
	for (string::reverse_iterator ite = s.rbegin(); ite != s.rend(); ++ite) {
		if (is_sjis(*ite)) {
			iter_swap(ite, ite + 1);
			++ite;
		}
	}
	return s;
}
int main() {
	cout << reverse_string("Hello") << endl;
	cout << reverse_string("こんにちは") << endl;
	cout << reverse_string("濁点(だくてん)") << endl;
	return 0;
}

utf8でやるとバグらないっぽです。
1
2
3
4
5
6
7
8
use Encode;
use utf8;

sub reverse_string {
  return encode('utf8',scalar reverse $_[0]);
}

print reverse_string("こんにちは");


split が文字列を分解するのは gawk に限られると思われ。。。

gawk -f p3414.awk -v string="split string array fieldsep"
pesdleif yarra gnirts tilps

スクリプト中の変数 string への値の受け渡しはgawk コマンドの -v オプションで
1
2
3
4
5
BEGIN {
        n = split(string, a, "")
        for (i = n; i >= 1; i--) printf "%s", a[i]
        print "\n"
}

UTF-8 を想定しています。
1
2
3
string reverseString(string s){
    return s.dup.reverse;
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    call reverse_string "Hello";
    message $$return;
    call reverse_string "こんにちは";
    message $$return;
    call reverse_string "濁点(だくてん)";
    message $$return;
    endmacro;

reverse_string:
    $$result = "";
    while( $$1 != "" ) {
        $$char = unichar( unicode( $$1 ) );
        $$result = $$char + $$result;
        $$1 = rightstr( $$1, strlen( $$1 ) - strlen( $$char ) );
    }
    return $$result;

入力Shift-JISってことなんでC文字列にしてみた。
C文字列のまま処理してもいいけどそれだとまるっきりCのコードなので、
一度NSStringに変換後処理して最後にまたC文字列に戻すという…無駄ですね。
無駄でした。

こういうとこはCで書くか、
最初に全部NSStringにしちゃってプログラム中は一貫してNSStringで処理するか、
どっちかかな。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import <Foundation/Foundation.h>

const char * reverse_string(const char * cString, NSStringEncoding encoding)
{
    NSString* aString = [NSString stringWithCString:cString encoding:encoding];
    NSMutableString* reversedString = [NSMutableString string];
    int index;
    for (index = [aString length] - 1; index >= 0; index--) {
        [reversedString appendString:[aString substringWithRange:NSMakeRange(index, 1)]];
    }
    return [reversedString cStringUsingEncoding:encoding];
}

int main(int argc, const char *argv) {
    NSAutoreleasePool* pool = [NSAutoreleasePool new];
      
    const char * ss[3];
    ss[0] = [@"Hello" cStringUsingEncoding:NSShiftJISStringEncoding];
    ss[1] = [@"こんにちは" cStringUsingEncoding:NSShiftJISStringEncoding];
    ss[2] = [@"濁点(だくてん)" cStringUsingEncoding:NSShiftJISStringEncoding];

    NSLog(@"%s", reverse_string(ss[0], NSShiftJISStringEncoding));
    NSLog(@"%s", reverse_string(ss[1], NSShiftJISStringEncoding));
    NSLog(@"%s", reverse_string(ss[2], NSShiftJISStringEncoding));

    [pool release];
    return 0;
}

こっちはNSStringに反転メソッドを追加したもの。
あと、再帰にしてみた。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#import <Foundation/Foundation.h>

@interface NSString (Reverse)
- (NSString *)reversedString;
@end

@implementation NSString (Reverse)
- (NSString *)reversedString
{
    if ([self length] > 1)
    {
        return [[[self substringFromIndex:1] reversedString] stringByAppendingString:[self substringToIndex:1]];
    }
    else
    {
        return self;
    }
}
@end

int main(int argc, const char *argv) {
    NSAutoreleasePool*