challenge /*コメント*/を取り除く

与えられた文字列から「/*」と「*/」で挟まれた部分を取り除くコードを書いてください。

なお、「/*」と入力末尾で挟まれた部分も取り除いてください。 つまり、入力が「AAA/*BBB」なら出力は「AAA」です。 また、コメントは入れ子になりません。入力が「AAA/*BBB/*CCC*/DDD*/EEE」のとき、ひとつめの「*/」でコメントが終わるので出力は「AAADDD*/EEE」になります。 「//」や「**」が混ざる場合の挙動は失敗しやすいので要注意です。

Pythonでの実行例は下のようになります:

>>> remove_comment('AAA')
'AAA'
>>> remove_comment('AAA/*BBB*/')
'AAA'
>>> remove_comment('AAA/*BBB')
'AAA'
>>> remove_comment('AAA/*BBB*/CCC')
'AAACCC'
>>> remove_comment('AAA/*BBB/*CCC*/DDD*/EEE')
'AAADDD*/EEE'
>>> remove_comment('AAA/a//*BB*B**/CCC')
'AAA/a/CCC'

このお題は匿名での投稿を参考にして作成しました。 ありがとうございます。

Posted feedbacks - Flatten

Nested Hidden
 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
using System;
using System.Text;
class Program
{
  static void Main(string[] args)
  {
    Console.WriteLine(RemoveBlockComment("AAA"));
    Console.WriteLine(RemoveBlockComment("AAA/*/BBB"));
    Console.WriteLine(RemoveBlockComment("AAA/*BBB"));
    Console.WriteLine(RemoveBlockComment("AAA/*BBB*/CCC"));
    Console.WriteLine(RemoveBlockComment("AAA/*BBB/*CCC*/DDD*/EEE"));
    Console.WriteLine(RemoveBlockComment("AAA/a//*BB*B**/CCC"));
  }

  static string RemoveBlockComment(string s)
  {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < s.Length; ++i)
    {
      if (i < s.Length - 1 && s[i] == '/' && s[i + 1] == '*')
      {
        i += 3;
        while (i < s.Length && (s[i - 1] != '*' || s[i] != '/')) ++i;
      }
      else sb.Append(s[i]);
    }
    return sb.ToString();
  }
}

一番乗りかな? 正規表現は自信が無いので、あってるか不安。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
 -*- coding: utf-8 -*-
import re

def remove_comment(comment):
    print re.sub(r'/\*.*?((\*/)|($))','',comment, re.M)

remove_comment('AAA')
remove_comment('AAA/*BBB*/')
remove_comment('AAA/*BBB')
remove_comment('AAA/*BBB*/CCC')
remove_comment('AAA/*BBB/*CCC*/DDD*/EEE')
remove_comment('AAA/a//*BB*B**/CCC')

一撃必殺!
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(require :cl-ppcre)
(defun remove-comment (str)
  (ppcre:regex-replace-all "/\\*(.+?)(\\*/|$)" str ""))

(remove-comment "AAA")                  ; => "AAA"
(remove-comment "AAA/*BBB*/")           ; => "AAA"
(remove-comment "AAA/*BBB")             ; => "AAA"
(remove-comment "AAA/*BBB*/CCC")        ; => "AAACCC"
(remove-comment "AAA/*BBB/*CCC*/DDD*/EEE") ; => "AAADDD*/EEE"
(remove-comment "AAA/a//*BB*B**/CCC")      ; => "AAA/a/CCC"

上のと同じかも
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
using System;
using System.Text;
using System.Text.RegularExpressions;
class Program
{
  static void Main(string[] args)
  {
    string[] ss = {"AAA", "AAA/*/BBB", "AAA/*BBB", 
      "AAA/*BBB*/CCC", "AAA/*BBB/*CCC*/DDD*/EEE", "AAA/a//*BB*B**/CCC"};
    foreach (string s in ss)
      Console.WriteLine(Regex.Replace(Regex.Replace(s, "\\/\\*.*?\\*\\/", ""), "\\/\\*.*$", ""));
  }
}

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
def remove_comment(str)
  str.gsub(%r!/\*(.+?)(\*/|$)!, "")
end

remove_comment('AAA')           # => "AAA"
remove_comment('AAA/*BBB*/')    # => "AAA"
remove_comment('AAA/*BBB')      # => "AAA"
remove_comment('AAA/*BBB*/CCC') # => "AAACCC"
remove_comment('AAA/*BBB/*CCC*/DDD*/EEE') # => "AAADDD*/EEE"
remove_comment('AAA/a//*BB*B**/CCC') # => "AAA/a/CCC"

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

void remove_comment(const char* s) {
  const char* cs;
  while ((cs = strstr(s, "/*")) != NULL) {
    fwrite(s, 1, cs - s, stdout);
    if ((s = strstr(cs, "*/")) == NULL)
      return;
    s += 2;
  }
  printf("%s\n", s);
}

main() {
  remove_comment("AAA");
  remove_comment("AAA/*BBB*/");
  remove_comment("AAA/*BBB");
  remove_comment("AAA/*BBB*/CCC");
  remove_comment("AAA/*BBB/*CCC*/DDD*/EEE");
  remove_comment("AAA/a//*BB*B**/CCC");
}

N年ぶりにperlでw
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sub remove_comment($) {
    my $s = @_[0];
    $s =~ s/\/\*(.+?)(\*\/|$)//g;
    $s;
}
print remove_comment('AAA'), "\n";
print remove_comment('AAA/*BBB*/'), "\n";
print remove_comment('AAA/*BBB'), "\n";
print remove_comment('AAA/*BBB*/CCC'), "\n";
print remove_comment('AAA/*BBB/*CCC*/DDD*/EEE'), "\n";
print remove_comment('AAA/a//*BB*B**/CCC'), "\n";

しばらく書いてないと書き方忘れたorz D言語も選択肢に入れてちょ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
module doukaku;
private import std.regexp;
private import std.stdio;

char[] remove_comment(char[] str) {
    return sub(str, r"/\*(.+?)(\*/|$)", "", "g");
}

int main () {
    writefln(remove_comment("AAA"));
    writefln(remove_comment("AAA/*BBB*/"));
    writefln(remove_comment("AAA/*BBB"));
    writefln(remove_comment("AAA/*BBB*/CCC"));
    writefln(remove_comment("AAA/*BBB/*CCC*/DDD*/EEE"));
    writefln(remove_comment("AAA/a//*BB*B**/CCC"));
    return 0;
}

1
2
3
function remove_comment(s) {
	return s.replace(/\/\*.*?(\*\/|$)/, '');
}

サクっとやってみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<?php

function remove_comment($str)
{
    return preg_replace('/\/\*.*?(\*\/|$)/', '', $str);
}

echo remove_comment('AAA')."\n";
echo remove_comment('AAA/*BBB*/')."\n";
echo remove_comment('AAA/*BBB')."\n";
echo remove_comment('AAA/*BBB/*CCC*/')."\n";
echo remove_comment('AAA/*BBB/*CCC*/DDD*/EEE')."\n";

Gaucheです
1
2
3
4
5
6
7
8
9
(define (remove-comment str)
  (regexp-replace-all #/\/\*(.+?)(\*\/|$)/ str ""))

(print (remove-comment "AAA"))                  ; => "AAA"
(print (remove-comment "AAA/*BBB*/"))           ; => "AAA"
(print (remove-comment "AAA/*BBB"))             ; => "AAA"
(print (remove-comment "AAA/*BBB*/CCC"))        ; => "AAACCC"
(print (remove-comment "AAA/*BBB/*CCC*/DDD*/EEE")) ; => "AAADDD*/EEE"
(print (remove-comment "AAA/a//*BB*B**/CCC"))      ; => "AAA/a/CCC"

1
2
3
4
5
6
7
8
9
(defun remove-comment (str)
  (replace-regexp-in-string "/\\*\\(.+?\\)\\(\\*/\\|$\\)" "" str))

(remove-comment "AAA")                  ; => "AAA"
(remove-comment "AAA/*BBB*/")           ; => "AAA"
(remove-comment "AAA/*BBB")             ; => "AAA"
(remove-comment "AAA/*BBB*/CCC")        ; => "AAACCC"
(remove-comment "AAA/*BBB/*CCC*/DDD*/EEE") ; => "AAADDD*/EEE"
(remove-comment "AAA/a//*BB*B**/CCC")      ; => "AAA/a/CCC"

std::stringで。
 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
#include <iostream>
#include <string>

std::string remove_comment(const std::string& s)
{
    std::string ret;

    std::string::size_type i1 = 0;

    while (1)
    {
        std::string::size_type i2;

        if ((i2 = s.find("/*", i1)) == std::string::npos)
        {
            ret.append(s.substr(i1));

            break;
        }
        else
        {
            ret.append(s.substr(i1, i2 - i1));

            i1 = i2 + 2;
        }

        if ((i2 = s.find("*/", i1)) == std::string::npos)
        {
            break;
        }
        else
        {
            i1 = i2 + 2;
        }
    }

    return ret;
}

void run(const std::string& s)
{
    std::cout << remove_comment(s) << std::endl;
}

int main()
{
    run("AAA");
    run("AAA/*BBB*/");
    run("AAA/*BBB");
    run("AAA/*BBB*/CCC");
    run("AAA/*BBB/*CCC*/DDD*/EEE");
    run("AAA/a//*BB*B**/CCC");
}

1
2
3
4
5
6
7
8
9
import java.util.regex.Pattern;

public class StringUtils {
	private static Pattern p = Pattern.compile("/\\*.*?(\\*/|$)");

	public static String removeComment(String s) {
		return p.matcher(s).replaceAll("");
	}
}

<algorithm>のstd::searchを使ってみた。size_typeよりイテレータのほうが扱いやすいかと思ったけど、そうでもなかった。(bcc32で確認)
 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
60
#include <iostream>
#include <string>
#include <algorithm>

std::string remove_comment(const std::string& s)
{
    std::string ret;

    const std::string beg = "/*";

    const std::string end = "*/";

    std::string::const_iterator it1 = s.begin();

    while (1)
    {
        std::string::const_iterator it2;

        it2 = std::search(it1, s.end(), beg.begin(), beg.end());

        ret.append(std::string(it1, it2));

        if (it2 == s.end())
        {
            break;
        }
        else
        {
            it1 = it2 + beg.size();
        }

        it2 = std::search(it1, s.end(), end.begin(), end.end());

        if (it2 == s.end())
        {
            break;
        }
        else
        {
            it1 = it2 + end.size();
        }
    }

    return ret;
}

void run(const std::string& s)
{
    std::cout << remove_comment(s) << std::endl;
}

int main()
{
    run("AAA");
    run("AAA/*BBB*/");
    run("AAA/*BBB");
    run("AAA/*BBB*/CCC");
    run("AAA/*BBB/*CCC*/DDD*/EEE");
    run("AAA/a//*BB*B**/CCC");
}

今回もoneliner。 見落としがなければ、欲張らないmatchができる正規表現が書ける言語ならすぐできる?
1
perl -pe 's#/\*.*?(?:\*/|$)##g'

正規表現使ったら、負けかなと思ったりします。
愚直に、一文字ずつ
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
(define (comment a)
  (with-output-to-string (lambda ()
    (with-input-from-string a (lambda ()
      (let loop ((sw #t) (c (read-char)))
        (let ((loopx (cut loop <> (read-char)))
              (test-char-peek (lambda (x y) (and (char=? c x) (char=? (peek-char) y)))))
          (cond ((eof-object? c) #t)
                ((and sw (test-char-peek #\/ #\*)) (loopx #f))
                ((and (not sw) (test-char-peek #\* #\/)) (read-char) (loopx #t))
                (else (when sw (write-char c)) (loopx sw))))))))))

いちお~、文字列を返す仕様で。 というか、元を破壊する方向で。
 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
#include <stdio.h>
#include <string.h>

char* remove_comment(char* str){
	char* comment_start;
	char* comment_end;
	comment_end=str;
	
	while(comment_start=strstr(comment_end,"/*")){
		comment_end=strstr(comment_start,"*/");
		if(comment_end){
			strcpy(comment_start,comment_end+2);
			comment_end=comment_start+1;
		}else{
			*comment_start='\0';
			break;
		}
	}
	return str;
}

int main(){
	puts(remove_comment("AAA"));
	puts(remove_comment("AAA/*BBB*/"));
	puts(remove_comment("AAA/*BBB"));
	puts(remove_comment("AAA/*BBB*/CCC"));
	puts(remove_comment("AAA/*BBB/*CCC*/DDD*/EEE"));
	puts(remove_comment("AAA/a//*BB*B**/CCC"));
}

正規表現使わない
1
2
3
4
5
6
7
8
removeComment []           = []
removeComment ('/':'*':xs) = skip xs
    where skip []           = []
          skip ('*':'/':xs) = removeComment xs
          skip (x:xs)       = skip xs  
removeComment (x:xs)       = x : removeComment xs 

main = getContents >>= (putStr . removeComment)

$だと複数行には対応できないので\zを使って"/\\*.*?(\\*/|\\z)"とすべきかと。

よく考えたらそれはPattern.MULTILINEを立ててる場合だけでした。 しかし、よく考えたらデフォルトでは"."は行末記号と一致しないのでPattern.compile("/\\*.*?(\\*/|$)", Pattern.DOTALL);とすべきですね。

これでいいんじゃないかな?
1
2
3
def remove_comment(str)
  str.gsub(/\/\*.*?(\*\/|\z)/,"")
end

後で読んでも分かるようにVERBOSEにしてみました。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def remove_comment(xs):
    import re
    p = re.compile(r"""
        /       # comment starts with slash
        \*      # next comes asterisk
        .*?     # comment contents
        (?:     # start of grouping without getting value
           \*/  #   end of comment (asterisk followed by slash)
           |    #   or
           $    #   end of input value
        )       # end of grouping
        """,re.VERBOSE)
    return p.sub('',xs)

Squeak Smalltalk で手続き的に。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
| stream delimiter out string upToAll |
string := 'AAA/a//*BB*B**/CCC'.
stream := string readStream.
delimiter := '*/'.
upToAll := [stream upToAll: (delimiter := delimiter reversed)].
out := String new writeStream.
[stream atEnd] whileFalse: [
   out nextPutAll: upToAll value.
   upToAll value].
^out contents

"=> 'AAA/a/CCC' "

Luaはまだいないかな?
1
2
3
function remove_comment( str )
	return (string.gsub( str, "/%*.-%*/", "" ) )
end

shiroさんの"なんでも再帰"(http://practical-scheme.net/docs/tailcall-j.html)にでていた方法で。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
(define (remove-comment str)
  (define (cmnt-open-/ l r)
    (cond ((null? l) r)
          ((char=? #\/ (car l)) (cmnt-open-* (cdr l) r))
          (else (cmnt-open-/ (cdr l) (cons (car l) r)))))
  (define (cmnt-open-* l r)
    (cond ((null? l) (cons #\/ r))
          ((char=? #\* (car l)) (cmnt-close-* (cdr l) r))
          (else (cmnt-open-/ l (cons #\/ r)))))
  (define (cmnt-close-* l r)
    (cond ((null? l) r)
          ((char=? #\* (car l)) (cmnt-close-/ (cdr l) r))
          (else (cmnt-close-* (cdr l) r))))
  (define (cmnt-close-/ l r)
    (cond ((null? l) (cons #\* r))
          ((char=? #\/ (car l)) (cmnt-open-/ (cdr l) r))
          (else (cmnt-close-* l r))))
  (list->string (reverse (cmnt-open-/ (string->list str) '()))))

正規表現1つで
1
2
3
4
import re
PAT = re.compile(r'/\* (.(?!\*/)) *.? (?:\*/)? ', re.X)
def remove_comment(s):
    return PAT.sub('', s)

Haskell の練習
1
2
3
4
5
6
7
8
9
removeComment :: String -> String
removeComment = outOfComment
  where
    outOfComment ('/':'*':cs) = inComment cs
    outOfComment (c:cs) = c : outOfComment cs
    outOfComment [] = []
    inComment ('*':'/':cs) = outOfComment cs
    inComment (c:cs) = inComment cs
    inComment [] = []

既出のHaskellコードのアルゴリズムを拝借してRubyで書いてみた。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def remove_comment(str)
  def off_comment(cs, rs)
    return rs.join unless(cs);
    if ((cs[0] == "/") && (cs[1] == "*"))
      on_comment(cs[2..(cs.length - 1)], rs);
    else    
      off_comment(cs[1..(cs.length - 1)], rs.push(cs[0]));
    end
  end

  def on_comment(cs, rs)
    return rs.join unless(cs);
    if ((cs[0] == "*") && (cs[1] == "/"))
      off_comment(cs[2..(cs.length - 1)], rs);
    else    
      on_comment(cs[1..(cs.length - 1)], rs);
    end
  end

  off_comment(str.split(//), []);
end

いつもこんな風に書いてる。
1
2
3
def remove_comment(txt)
  txt.gsub(/\/\*.*?\*\//m, '')
end

javascript版
1
2
3
var remove_comment = function(txt) {
 return txt.replace(/\/\*.*?\*\//, '')
}

ぁ。行末処理…
1
2
3
def remove_comment(txt)
  txt.gsub(/\/\*.*?(\*\/|$)/, '')
end

こっちも行末処理忘れた。
1
2
3
var remove_comment = function(txt) {
 return (txt + '\n').replace(/\/\*.*?(\*\/|\n)/, '')
}

1
2
3
4
5
6
7
8
(define (remove-comment str)
  (receive (ss cs) (string-scan str "/*" 'both)
           (if ss
               (string-append ss (receive (se ce) (string-scan cs "*/" 'both)
                                          (if se
                                              (remove-comment ce)
                                              "")))
               str)))

確かに、サンプル入力に「改行が含まれるケース」も入れておくべきでしたね(^^;

ocamllex で。(fslex でも通る)
 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
rule proc = parse
| "/*" { skip lexbuf }
| _    { Lexing.lexeme_char lexbuf 0 }
| eof  { raise End_of_file }
and skip = parse
| "*/" { proc lexbuf }
| _    { skip lexbuf }
| eof  { raise