Groovyで後置if

なんか後置ifがtwitter上で話題になってたのでやってみました。
文のうしろに「if ほげほげ」みたいに書くとほげほげが真の場合だけ文を実行してくれる、てやつですね。Groovy風にかくなら

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

て感じかな、と。つまりifてメソッドクロージャに追加して、条件を引数にしちゃえばええやん、と思いついたのです。
で、とりあえず流してみると

Exception thrown: No signature of method: ConsoleScript0.if() is applicable for argument types: (java.lang.Boolean) values: [false]

groovy.lang.MissingMethodException: No signature of method: ConsoleScript0.if() is applicable for argument types: (java.lang.Boolean) values: [false]
	at ConsoleScript0.run(ConsoleScript0:3)

そんなメソッドねーべ、とおこられます。こりゃ行けそう。とりあえず先頭でなんもしないメソッドを追加してみます。

Closure.metaClass.if = {boolean condition->
};
// 以上追加。以下は元々あったassertたち

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

これで動いて、2個目のassertで落ちるはず。

Exception thrown: Expression: (executed == true). Values: executed = false

java.lang.AssertionError: Expression: (executed == true). Values: executed = false
	at ConsoleScript3.run(ConsoleScript3:11)

はい、予想通り。じゃ、実装。conditionがtrueの場合だけクロージャ自身を実行する訳ですね。

Closure.metaClass.if = {boolean condition->
    if (condition) {delegate.call()} // 1行追加
}

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

実行してみると…

Exception thrown: No signature of method: ConsoleScript4.call() is applicable for argument types: () values: []

groovy.lang.MissingMethodException: No signature of method: ConsoleScript4.call() is applicable for argument types: () values: []
	at ConsoleScript4$_run_closure1.doCall(ConsoleScript4:2)
	at ConsoleScript4.run(ConsoleScript4:10)

ありゃ、なんか落ちました。なんかdelegateが「ConsoleScript4」てクラスになってるみたいです。
「ConsoleScript」はGroovyConsoleでスクリプトを書いた場合にスクリプト自体の為に作成されるクラスです。あれれ?Closureじゃないの?
ちょっと調べてみると…
[GROOVY-3674] In Closure as injected method of Closure, delegate is not collect - jira.codehaus.org

Setting the delegate correctly is something only ExpandoMetaClass does〜calling EMC.enableGlobally() before clos is getting its value. If you do that, then your example works too.

ふむ。EMC.enableGlobally() すりゃうまく行くよ、と。EMCはExpandoMetaClassを省略してるんですな。じゃ…

ExpandoMetaClass.enableGlobally();

Closure.metaClass.if = {boolean condition->
    if (condition) {delegate.call()}
}

def executed = false;

{->executed = true}.if('a' == 'A')
assert executed == false;

{->executed = true}.if('a'.equalsIgnoreCase('A'))
assert executed == true

はい、通りました。後置ifの完成です。

さて、トリッキーなところが2つあったので、別エントリで立てときます。

MacBook Air SMC ファームウェア・アップデート1.2を実行すると「このアーキテクチャが対応していないので起動できません」と言われる件

ちょっとハマったので書いておきます。
えー、私は「パッケージの内容を表示」して、Unix実行ファイルを直接叩いて入れたんですが、そんな危なげな事は必要ありませんでした。入れ終わってから犯人が判明。
アップデートが終わって再起動してみると、すっごく重い。はたと気付いて見てみると案の定CoolBookControllerのDeactivate original driverのチェックが外れてました。で、Preferencesから再度Deactivate original driverをチェックして再起動。
…。
…。
…あっ。

という訳で、MacBook Air SMC ファームウェア・アップデート1.2が入らなくて困ってるみなさーん!CoolBook Controller入れてませんか?
一度CoolBook Controllerを起動してDeactivate original driverのチェックを外してから /アプリケーション/ユーティリティ の中に有るファームウェア・アップデートを実行してみてみて下さい!
んで、アップデート後はDeactivate original driverし直すのもお忘れなく!


えー。CoolBookControllerは犯人ではなかったようなので訂正。
これまでは「大文字小文字を区別」付きのHFS+にしてたんですが、SSDを入れ替えた時に「大文字小文字を区別」しない普通のHFS+(ジャーナリング)にしたんです。それでデータを移して起動したら…MacBook Air SMC ファームウェア・アップデート1.2が自動実行されるじゃないですか!
丸ごとSSDの中身を移したのでもちろんCoolBookControllerも入った状態だったのですが、ばっちり実行されました。
なので、おそらく犯人は「大文字小文字を区別」です。これは…気軽に外して、戻して、なんて出来るもんじゃないですね。
なので、「大文字小文字を区別」するフォーマットでパーティションを切ってしまっている人は、私がやったように
1. 「MacBook Air SMC ファームウェア・アップデート」を右クリックして
2. 「パッケージの内容を表示」し
3. 「Contents/MacOS/」の中にある「MacBook AIR SMC Firmware Update」を直接実行する
しか無いようです。


アップデートするとCoolBookControllerが外れるのは事実なので、「アップデート後はDeactivate original driverし直」さないととてつもなく重いのは本当です。

new Target(property: value) with assertionによる簡易テスト

懺悔します。私、これまで、Groovyの1ファイルスクリプト書くときテストをさぼってました。
だってテストしにくいんですもん。

1ファイルで実行するだけのスクリプトを書いている時に、もう1個テスト用にファイルを作るのはとてもめんどくさいです。ファイルを行き来するだけで切り替えも発生してしまいます。非常に不便です。
1つのファイル中に複数のクラスを作る場合、ファイル名と同名のクラスは作れません。が、importで.groovyファイルからクラスをインポート出来るのはファイル名と同名のクラスを書いてある場合だけです。

で、まぁ書き捨てだしいいや、とテストをしない事が多かったんです。

でも、assertなら書けます。これならそんなに手間ではないし、テストが失敗した時だけ騒いで成功時にはおとなしくしています。もっとassertするべきやったんです。
あとはassertするためのテストオブジェクトに実行空間を汚染させなければ最高です。で、new Target(property: value) with assertion。私の造語、というかやる事そのまんまなんですけどね。
こんな風にします。

new Foo(
    bar:5,
    buzz:10
).with {
    assert hoge == 15 : "hogeはbar + buzzの値となる"
    assert fuga == 50 : "fugaはbar * buzzの値となる"
}
class Foo {
    int bar
    int buzz
    public int getHoge() { return bar + buzz; }
    public int getFuga() { return bar * buzz; }
}

newでプロパティも設定してしまって、で、withでassertを書きまくる。これならインスタンスが生きてる間にテストをやって、テストが終わったら即ごみ収集に持って行ってもらえます。
これは楽です。これならちょっとしたスクリプトでもテスト駆動で作る事が出来ます。

アメージング!
エクセレント!
ぜひお試しあれ。

プリミティブはオブジェクトじゃないし、プリミティブと参照なんて区別はない

404 Blog Not Found:javascript - にはクラスはないの重箱の隅をつつきます。「JavaScriptにクラスはない」という表題の主張自体は合っていると思うので、重箱の隅。
最初気になったのは「参照」の間違いだったんですけど、調べるうちにいろいろと…

JavaScriptにおけるほとんど全てのデータはオブジェクト」ではない

4 概要 (Overview)によると、プリミティブ値は

プリミティブ値 (primitive value) は Undefined, Null, Boolean, Number, String 型のうちの一つ

とあり、オブジェクト

オブジェクトは Object 型の構成要素である。序列のないプロパティの集合体で、それぞれのプロパティがプリミティブ値やオブジェクト、関数を含む

とは明確に区別されています。プロパティ(メソッドも含む)を持つのはオブジェクトだけで、プリミティブ値はプロパティを持ちません。
じゃあ、なぜ

p(true.toString());
p("String".toString());
p((1).toString());
p((3.14159265).toString());
p([0,1,2,3].toString());
p({q:"answer to life, the universe, and everything", a:42}.toString());

とプリミティブ値のtoString()メソッドを呼べる(ように見える)のか、というと…
11 式 (Expressions)

  1. MemberExpression を評価。
  2. GetValue(Result(1)) を呼出す。
  3. Expression を評価。
  4. GetValue(Result(3)) を呼出す。
  5. ToObject(Result(2)) を呼出す。
  6. ToString(Result(4)) を呼出す。
  7. 基準オブジェクトが Result(5) でプロパティ名が Result(6) である Reference 型の値を返す。

と、(MemberExpression[Expression]と解釈される)MemberExpression.ExpressionではMemberExpressionはオブジェクトに変換される、という仕様になっているからです。
つまり、実は暗黙にオブジェクトに変換されて

p(new Boolean(true).toString());
p(new String("String").toString());
p(new Number(1).toString());
p(new Number(3.14159265).toString());

とやるのとおんなじ意味になってるんですね。暗黙にオブジェクトに変換されるからメソッド(プロパティ)にアクセスできるだけで、プリミティブ型がメソッド(プロパティ)を持ってる訳では有りません。
ちなみに、プロパティアクセス式にあるtoObject(やtoString)は9 型変換 (Type Conversion)で説明用に定義された物で、実際にそういう関数が有る訳ではありません。

「プリミティブと参照」なんて分類はない

実際に型として「Reference 型」も登場するのでまたややこしいんですが…「プリミティブと参照」なんて分類はありません。
8 型 (Types)

値は実体であり、9 つの型のうちの 1 つを取る。型は 9 つ (Undefined, Null, Boolean, String, Number, Object, Reference, List, Completion) ある。Reference 型, List 型, Completion 型の値は式評価の中間結果としてのみ使われ、オブジェクトのプロパティに蓄積はできない。

まぁ内部的に使われるだけのReference, List, Completionおよび特殊な値であるUndefinedとNullは置いといたとして、「には二種類ある - プリミティブとオブジェクト」と言うべきなのです。
ちなみに、Reference 型はまた少し違った話になります。これはプロパティ(や変数)に代入可能な物ではないので、後ほど。実はこれが凄く面白かったんで調べこんだんですが;-p


閑話休題
で、その「参照」の話なんですが、

a = bとしてから、bにだけ改変を加えた場合、プリミティブではbだけが変わっていますが、参照ではaも変わってしまっています

と言っている例が変なんです。これが読んですぐに気がついた間違いで、Javaにおいて「Stringだけ参照渡しじゃない、同じオブジェクトなのに特別扱いだ」なんて言う勘違いと本質的には一緒だと思います。
はしょりつつ例を引用すると…
プリミティブの例:

var a = 0;
var b = a;
p('a is ', a, ', b is ', b, '.');
b++;
p('a is ', a, ', b is ', b, '.');
a is 0, b is 0.
a is 0 + b is 1.

参照の例:

var a = {x:'foo', y:'bar'};
var b = a;
p('a.x is ', a.x, ', b.x is ', b.x, '.');
b.x = 'baz';
p('a.x is ', a.x, ', b.x is ', b.x, '.');
a.x is foo, b.x is foo.
a.x is baz, b.x is baz.

と、「プリミティブはbを変更した後にaを見ても変わってないけど参照はbを変更すると変わってるよね」とやってるのですが大きな間違いです。
プリミティブの例と参照の例で違う事をやっているだけです。
プリミティブの例でやっている事をオブジェクトでやってみましょう。(プリミティブと参照という区別は間違いで、プリミティブとオブジェクトです。)

var a = new Number(0);
var b = a;
p('a is ', a, ', b is ', b, '.');
b++;
p('a is ', a, ', b is ', b, '.');
a is 0, b is 0.
a is 0 + b is 1.

プリミティブのときと全く同じ結果です。++演算子は左辺値から値を取得し、プリミティブのNumberに変換し、それに1を加算し、その結果を左辺に再代入する物です。値そのものを変化させる訳ではなくて、左辺にある変数なりプロパティを変更するだけです。
で、今度は「参照の例」でやっている事をプリミティブに対してやってみる…事が出来ると良いのですが、プリミティブにはプロパティがないので、出来ません。プロパティを持っているのはオブジェクトだけですから。
プロパティを変更すると同一のオブジェクトを持つ別の変数にも伝播する事からオブジェクトはいわゆる「参照」と同じ振るまいをしますが、プリミティブの場合プロパティが(破壊的な操作も)ないのでプリミティブがいわゆる「参照」かどうかを調べる事は出来ません。(Reference型は存在するのでちょっと迂遠な表現になってます^^;)
が、実は少なくともString(javascriptではプリミティブです)も「参照」じゃないかと思います。可変長の文字列の配列を作れますから。実際、以下のような実験をしてみると

<div id="out"></div>
<script type="text/javascript">
function bench() {
    var str = "abcdefgijklmnopqrstuvwxyz"
    for (var i = 0; i < 20; i++) {str += str}
    document.getElementById("out").innerHTML = "String:" + getPutTime(str) +
                                                                           ", Object:" + getPutTime(new String(str))
            }
function getPutTime(val) {
    var l = new Array(1000000)
    var start = new Date().getTime()
    for (var i = 0; i < l.length; i++) {l[i] = val}
    return new Date().getTime() - start
}
</script>
<button onClick="bench()">test</button>

プリミティブのStringとStringオブジェクトでコピーの時間がほぼ一緒です(safari4とcamino1.6.7とchromium 3.0.184の場合)。
26*(2^20)文字で27262976文字あるので、もし値だとすると結構なコストになると思います。まぁ一定の範囲を一気にコピーするのは凄く速かったはずなので、一概には言えないんですが…
さらに、safariではstrを作るところのループは20から24に変える(2^4で16倍の長さ)だけでout of memoryになりますが、ベンチマークのArrayはサイズを100000000(100倍)にしてもout of memoryにはなりません。少なくとも配列にはプリミティブの文字列も参照が入れられている事になります。
変数やプロパティについても、(たぶん)サイズの違うBooleanやStringやNumberが入れられるので、おそらく即値が入っている訳ではないでしょう。

おまけ:JSのReferenceはちょっと違う

さて、おまけで面白かったReference型について、ちょっと書いておきます。
8 型 (Types)

Reference は、オブジェクトのプロパティへの参照である。Reference は基準オブジェクト(base object) とプロパティ名の 2 つの成分から構成される。

メモリ上のどこにある値、とか、そんな定義じゃないんですね。「どのオブジェクトのなんてプロパティ」がECMAScriptにおける「参照」なんです。ちなみにメソッドもオブジェクトのプロパティでしかないので、参照に入れる事が可能です。
じゃあグローバル変数や関数や変数はどうなるの?と調べてみると…10 実行コンテキスト (Execution Contexts)10 実行コンテキスト (Execution Contexts)を見るとスコープもオブジェクト、グローバルもオブジェクトでグローバル変数や関数やローカル変数もそれらのプロパティなんですね。
というわけで識別子は実は全てオブジェクトのプロパティになってる訳です。違う意味で「全てオブジェクト」なのでした。

(与太話)JavaScriptにもClassは有る!

8 型 (Types)

プロパティ パラメータ 説明
[[Prototype]] 無し このオブジェクトのプロトタイプ。
[[Class]] 無し このオブジェクトの種類。

(以下略)

なんと!内部プロパティにはClassが有るじゃないですか!…

Host オブジェクトの[[Class]] プロパティの値は任意の値でよく、組込み オブジェクトからその [[Class]] プロパティのために使用される値でも例外ではない。 この仕様はプログラムに [[Class]] プロパティの値へのアクセスを意味するものは何も提供しないことに注意; これは組込み オブジェクトの種類の違いの区別に内部的に使用される。

て組込みのオブジェクト専用の内部プロパティですか…たぶんC++(やその他の実装コード)のクラスとひもづけて、動作を高速化するための物なんでしょう。
ちなみに、Hostオブジェクトとはユーザ定義のオブジェクトとかDOMのオブジェクトとか、組込みオブジェクト以外のオブジェクト全部を指すみたいです。そのその他大勢についてはClassは適当でかぶっても良い、て事…なんでしょうか??
少なくとも、「Classはない」と言ってるClassとは少し毛色が違うポイですし、JavaScript言語には現れない物です。「Classは有ったんだ!」なんて真に受けないでくださいね!-)

embeddedのgroovy-all-1.6.3.jarだけではGrabできない?

@Grabアノテーションを使うためには、embedded/groovy-all-1.6.3.jarの他にlib/ivy-2.0.0.jarが必要な様です。単に固め漏れなのか、あるいはなにかライセンス絡みで入れられなかったのかもしれません。わかりませんが…


JFreeChartを試してみようといろいろしていたのですが、私が普段使っているJar Bundlerを使って作ったGroovy.appではGrabが動かなくてハマりました。
他にもいろいろハマりつつだったのですが、jfreechartと関係ないクラスが足りないと言われたので、早い段階で「embeddedのjarだけでは怪しいぞ」という見当はついて、まずは$GROOVY_HOME/bin/groovyConsoleから起動したGroovyConsoleで動く所まで作り込んだのが以下のスクリプトです。

import org.jfree.chart.*;
import org.jfree.chart.plot.*;
import org.jfree.data.general.DatasetUtilities;
import javax.swing.JFrame;

@Grab(group='jfree', module='jfreechart', version='1.0.9')
def createLineChartFrame() {
    def chart = ChartFactory.createLineChart("test", "X軸", "Y軸",
            DatasetUtilities.createCategoryDataset("", "",
                    (double[][])[[1,2,3],[2,3,4],[4,6,5]]),
            PlotOrientation.VERTICAL,
            false, false, false)

    new ChartFrame("barChartFrame", chart)
}

createLineChartFrame().with {
    setSize(320, 240)
    setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    setVisible(true);
}

とにかく@Grabアノテーションはクラスなりスクリプトクラスの特性として展開される何かなりに付けないと動かないらしいので、無理矢理関数を1個切ったりしています。
で、まぁこれでバッチ起動したGroovyConsoleからは動いたのですが、Groovy.appからは動かないのでした。
lib配下をすべてクラスパスに入れて動かしてみたりしたのですが、結局groovy-all-1.6.3.jar以外に必要だったのはivy-2.0.0.jarだけのようです。

  • embedded/groovy-all-1.6.3.jar
  • ivy-2.0.0.jar

の2つをGroovy.app内のContents/Resources/JavaにコピーしてClassPathを通してやると、上記スクリプトが無事動くようになりました。

「topcoderの道1」をGolfで

これも実は2009-06-06 - uokumuraの日記する前にやっていたのだけれど、こっちはやる前にコードを見ちゃったのと、野球見ながらやっていたので時間が計れてないのです。
で、縮めれそうなのでGolfの方向でネタにする事にして縮めてみたら、(指定されてる範囲で)仕様を守りつつ70文字(本体63文字)までは縮める事が出来ました。

topcoderの道1|プログラミングに自信があるやつこい!!

与えられた英語の大文字で構成された文字列の中の文字を、与えられた数字の分だけ左にシフトさせなさい。たとえば、’C’を2つ左にシフトさせると’A’、’Z’を2つ左にシフトさせると’X’。
与えられる英語の文字列はAからZで、Aの次はZにシフトさせるものとする。

という問題で、見た答えはゲンゾウ用ポストイット: 続・topcoderの道1をといてみる

class CCipher{  
    final def ALPHA_LIST = 'A'..'Z'  
    String decode(String ciphertest, int shift){  
        ciphertest.collect{  
            ALPHA_LIST[ALPHA_LIST.indexOf(it) - shift]  
        }.join()  
    }  
}  

groovyだとインデックスがマイナスなら後ろから見るっていう特徴をうまく使った、エレガントなお答え。
「対象文字列の各文字について、アルファベット上でshift字前の文字を集める」なんてまさしく仕様を素直にコードに落とせています。
これ以上する事も無いのですが、
Groovyで文字処理を書くときの心得 - uehaj's blog

Cだと例えば

char ch;
char converted = (ch-'A'-shift) % 26 + 'A';

みたいに良くやるやりかたでGroovyを書こうとすると死ぬる。えーとCharacter.valueOf(ch.charAt(0) as char)・・・とか、全然簡潔じゃないし!!

なんて読んじゃえばそのいばらの道に踏み込みたくなるのが人情というもの。で、やってみると結構簡潔になったのです。Golfできるくらいに。
で、出来たのが以下のコード。

decode={s,n,Z=(int)'Z'->s.collect{(char)(Z+((int)it-Z-n)%26)}.join()}

Aを起点にすると符号が変わってしまって剰余だけではうまく動かないので、Zの何文字前かを調べて、そのさらにn文字前、と必ず負の値になるように仕掛けています。ちなみに、'Z'やitをchar型でなくint型にキャストしているのはGolfしたくなったからです。せこく2文字稼ぎました^^;
ファイル名をCCipher.groovyにすれば指定通りのメソッドが70文字のファイルで定義できる事になります(正確にはdecodeフィールドに置かれたクロージャですが、それはメソッドと見なすのがgroovy流なので…)。引数名は指定と違いますが、まぁそこはIFに現れないので実装上の詳細という事で(Golfしたかったのです)。

ひとまず例をそのままassert文にすれば

assert decode("VQREQFGT", 2) == "TOPCODER":decode("VQREQFGT", 2)
assert decode("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 10) == "QRSTUVWXYZABCDEFGHIJKLMNOP":decode("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 10)
assert decode("TOPCODER", 0) == "TOPCODER"
assert decode("LIPPSASVPH", 4) == "HELLOWORLD"

で、これはばっちり通ります。境界例をテストしてみても…

assert decode("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26) == "ABCDEFGHIJKLMNOPQRSTUVWXYZ":decode("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26)
assert decode("BCDEFGHIJKLMNOPQRSTUVWXYZA", 27) == "ABCDEFGHIJKLMNOPQRSTUVWXYZ":decode("BCDEFGHIJKLMNOPQRSTUVWXYZA", 27)

ばっちり。また、仕様では指定されていない以下のような機能まで付加する事が出来ました。

assert decode("vqreqfgt",2,(int)'z') == "topcoder":decode("vqreqfgt", 2,(int)'z')
assert decode("VQREQFGT",2,(int)'Z') == "TOPCODER":decode("VQREQFGT",2,(int)'Z')

が、さすがにshiftに負の数値を指定すると動きません。

assert decode("QRSTUVWXYZABCDEFGHIJKLMNOP", -10) == "ABCDEFGHIJKLMNOPQRSTUVWXYZ":decode("QRSTUVWXYZABCDEFGHIJKLMNOP", -10)

これはエラーになってしまいます。まぁ負の場合後ろへシフトするように作っても

decode={s,n,O=(int)(n<0?'A':'Z')->s.collect{(char)(O+((int)it-O-n)%26)}.join()}

と起点を表すデフォルト引数(Zとは限らなくなったのでORIGINでOとしました)を3項演算で取れば良いだけなんですけど、Golfにこだわっちゃいました;p
まぁ、Golfしないなら

String decode(String ciphertest ,int shift) {
    def origin = (char) (shift<0 ? 'A' : 'Z');
    ciphertest.collect {
        (char) (origin + ((char) it - origin - shift) % 26)
    }.join()
}

て感じでしょうか。なんともトリッキーですねぇ…
たしかに、「Groovyで文字の絡む処理はC言語的にやったら負け」なようです。

「10分でコーディング」に挑戦

ゲンゾウ用ポストイット: 10分でコーディング|プログラミングに自信があるやつこい!! だって。とか10分でコーディング - uehaj's blogとかはやってるみたいなのでやってみました。(もちろん自分でやってみるまではコードには目を通しませんでしたよ!)
10分でコーディング|プログラミングに自信があるやつこい!!

あまりに簡単なので制限時間を10分としてやってみてください。
これ以上かかった人は
自分はかなりプログラミングができない。
とつらい事実を認識しましょう。

うひー。これは頑張らないと…
てわけでやってみたら、10分ぎりぎりでなんとか書き上げました。あぶなー…

まずはシンプルなのを。

文字列をカードのデッキに見立てて、指定された人数分配る、というプログラムらしいです。仕様は例で示されているので、例通りのassertを1個ずつ書いていく事にしました。

2つの引数がもらえます。

3
"123123123"

最初の3はプレイヤーの人数を示しています。
"123123123" はトランプの並びを示しています。あなたはこのなかのトランプを配っていかなければなりません。
この場合、あなたのプログラムは

{"111","222","333"}

を返さなければなりません。

ふむふむ。

assert deal(3, "123123123") == ["111","222","333"]:deal(3, "123123123")

もちろんエラーになるので、ひとまずfake it。

String[] deal (int numPlayers, String deck) {
    return  ["111","222","333"]
}

assertが意図通りに動く事を確認したので、実装します。要はnumPlayers分の配り先を作って、先頭から順に配っていけば良いんですね。

String[] deal (int numPlayers, String deck) {
    def res = (0..<numPlayers).collect{[]}
    deck.eachWithIndex{it, i->res[i%numPlayers] << it}
    return res*.join("")
}

通りました。リファクトしたいところですが、時間制限が有るのでどんどん行っちゃいます。

割り切れない場合

すべてのプレイヤーは同じ数だけのトランプを受け取らなければなりません。ですので

4
"123123123"

この場合、あなたのプログラムは

{"12","23","31","12"}

を返さなければなりません。
{"123","23","31","12"} は駄目です。

だそうです。

assert deal(3, "123123123") == ["111","222","333"]:deal(3, "123123123")
assert deal(4, "123123123") == ["12","23","31","12"]:deal(4, "123123123")

もちろん{"123","23","31","12"}になってしまってエラーになります。
配りきれない最後の端切れ部分はのぞかなきゃいけないんですね。なら…

String[] deal (int numPlayers, String deck) {
    def res = (0..<numPlayers).collect{[]}
    deck[0..<(deck.length()-(deck.length()%numPlayers))].eachWithIndex{it, i->res[i%numPlayers] << it}
    return res*.join("")
}

deckの割り切れる部分だけを対象にしてやれば良いんです。これで2つとも通りました。

他のテストも

では、以下にもうすこし例をのせます。

と、いろんな例が載っています。多分もう通ると思うので、一気にassertを足してみました。通らなきゃコメントアウトしつつ実装していけば良いのです。

assert deal(6, "012345012345012345") == ["000", "111", "222", "333", "444", "555"]: deal(6, "012345012345012345")
assert deal(4, "111122223333") == ["123","123","123","123"]:deal(4, "111122223333")
assert deal(1, "012345012345012345") == ["012345012345012345"]:deal(1, "012345012345012345")
assert deal(6, "01234") == ["", "", "", "", "", ""]: deal(6, "01234")
assert deal(2, "") == ["", ""]: deal(2, "")

完璧に通りました。完成です。

指定された仕様?

完成かと思いきや、

クラス名、などは以下のとおりです。

Class:    Cards
Method:    deal
Parameters:  int, String
Returns:   String
Method signature: String
deal(int numPlayers, String deck)

て仕様が最後に指定されてるじゃないですか。関数で書いてしまいました。仕方が無いので、この仕様に合うように直します。
保存ボタンを押して、ファイル名を「Cards.groovy」と指定して保存。
ばっちりです!今度こそ完成です。


で、出来たので他の人のを見てみると…
ゲンゾウ用ポストイット: 10分でコーディング|プログラミングに自信があるやつこい!! だって。:

class Cards{  
    String[] deal(int numPlayers, String deck){  
        def cardCount = (int)(deck.size() / numPlayers)  
          
        def i = 0  
        deck.toList().groupBy{  
            i++%numPlayers  
        }*.value*.join()  
    }  
}  

むむ!groupByとは!そんなんが有ったんですねー…悔しい、かっこいい…
やっぱAPIはきっちり身に付けないとあきませんねー…