読者です 読者をやめる 読者になる 読者になる

CodeIQ Blog

自分の実力を知りたいITエンジニア向けの、実務スキル評価サービス「CodeIQ(コードアイキュー)」の公式ブログです。

普通じゃないHello World問題「Restricted Words」の解説記事 #伝説 #しえる

問題解説

CodeIQ中の人、millionsmileです。

普通じゃないHello World問題「Restricted Words」の解説記事です。

出題者は、CodeIQ伝説の人@cielavenirさんです。伝説と呼ばれる所以は、
・最も問題を解いていて(問題数は歴代1位)、
・最もバッジをもらっていて(バッジ獲得数も歴代1位)、
・最もいろんな出題者に覚えられていて(出題者によくツイッターで絡んでいるため)、
・そして誰よりも問題が解くのが早い(だいたい1番めの解答者)、
からです。

そんな伝説の人が、今回はじめて問題をだしました。

そして開けてびっくり玉手箱、なんと542人もの方に挑戦いただきました。採点もさぞ大変だったろうと思います。

少々長い(行数にして454行)ですが、@cielavenirさん初の問題の解説記事となります。
お楽しみください。

https://codeiq.jp/ace/cielavenir/q431f:id:codeiq:20130925160646p:plain


◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇

2013年8月19日〜2013年9月19日の期間、数値・文字・文字列リテラルを使わずにHello Worldを出力する問題を出題しました。
当初は1ヶ月で50人行かない予想でしたが、10倍を越える542人の方が挑戦して下さいました。
CodeIQ史上2番目に多い(事務局問題以外では最多)挑戦者数となりました。
皆様誠にありがとうございます。
Hello Worldという極めて簡単に見える題材を扱ったことと、対応言語の多さが功を奏したのかなと思います。

「実行結果のみを評価する」問題であれば、別のサイトでも出題できますので、今回は「その結果が得られた過程を評価する」形式としました。
その結果、自動採点することができず、採点がかなり大変なことになりました。次回はその辺ももう少し練りたいです。

問題文

標準出力に
Hello World
と出力するプログラムを作成して下さい。

ただし、数値、文字及び文字列リテラルを解答に含めることはできません。
Perlのqqやqw、Rubyの%Q、%q、%wなども避けたほうが評価が高くなります。
言語仕様をフル活用して下さい!

プログラミング言語は
AppleScript(osascript)/C/C++/C#/Clojure/D/Erlang/Fortran/Go/Groovy/Haskell/
Hello Algorithm/HSP/Java/JavaScript(Node.js)/Kuin/Lisp/Lua/OCaml/Pascal/
Perl/PHP/Pike/Python/R/Ruby/Scala/Scheme/Smalltalk/VB.Net
のいずれかを使用して下さい。

言語の追加履歴です。

8/19 C/C++/C#/Java/JavaScript(Node.js)/Perl/PHP/Python/Ruby (9言語)
8/27 D/R/Scala/VB.Net (4)
8/28 Hello Algorithm (1)
8/29 Pascal/Fortran (2)
9/1  Go/HSP (2)
9/6  Clojure/Lisp/Scheme (3)
9/9  Groovy/Lua (2)
9/11 Haskell/OCaml (2)
9/14 AppleScript/Erlang/Kuin/Pike/Smalltalk (5)

AppleScript/Erlang/Hello Algorithm/Go/Lua/Pikeの挑戦者数は0、
Kuin/OCaml/VB.Netが1名、Fortran/HSPが2名、Groovy/Pascal/Smalltalkが若干名、
残り16言語が9割以上でした。
関数型言語は解答作成がそれなりに大変でしたが、追加した甲斐がありました。

想定解法としては、問題のポイントは以下の2点になります。

  • 数値リテラルおよび文字列リテラルが禁止されている状況でどのようにして数値を手に入れるか
  • 手に入れた数値を演算して目的の値として出力する

2点目については、数値1を手に入れることさえ出来れば、加算により目的の文字コードを手に入れてから、char型として出力するかあるいはchr()を用いて文字列に変換すれば良いということになります。
例外はPerlで、少なくとも5.12以降は、use bytes;しないとchr()で得られた文字列をprintすることができないようです。

加算を行いやすいのは1なので、1点目については1を得る方法を解説します。
まず、log(E)を使う方法があります。これはJavaだと

(int)Math.log(Math.E)

で実現できます。
C/C++だと

(int)log(M_E)

と書けますが、C/C++ではM_Eは定数ではなくマクロなので、厳密には規約違反となります(@kunio_Yb様よりご指摘いただきました。ありがとうございます)。

もしくは言語仕様を利用する方法で、これは言語ごとにやり方が異なります。

  • C/C++

初期化式を持たないグローバル変数はbssセクションに置かれます。main()が実行されるより前に0で初期化されるので、その変数をインクリメントすれば1になります。
もしくは、少なくともx86/x64のgcc/clang上では、sizeof(char)は1です。

  • D

グローバル変数は0で初期化されるので、その変数をインクリメントすれば1になります。

  • C#

System.Convert.ToInt32(true)により1が得られます。
また、「空配列の長さを表す文字列の長さ」も1になります。

(new int[]{}).Length.ToString().Length
  • Java

static変数は0に初期化されるので、その変数をインクリメントすれば1が得られます。
もしくはjava.math.BigInteger.ONE.toString()を整数に変換すると1が得られます。
また、「空配列の長さを表す文字列の長さ」も1になります。

String.valueOf((new int[]{}).length).length()
  • JavaScript

varにより宣言された変数はundefinedになります。インクリメントするとNaNになってしまいますが、「-~」によりインクリメントすれば1が得られます。
また、「空配列の長さを表す文字列の長さ」も1になります。

[].length.toString().length
  • Perl

myにより宣言された変数はundefとなるので、それをインクリメントすることで1が得られます。
また、「空配列の長さの否定」も1になります。

@a=();!scalar(@a); # => 1
  • PHP

真と判定される組み込み変数(出題者は$GLOBALSを使いました)の二重否定(!!)を取ることで1が得られます。
また、「空配列の長さの否定」も1になります。

!count(array())
  • Python

Trueを文字列に変換し、そのイテレータの先頭の要素をord()することでTの文字コードである0x54が得られます(変数tに代入)。Pythonは++演算子がありませんが、-~によるインクリメントは行えるので、-~t-tにより1が得られます。
また、「空配列の長さを表す文字列の長さ」やTrueのint変換も1になります。

len(str(len([])))
int(True)
  • Ruby

trueを文字列に変換し、bytes.to_aにより文字コードの配列を得て、その先頭を取り出すことでtの文字コードである0x74が得られます(変数tに代入)。Rubyは++演算子がありませんが、-~によるインクリメントは行えるので、-~t-tにより1が得られます。
また、「空配列の長さを表す文字列の長さ」も1になります。

[].size.to_s.size
  • R

length(c(TRUE))は1となります。

  • VB.Net

「空配列の長さを表す文字列の長さ」は1になります。

(new integer(){}).Length.ToString().Length

これらの解答は
https://github.com/cielavenir/codeiq_problems/tree/master/q431
にて公開しています。
8/28以降に追加した17言語については、恐れ入りますが解答例にてご確認下さい。ほとんどがtrueの配列の長さを用いた解答になっています。
一点のみ、Fortranについては、今回文字列リテラルが使えないことから、出力を合わせるためには、print文のフォーマットをchar()から作成する必要がありました。このため難易度が若干上がってしまいました。

他の言語で想定解法が使えるかどうかの保証がなかったのと、Brainfuckなどの低級言語では普通にHello Worldを書いただけで問題の仕様を満たしてしまうので、今回は言語指定とさせて頂きました。
保証がないだけなので、本当は言語不問でも良かったのですが、もし他の言語で提出したかった方がいましたらごめんなさい。

Rは計算機プログラミングには余り使われませんが、全く使われないかというとそうではありません。
print()を使うと整形された出力になりますが、cat()は整形を行わないので、要求されたフォーマットで出力するのに使うことができます。

当初は1ヶ月で50人行かない予想でしたが、3日で30人を突破し、少しびっくりしました。
皆様の解答を見ていますと、Perl/PHPのBarewordを活用した解答や、「Hello/Worldクラスのクラス名」などを使う解答が意外と多いことに気が付きました。そこで、これらは別解として認めることにしました(評価5)。
ただしこの場合でも空白の出力は工夫が必要です。その工夫が今ひとつだった解答は次点ということで評価4とさせて頂きました。
一方、

  • PHPのタグ外にHello Worldと記述する
  • DATAファイルハンドルを使う
  • 正規表現リテラル/Hello World/から直接答えを得る(/ /だけなら場合により許容)
  • ファイル名をHello Worldとする

以上の解答は余りに安直すぎると思われたので、評価3とさせて頂きました。評価基準がはっきりせず申し訳ありません。
なお、

  • 出力する文字コードを数値リテラルを用いて埋め込む
  • 外部と通信して答えを得る
  • 外部ファイルを読み込む

以上の解答は評価1とさせて頂きました。頑張っていただいたのですが、そのやりとりにリテラルが使われていることに変わりはありませんから。

実はこの問題は、当初は次のような問題でした。

[Ruby] 文字列が2つ与えられるので、それらが等しいかどうか判定し、等しいならTrue、等しくないならFalseを出力して下さい。
解答
puts (gets.chomp==gets.chomp).to_s.capitalize

ところが、これができるなら少し応用すれば任意の文字が出力できるのではないかと考えました。であれば基本のきであるHello Worldにしたらどうかと思い、整形したのがこの問題になります。
想定解法が少なかったのは、問題を作った時にはあったこの過程が挑戦者にはないからだと思いました。
大体、評価5が50%、4が20%、3が20%、1が10%の分布になったので、やはり「数値リテラルを使わずに1を変数に代入する」といった誘導を付けるべきだったと反省しています。

ところで、C/C++の解答で末尾にreturn 0;と書かれたもの、および改行を出力し忘れている解答が多数ありました。特に改行については問題文で明示がなかったことも有り、今回はおまけいたしました。

Unixでは、1行目が#!で始まる場合、3文字目以降を実行ファイルのパスとして、そのスクリプトを実行する仕組みが存在します。これをShebangといいます。
出題当初は、RubyやPythonは1行目に「ruby」や「python」を含む、という指示の書き方をしていました。これは、例えばruby2.0やpython3.3など、バージョンを書けるようにするためだったのですが、ごく一部、(「ruby」や「python」の後ろに)バージョン以外の文字列を書き込んだ方がいらっしゃいました。尤も今回は言語を識別できれば十分なので、この点については無視して採点いたしました。

おもしろかった解答をいくつか方法のみ紹介します。

  • JavaやC#には様々なクラスが存在する
    • そのクラス名の一部を取得する
    • 様々な定数値を組み合わせる
  • 例外を捕捉し、メッセージを切り取る
  • JavaScriptの関数に/*Hello World*/なるコメントを挿入しておき、それをtoStringした結果から取得する
  • クラスのフィールド一覧を使う
  • 数学関数を駆使して目的の文字コードを得る
  • (スクリプト言語において)自分自身を読み込み、
    • 各行の長さから文字コードを得る(改行コードに注意が必要です)
    • コメントとして挿入しておいたHello Worldを読み込む
    • コード中にうまくHello Worldを埋め込み、行末の空白の長さで取得する文字のインデックスを指定する
  • Rubyのmethod_missingやconst_missingを使う
    • Hello/Worldメソッド、クラスを呼び出す
    • メソッド名を1文字ずつ分割して、文字の出現頻度を数える
      • Hashを取り出す順序に依存するのでRuby 1.9+用
  • "Hello World\n"をBase64エンコードすると"SGVsbG8gV29ybGQK"となることを利用し、method_missing内で展開する
  • 構造体のsizeofから文字コードを得る
  • 行番号から文字コードを得る
  • 12文字からなるクラスを2つ用意し、そのクラス名を1文字ずつxorする
  • Cの__FUNCTION__を使う
  • enumの初期化子を活用する(enum{zero, one, two, sp=two<<two<<two})
  • 配列を文字列化して、[と,と]を削る
  • 線形合同法を使う乱数生成器に適切なシードを入れて、その出力を変換する
  • Node.jsのエラーは当該行を全て出力することを利用し、制御文字を埋め込んでおく
    • ターミナルを終了するしかなくなりました。ありがとうございます。
  • Rのsubstitute()を使う
    • 個人的には3番目に感動した解答でした。
  • Cの#マクロを使う
  • Rubyの%s!...!を使う
    • うまく引用符を回避した解答でした。いや、単に私が気づかなかっただけですが…。

以下、私が別途許可をとったコードを紹介させていただきます。

  • さりあん様(Clojure)
    • Javaの答案の末尾にClojureのコード片があったので、もしかしてClojureで解けるのですかと訊いたところ送ってくださいました。
    • また掲載許可して下さりありがとうございます。
;; 前回提出した Java を Clojure で書いた場合です。
;; 掲載可 (環境 Clojure 1.5.1)

;; Hello と World を Keyword (:Hello と :World) から取得できる (クラスやメソッドの名前を取得するように)。
;; WhiteSpace は文字コード32を、Keyword、文字コード演算、ビット演算などで取得できる。

;; :32 という Keyword から文字コード32スペースを取得する場合。
; keyword -> string -> int -> char
(def space1 (->> :32 name read-string char))

; (a - A) の引き算で文字コード32スペースを取得する場合。
; keyword -> string -> char -> int -> 引き算 -> char
(def space2 (char (- (->> :a name first int) (->> :A name first int))))

; bit シフトで文字コード32スペースを取得する場合。
; [[]] は (vector (vector)) で vector を一つ含む vector であり要素数が1。
(def one (count [[]]))
(def space3 (char (bit-shift-left one (->> one inc inc inc inc))))

(println (str (name :Hello) space3 (name :World)))
;; printからprintlnに直させて頂きました ciel
  • にとり様(Lisp)
(in-package :cl-user)

(defmacro print-capitalized (symbol &rest symbols)
  `(progn
     (princ ,(string-capitalize symbol))
     ,@(loop for s in symbols
             collect `(princ ,(name-char 'space))
             collect `(princ ,(string-capitalize s)))))

(print-capitalized hello world)
;-> Hello World
  • tompng様 (C)
#include <stdio.h>
#define __ (sizeof(int)^sizeof(int))
#define SIZE (unsigned char)(~__)
char c[SIZE]; // gcc -O2でも通るようにcの宣言を移動しました ciel
#define chr(octet) /*char c[SIZE];*/c[x]=octet;return -~x;
#define I(x) (x-~x)
#define O(x) (x+x)
int init(int x){char c[SIZE]={__};return x;}
int H(int x){chr(O(O(O(I(O(O(I(__))))))));}
int e(int x){chr(I(O(I(O(O(I(I(__))))))));}
int l(int x){chr(O(O(I(I(O(I(I(__))))))));}
int o(int x){chr(I(I(I(I(O(I(I(__))))))));}
int _(int x){chr(O(O(O(O(O(I(O(__))))))));}
int W(int x){chr(I(I(I(O(I(O(I(__))))))));}
int r(int x){chr(O(I(O(O(I(I(I(__))))))));}
int d(int x){chr(O(O(I(O(O(I(I(__))))))));}

int print(int x){/*char c[SIZE];*/puts(c);return x;}

int main(){
  print(d(l(r(o(W(_(o(l(l(e(H(init(__)))))))))))));
}

これらの解答は、Clojure/Lisp/Scheme/Erlang/Haskell/OCamlの解答を作成するときに参考にさせて頂きました。
ありがとうございます。

  • orisano様 (C)
#include <stdio.h>
#define CODE_IQ
typedef struct{long _[__GNUC__];}____;int _____=__LINE__,_______;
main(int _,char**__,char**___)
{
        char *______=__VERSION__;
        while(*___[_______]-*______-sizeof(double)*sizeof(float)+sizeof(char))++_______;
        ___[_______][__FUNCTION__[_____]-__func__[--_____]]=!_____;___[_______][_]+=sizeof(____);
        for(_____=!!NULL;___[_______][++_____];)___[_______][_____]^=__LDBL_MANT_DIG__/__FLT_RADIX__;
        return putchar((_____<<=_____)-(**__=((_<<_<<_<<_)-_))*(__CHAR_BIT__-__llvm__)+!printf(++___[_______],_)),______+=putchar(__builtin_popcount(__INT_MAX__)+_++),!puts((char[]){_____>>~-_|**__, *__FUNCTION__+__USER_LABEL_PREFIX__,__FLT_MAX_EXP__-~-__DBL_DIG__,______[-_],EOF[______]});
}

これをOSX上のgcc -Eでプリプロセスして、更にアンダーバーを置換すると次のようになります。

#include <stdio.h>
#define CODE_IQ
typedef struct{long s1[4];}s4;int s5=3,s7,zero; // putsでの不具合を回避するためzeroを追加 ciel
main(int s1,char**s2,char**s3)
{
 char *s6="4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)";
 while(*s3[s7]-*s6-sizeof(double)*sizeof(float)+sizeof(char))++s7;
 s3[s7][__FUNCTION__[s5]-__func__[--s5]]=!s5;s3[s7][s1]+=sizeof(s4);
 for(s5=!!((void *)0);s3[s7][++s5];)s3[s7][s5]^=64/2;
 return putchar((s5<<=s5)-(**s2=((s1<<s1<<s1<<s1)-s1))*(8 -1)+!printf(++s3[s7],s1)),s6+=putchar(__builtin_popcount(2147483647)+s1++),!puts((char[]){s5>>~-s1|**s2, *__FUNCTION__+s1,128 -~-15,s6[-s1],(-1)[s6],zero});
}

以降このコードを解説していきます。
まず、main()の仕様についてですが、第3引数には環境変数一覧が入ることになっています。

while(*s3[s7]-*s6-sizeof(double)*sizeof(float)+sizeof(char))++s7;

は、'4'+8*4-1すなわち"S"から始まる環境変数を検索しています。大抵の環境ではこれはSHELLになります。
これで、s3[s7]は"SHELL=/bin/bash"になりました。
次に、

s3[s7][__FUNCTION__[s5]-__func__[--s5]]=!s5

について、__FUNCTION__や__func__は関数名すなわち"main"を表します。
s5は3なので、__FUNCTION__[s5]-__func__[--s5]は'n'-'i'すなわち5となり、!s5は0なので、s3[s7]は"SHELL"となります。
次に、

s3[s7][s1]+=sizeof(s4);

について、mainの第1引数はargvの個数が入ります。引数を指定しなくても、1個目は実行ファイル名が入ることになっていますので、s1は1となります。
この1を得るテクニックはコードゴルフでもよく使われます。
sizeof(s4)ですが、OSXではlong型は8バイトであり、それが4つ並んだ配列のバイト数ですから、32となります。
よってs3[s7]は"ShELL"となります。
次に、

for(s5=!!((void *)0);s3[s7][++s5];)s3[s7][s5]^=64/2;

は、s3[s7]の2文字目から終わりまで、大文字小文字を逆転しています。
よってs3[s7]は"SHell"となります。
なので、実は

s3[s7][s1]+=sizeof(s4);for(s5=!!((void *)0);s3[s7][++s5];)s3[s7][s5]^=64/2;

for(s5=!((void *)0);s3[s7][++s5];)s3[s7][s5]^=64/2;

と同じでした。まあ第1引数の解説ができたのでよかったのですが。
最後の出力ですが、printfは書き込んだ文字数を返すので、!printf()は0になります。なのでこのように書き換えることができます。

printf(++s3[s7],s1);
putchar((s5<<=s5)-(**s2=((s1<<s1<<s1<<s1)-s1))*(8 -1));
s6+=putchar(__builtin_popcount(2147483647)+s1++);
return !puts((char[]){s5>>~-s1|**s2, *__FUNCTION__+s1,128 -~-15,s6[-s1],(-1)[s6],zero});
  • まず、mainの第3引数はポインタであることを利用し、++することで2文字目から"Hell"を出力しています。
  • 次に、s5は5なので、s5<<=s5でs5に160を代入し、次いで160-(**s2=(1<<1<<1<<1)-1)*7により'o'を出力しています。

ここでs2の1要素目の1文字目に7が代入されます。

  • 次に、__builtin_popcount()は立っているビット数を返すので、__builtin_popcount(2147483647)は31となります。1を足して、空白を出力しています。

また、putchar()は出力した文字を返すため、ポインタs6を32進めています。s1はインクリメントされて2になります。
これによりs6は" 5658) (LLVM build 2336.11.00)"となります。
~-は非破壊的にデクリメントをするので、s5>>~-s1は80になり「s2の1要素目の1文字目」とビット和をとって87すなわち'W'、'm'+2は'o'、,128 - ~-15は'r'、s6[-s1]すなわちs6[-2]は'l'となります。
(-1)[s6]はs6[-1]と同じ意味であり、'd'となります。
以上の手順で"Hello World"を出力しています。定数を活用した大変面白い解答でした。

  • tompng様 (Ruby)
def method_missing name, *arg
  n=name.to_s.chars.first
  a=arg.first
  if n.downcase == n
    n+a.to_s
  else
    unless n.respond_to? n
      String.class_eval{define_method(n){a.size-n.size}}
      print n+a+(a.reverse.ord-n.ord-a.hex/n.send(n)-n.send(n)).chr #本人からの希望により修正 ciel
    end
  end
end

H  H eeee l    l     oo
H  H e    l    l    o  o
HHHH eeee l    l    o  o
H  H e    l    l    o  o
H  H eeee llll llll  oo

W   W  oo  rrr  l    ddd
W   W o  o r  r l    d  d
W W W o  o rrr  l    d  d
WW WW o  o r  r l    d  d
W   W  oo  r  r llll ddd

Rubyでは定義されていないメソッドを呼び出すとmethod_missingが呼ばれます。
method_missingを使った解法のほとんどが'Hello'や'World'を直接得るコードでしたが、この解答ではアスキーアートを書くために工夫を取り入れています。
method_missingが実際のコードで、下のHello Worldはそれを呼び出すために存在します。では1行目を見てみましょう。

H  H eeee l    l     oo

Rubyではメソッド呼び出しの括弧は省略することができるので、これはこのように解釈されます。

H(H(eeee(l(l(oo()))))

当然これらのメソッドは定義されていないので、method_missingが呼ばれることになります。
method_missingは、メソッド名の1文字目(n)と第1引数(a)を取り出し、nが小文字なら、nとaを結合して返します。ちなみに引数がない場合はaはnilになり、nil.to_sは''(空文字列)となります。
ですので、oo()の返し値は'o'となります。l(oo())の返し値は'lo'です。同様にして、

eeee(l(l(oo()))

'ello'

となります。
さて、nが大文字ならどうでしょうか。この場合、nとaを出力し、続いて「ある文字」を出力します。
まず、

String.class_eval{define_method(n){a.size-n.size}}

で、Stringのインスタンスメソッドにnを追加します。返し値は「(aの長さ)-(nの長さ)」です。aは'ello'ですから3が返ることになります。
次に、

a.reverse.ord-n.ord-a.hex/n.send(n)-n.send(n)

で、ある文字コードを計算します。
a.reverse.ordはaの最後の文字の文字コードなので111です。
n.ordはnすなわち'H'の文字コードなので72です。
a.hexはaの最初の文字を16進数として解釈した時の値ですから14です。
つまり上の式は、

111-72-14/3-3

つまり32となり、空白の文字コードを得ることが出来ました。
これをchrして出力しています。
これで

H(eeee(l(l(oo())))

は'Hello 'を出力しているとわかりました。では最後に残ったHはどうなるのでしょうか。
実は空白を出力する行は

print (a.reverse.ord-n.ord-a.hex/(a.size-n.size)-(a.size-n.size)).chr

で十分なのです。
ではなぜわざわざインスタンスメソッドを定義したのでしょう。
肝は

unless n.respond_to? n

にあります。
つまり外側のHでは、メソッドHは既に定義されているので、unless以下は実行されません。上の問の答えは「最後に残ったHは何もしない」となるのです。
2行目から5行目も「何もしない」ことを利用してアスキーアートを描いています。
7行目の

W   W  oo  rrr  l    ddd

すなわち

W(W('orld'))

についても同様です。こちらについては、'o'を16進数で解釈することはできないので'o'.hexは0となります。ですので最後に

100-87-0/3-3

つまり10すなわち改行を出力することになります。
8行目から11行目も「何もしない」ことを利用してアスキーアートを描いています。
私もこれを書く中でかなり勉強させて頂きました。素晴らしいです。TRICK (in rubyKaigi)に出していたら入賞していたかもしれないコードだと思います。それにも関わらず、今回は提出して下さりありがとうございました。

PS.
ideoneにはBrainfuckの他、WhiteSpaceなど他の低級言語も登録されているので、今回「ideoneで使える言語」という縛りができませんでした。
そのため対応言語を要望に応じて後から増やす形となってしまい、運営さんには数日ごとに大変ご迷惑をお掛けしました。この場をお借りしてお詫び申し上げます。
#人数増えると思っていなかったので、95%カバーしとけばいいやとたかをくくっておりました--;



エンジニアのための新しい転職活動!CodeIQのウチに来ない?の特集ページを見る