No Programming, No Life

プログラミング関連の話題や雑記

Groovyで2回目の呼び出し以降は計算せず高速で値を返却するメソッドを作る

2回目の呼び出し以降は計算せず高速で値を返却するメソッドを作ってみました。 いわゆるメモ化ですね。Groovyの場合はクロージャが使えるので、理解しやすいと思います。

動作環境

Groovy Version: 3.0.2 JVM: 14 Vendor: Oracle Corporation OS: Windows 10

ソース

def calc(num1, num2) {
    def num = num1 + num2 // 計算結果をここで保持
    return { num } // 計算結果を返すクロージャを返す
}

def doCalc = calc(5, 8)
assert doCalc() == 13
  • 最初に calc メソッドが呼ばれた時に1回だけ num1 + num2 が計算されて、結果が num に格納されます
  • 格納された num を返却するクロージャが返却されます
  • 返却されたクロージャは何度呼び出しても num を返却するだけなので高速です

GroovyでMarkdownパーサーを作ろう #4「Headers(見出し)の実装」その2

このシリーズの一覧


Groovy! (挨拶)

どうも、ふも(@)です。

はじめに

さてそれでは、前回の続きで、Headers(見出し)の具体的な実装に入って行きたいと思います。

一行ずつ処理する

前回までは、ファイルから読み取った内容を一気にHTMLのBODYタグに埋め込んでいましたが、
今回実装しようとしているAtx形式のヘッダーは行単位で処理してゆくのが適していそうです。
そこでまず、ファイルから一行一行取り出して処理するように変更したとして、
その一行一行に対する処理を考えてみましょう。
ということでまず、fumomarkdowngの冒頭部分を以下のように変更します。

// 第一引数の内容を読み込む
def lines = new File(args[0]).readLines()

// Markdown形式をHTMLに変換
def md = new Markdown()
def contents = lines.collect {
  md.headerAtx(it)
}.join('\n')

マークダウン形式のファイルを読み込んで一行ずつのリストにして(#readLines)、変数linesに格納します。
次に、Markdownクラスを作成*1して、読み込んだ各行について、Markdown#headerAtxを呼び出し変換処理を行う。
変換した結果は、改行文字で#joinして、contentsの出来上がりという流れ。
これで、外枠の読み込み部分はできましたので、次は変換処理のメインに行きましょう。

Markdownクラスの作成

今後、色々な変換処理を実装することになりそうなので、Markdownというクラスを作成することにしました。
今回はとりあえずAtx形式変換するメソッド #headerAtx を実装することにしましょう。

Groovyではクラス定義の仕方はJavaと同じです。

class Markdown {
   // ...
}

メソッド定義もJavaと同じです。さらにGroovyでは引数を省略してdefキーワードも使えるのですが、今回は明示的にStringにしておきましょうか。
引数はString mdで、ファイルから読み取った1行の文字列が来る想定です。

class Markdown {
  /** 見出し:Atx形式 */
  String headerAtx(String md) {
    // ...
  }
}

行の先頭をまず確認します。具体的には "#" で始まっているかどうか?を確認する必要がありますね。

次に、#headerAtxの中身です。まずは、'#'の数を数えておきましょう。

def sharpSize = md.find(/^#+/).size()

#の数は1〜6の範囲です。それ以上でも以下でもないです。
"#" が所定の数で始まっているなら、次のステップへ移行します。

'#'の数が1〜6なら所定の処理を行う部分は、Groovyならin演算子を使って以下のように書けますね。

if (sharpSize in 1..6) {
  // ...
} else {
  return md 
}

"#" が所定の数で始まっていなかったり(例えば7個とかね) そもそも "#" で始まっていなかったりした場合はそのままその行は終了。

'#'の数が範囲外なら、何もせずにそのままreturn mdします。

さて、範囲内だった場合、いよいよ変換処理なわけですが、'#'の数がそのままHタグの数値部分になるのですが、
'#'の数はすでにわかっているので、

その行の先頭と末尾の連続する "#" を取り除きます。

def contents = md.replaceAll(/^#+\s*|\s*#+$/, '').trim()

もしくは、同じことが以下のようにも書けます。

def contents = md.replaceAll(/(?x) # enable whitespace and comments
  ^\#+   # 先頭からはじまるシャープ
  |      #   または
  \#+$   # 最後のシャープ
/, '').trim()

(?x)はGroovyの正規表現拡張で、Groovyの正規表現リテラルのようなもの*2の中で、改行を入れたり # でコメントを入れたりして見やすく正規表現が書けるようにするものです。
(ただ、今回は '#' 自体をどうにかしたい関係で、 ¥# のようにエスケープが必要になってしまい、あまり見やすいとは言えないのですが。)
上記のどちらでもやりたいことは一緒で、先頭と末尾の連続する '#' を削除したあと、trimしています。

最後に "#" を取り除いた結果をトリムして、Hタグでくるんであげれば変換完了です。

ということで、変換結果はcontentsに入っているはずなので、以下のようにして変換完了です。

def tags = [  "<h${sharpSize}>",
             "</h${sharpSize}>" ]
return "${tags.first()}${contents}${tags.last()}"

おわりに

少し急ぎすぎた感じもありますが、これで期待どおりの結果が得られます。

今回使用したソースはこちら

前の記事:GroovyでMarkdownパーサーを作ろう #3「Headers(見出し)の実装」 - No Programming, No Life

*1:この後すぐ示します。

*2:/ ではじまり / で終わる文字列。

GroovyでMarkdownパーサーを作ろう #3「Headers(見出し)の実装」その1

このシリーズの一覧


Groovy! (挨拶)

どうも、ふも(@)です。

はじめに

さて、今回から具体的な変換処理の実装に入っていくわけですが、いざ実際にコードを書いていこうとすると色々と下準備が必要になってきそうです。ゆっくりやっていきたいと思います。
さしあたって、今回から数回に分けて、Header(見出し)の実装に取り掛かりましょう。

見出し(見出し)記法の確認

Headers - 見出し
MarkdownはSetextとatxという、二つの形式をサポートしています。

blog::2310 » Markdown文法の全訳

ということで、見出しの実装でも二種類あるようですね、今回はひとまず簡単な方のatxという方を実装して行きたいと思います。

Atx形式

Atx形式は見出しの行頭に1つから6つまでの#(ハッシュ記号)を用いる方法です。#(ハッシュ記号)の数が見出しレベルと一致します。例えば

# This is an H1
## This is an H2
###### This is an H6

Markdown独自の機能として、atx形式を「閉じる」ことができます。 この表現が好みであれば使うことができますが、単純に見栄えの問題です。 行末の#(ハッシュ記号)の数は行頭と一致する必要もありません。

# This is an H1 #
## This is an H2 ##
### This is an H3 ######
blog::2310 » Markdown文法の全訳

要約すると

  • 行頭を "#" 1つ〜6つで始めると、それは見出しとなる
  • 例えば、"# abc" なら、 <h1>abc</h1> のようになる
  • 見映えのため、行末には好きなだけ "#" を並べられる(そしてそれらは変換結果としては無視される)

という感じでしょうか。

ロジックを考えてみる

  1. 一行ずつ処理する
    1. 前回までは、ファイルから読み取った内容を一気にHTMLのBODYタグに埋め込んでいましたが、今回実装しようとしているAtx形式のヘッダーは行単位で処理してゆくのが適していそうです。そこでまず、ファイルから一行一行取り出して処理するように変更したとして、その一行一行に対する処理を考えてみましょう。
  2. 一行に対する処理
    1. 行の先頭をまず確認します。具体的には "#" で始まっているかどうか?を確認する必要がありますね。そして#の数は1〜6の範囲です。それ以上でも以下でもないです。
    2. "#" が所定の数で始まっているなら、次のステップへ移行します。まず#の数を数えておきます(1〜6のいずれかになるはずですね)。あとでHタグ数値部分として使うためです。次はその行の先頭と末尾の連続する "#" を取り除きます。最後に "#" を取り除いた結果をトリムして、Hタグでくるんであげれば変換完了です。
    3. "#" が所定の数で始まっていなかったり(例えば7個とかね) そもそも "#" で始まっていなかったりした場合はそのままその行は終了。*1

という流れで良さそうですね。

おわりに

ちょっと長くなりそうなので、実際のソースは次回に引き継ぎます。
それではまた。

次の記事:GroovyでMarkdownパーサーを作ろう #3「Headers(見出し)の実装」その2 - No Programming, No Life
前の記事:GroovyでMarkdownパーサーを作ろう #2「HTML出力部分の作成」 - No Programming, No Life

*1:7個以上の場合は、6個までは見出し<H6>として処理してあげた方がいいのかな?

GroovyでMarkdownパーサーを作ろう #2「HTML出力部分の作成」

このシリーズの一覧


Groovy! (挨拶)

どうも、ふも(@)です。

はじめに

さて、それでは早速コードを書いて行きたいと思います。といってもまだ、Markdownの変換ロジックには入りません。
今回は設定ファイルを読み込んで、その内容をプレーンなHTMLにそのまま埋め込んで出力させるというところまで作って行きたいと思います。

作成するスクリプト

スクリプト名は前回決めた通り、fumomarkdowngにしようと思うので、

fumomarkdowng.groovy

になります。
そして、マークダウン形式で内容が書かれたファイルは、

hoge.md

にしてみましょう。

外部ファイルの内容読み込み

何はともあれ、hoge.mdの内容をまず読み取るところから。
Groovyでは、外部ファイルの読み込みはFile#text*1を利用して行えます。

new File('path to file').text

コマンドライン引数はargsという暗黙変数にString[]型として格納されます。第一引数の文字列の取得は

args[0]

のようにします。ということで、外部ファイルの内容を文字列で取得し、contentsという名前の変数に格納しておくことにします。

// 第一引数の内容を読み込む
def contents = new File(args[0]).text

HTMLを出力

で、次はHTMLの出力部分。ひとまず標準出力への出力でいいかな、ファイルへ出力する場合はリダイレクトすればいいし。
Groovyでは標準出力への出力はprintprintlnprintfを使います。
複数行の文字列は ''' か """ で囲みます*2*3。今回は変数展開を使いたいので、""" の方を使いましょう。

// 読み込んだ内容をHTMLのテンプレートに流し込む
println """<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>fumomarkdowng</title>
</head>
<body>
${contents}
</body>
</html>"""

まぁ、こんな感じでしょうか。タグの中身は${contents}なので、ひとまず読み込んだファイルの中身をそのまま入れ込む感じです。

実行してみる

$ groovy fumomarkdowng.groovy hoge.md
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>fumomarkdowng</title>
</head>
<body>
# level1
## level2
### level3
</body>
</html>
$

うまく行きました、予想通りです。
あ、ちなみに、hoge.mdの内容はお察しの通り

# level1
## level2
### level3

です。まだ本題であるMarkdownの変換処理を何も書いていないのでそのまま出力されているわけです。

おわりに

さて、次回からは変換ロジックを書いて行きたいと思います。まずは簡単な見出しあたりからかなぁ。ではまた!

今回使用したソースはこちら

次の記事:GroovyでMarkdownパーサーを作ろう #3「Headers(見出し)の実装」 - No Programming, No Life
前の記事:GroovyでMarkdownパーサーを作ろう #1「名前決め」 - No Programming, No Life

*1:#getTextの省略系

*2:他の言語だとヒアドキュメントとか呼ばれてるやつです。今回みたいにテンプレートとして使う場合に改行込みに出来るので便利です。

*3:Groovyでは複数行の文字列を扱う記法がまだあります。ドルスラッシュ文字列とか。詳しくは参考記事のv1.8の新機能あたりをご覧下さい

GroovyでMarkdownパーサーを作ろう #1「名前決め」

このシリーズの一覧


Groovy! (挨拶)

どうも、ふも(@)です。

はじめに

最近がっつりとブログを書いている時間がないので、先にやりたいことを決めて、ちょっとずつその軌跡を記録していく感じで記事にしていこうと思います。だいたい一つの記事が3分以内で読める程度の量になるといいかなと思っています。*1

ということで早速始めたいを思います。

作るもの

さて、タイトルにもある通り、今回はMarkdownパーサーを作ってみたいと思います。
Markdownパーサー自体は巷に沢山あふれていると思いますが、あえて自分で書くことで得られるものも多いんじゃないかなぁと思いまして、やることに決めました。

Markdownとは何か

Markdown は、文書を記述するための軽量マークアップ言語のひとつである。「書きやすくて読みやすいプレーンテキストとして記述した文書を、妥当なXHTML(もしくはHTML)文書へと変換できるフォーマット」として、ジョン・グル―バー(John Gruber)により作成された。アーロン・スワーツ(Aaron Swartz)も大きな貢献をしている。[1] Markdownの記法の多くは、電子メールにおいてプレーンテキストを装飾する際の慣習から着想を得ている。

Markdown - Wikipedia

ジョンとアーロン、GJ!
要は、「Markdown記法で書いて -> 変換して(今回作るのはここ) -> 妥当なHTML完成」となるわけですね。

名前決め

さて、今回は第1回ということで、まだコードは書きません。具体的な内容は次回以降にしましょう。
まずは「名前重要*2ということで、名前を決めたいと思います。

プログラマが知るべき97のこと

プログラマが知るべき97のこと

練習用に自分だけが使う想定で書くということで、自分のIDの「頭文字+技術名+言語の頭文字」というルールを安易に摘要したいと思います。よって、

fumomarkdowng

に決定! 使い方は

$ groovy fumomarkdowng.groovy hoge.md

とすると、標準出力にベベベっと結果が出力される感じで。

おわりに

さて、まだ何もしていませんが、今回はこれで終了です。次回からはちょっとずつ作って行きたいと思います。


次の記事:GroovyでMarkdownパーサーを作ろう #2「HTML出力部分の作成」 - No Programming, No Life

*1: ドットインストールさん(http://dotinstall.com/)みたいに短く簡潔にできたらいいな。

*2:プログラマが知るべき97のこと』より「10 名前重要」 まつもとゆきひろ

GroovyでMarkdownパーサーを作ろう

GroovyでMarkdownパーサーを作ってみよう。

Headers(見出し)の実装 (予定)

Blockquotes(引用)の実装 (予定)

Lists(リスト)の実装 (予定)

Code Blocks(ソースコードを表現)の実装 (予定)

Horizontal Rules(罫線)の実装 (予定)

Links(リンク)の実装 (予定)

Emphasis(強調)の実装 (予定)

Code(ソースコードの記述)の実装 (予定)

Images(画像)の実装 (予定)

Automatic Links(自動リンク)の実装 (予定)

Backslash Escapes(バックスラッシュ(円記号)による変換回避)の実装 (予定)

Re:3年間の進歩(10分でコーディング)

Groovy! (挨拶)

はじめに

id:uehajさんが、3年間の進歩 - uehaj's blog で楽しそうなお題を解いていたので私もやってみました。こういう三年越しの計画とかっていいですね。昔自分で解いたお題を今の知識で再トライするとか素敵です。

お題

あなたはこれからトランプを配っていきます。
あなたにはトランプを配る人数、
そしてトランプが渡されます。
今回はとても簡単なので例題で説明します。

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

3
"123123123"

最初の3はプレイヤーの人数を示しています。
"123123123" はトランプの並びを示しています。あなたはこのなかのトランプを
配っていかなければなりません。
この場合、あなたのプログラムは
{"111","222","333"}
を返さなければなりません。
"111"は一番めのプレイヤーが受け取るトランプです。
"222"が2番目のプレイヤーが受け取るトランプです。
"333"が2番目のプレイヤーが受け取るトランプです。
ところが、以下のような場合もあります。
すべてのプレイヤーは同じ数だけのトランプを受け取らなければなりません。
ですので

4
"123123123"

この場合、あなたのプログラムは
{"12","23","31","12"}
を返さなければなりません。
{"123","23","31","12"} は駄目です。
では、以下にもうすこし例をのせます。

例1)
6
"012345012345012345"
Returns: {"000", "111", "222", "333", "444", "555" }

例2)
4
"111122223333"
Returns: {"123", "123", "123", "123" }

例3)
1
"012345012345012345"
Returns: {"012345012345012345" }

例4)
6
"01234"
Returns: {"", "", "", "", "", "" }

例5)
2
""
Returns: {"", "" }

クラス名、などは以下のとおりです。
Class:    Cards
Method:    deal
Parameters:  int, String
Returns:   String
Method signature: String
deal(int numPlayers, String deck)

10分でコーディング|プログラミングに自信があるやつこい!!

回答

v1.8.6から追加された #collate を使いたくて調べてたら10分を軽々オーバーしてしまった罠です。*1

追記 (2013-01-27)

#collate は こっち の第二引数にboolean keepRemainderを指定する方を利用したほうが効率的だったので、コードを修正しました。

おわりに

こういう時間を制限されてるお題は本来、アルゴリズムは極シンプルなものにしておくベキだとは思うんですが、
今回はやっぱり #collate が使いたかったので、時間をかけてでもコーディングを楽しみました。

Enjoy! Coding.

*1:結局30分くらいかかった