Mac mini m1 買った(届いてない)。選択理由

今更ながら、Mac mini m1 買いました。

動機

単純に、今使っている MacBook Pro 2016 (16GB, 2.9GHz デュアルコア)が開発用途での利用に厳しくなってきたからです。IntelliJを起動するのに数分かかったり、キーボードの入力から反映まで1〜2秒のタイムラグは日常でした。このストレスから、プライベートな開発や色々試したりするのが最近億劫になっていたのです。

結構前からそのような状態だったので、次、Mac BookPro m1 14インチが発表されたら買い換えようと思ってましたが、待てませんでした。。

選定理由

PCを選定する上で、選定する際の基準を考えてみます。私がいつも考えるのは下記です。

今回は、後半2つが悩みどころでした。

Mac or Windows or Linux

まずは王道のこの3択ですね。私個人としては現状Mac 1択なので、特に考慮してはいませんが、それだと脳死Mac選んでるみたいで嫌なので、選択理由を書いておきます。

Linuxでない理由

なんだか、スーパーハカーな感じの人は、Mac使うくらいなら、Linux使えよ。金の無駄だって言ってるイメージがあるんですが、メインPCをLinuxにする理由はないですね。単純に開発用途以外で使いづらいです。世の中、WindowsMac向けにクライアントソフトは開発されているので。実際、一時期黒い画面でなんかすごいことしてそうな人のイメージに憧れて、Ubuntuを使おうとインストールしたこともあったんですが、結局Windowsに戻りましたし、Windowsの方がスムーズに開発できました。(ネット情報が多いので、詰まりにくい)。まあ、普通の人にとってLinuxはサーバー用途です。クライアントPC用途ではない。

Windowsでない理由

特にないです。正直、最近はWindowsでもいいかなと思うことがしばしば。私はゲームはPS4でできれば充分派ですが、Windowsでしかできないゲームを見つけて、それがやりたかった時、Winodowsも持っておくべきか。。と思うことはあります。

まだMacに比べて面倒が多いなとは思いますね。WSLで解消されてきてはいますが。

Macである理由

Apple製品で固めてるからですね。エコシステムに組み込まれてると、製品間の連携が楽です。Amazon EchoもHome pod mini に置き換えたい。

ノート or デスクトップ, 価格

今回の争点です。私は今まで個人のPCとしてはノート型しか買ったことがありませんでした。なんとなく持ち運びできた方がいい気がしましたし、キーボード、マウスともにこだわりがないので、一体になってるノート型はコスパがいいという判断です。ノートを選択・購入していた頃はスペックもそこそこで事足りていたので、マシンスペックで困ることもありませんでした。今使っているMBPなんかは、当時としてはオーバースペックくらいの印象。

しかし、最近は性能面と価格がボトルネックになってきました。最近はIntelliJを複数起動することはいつもですし、dockerで複数システムを起動したり、常時コンパイルとテストを走らせていたい(が、重くなりすぎてできない)といった感じになってきました。そうなると、メモリは足りない、マシンは熱暴走気味で動かなくなる。ファンが頑張ってもすぐ熱は溜まると開発できたものではなくなってました。そうなってくると、もっとハイスペックなマシンが欲しくなります。MBPでそれを実現するとすぐに30万を超えてきます。出費としては痛すぎます。※もっと金持ちならよかったのに。。もっと安く抑えるには必然的にノート型は諦めることになります。

ノートを諦めると、PCを持ち運ぶことはできなくなりますが、私にとってこれは特に問題ありませんでした。実際、外で開発をすることはほとんどありませんでしたし、今はオンラインでのコミュニケーションも主流になってきて、ますますオフでPCを持ち運ぶようなことはなくなってきました。そしたらもう、デスクトップ型で問題ありません。

そうして私は、デスクトップ型のMac miniを選択しました。

結論

MBPは高い。

Mac miniは安い。

外にPC持ち運ばない。

Mac miniでいいじゃん?

外で開発しなきゃならない時は、AWS上とかで開発すればいいんじゃないかって気がしてきている。

Scala で Builder Pattern

ScalaJava like なBuilder Patternを書く機会は自分の場合あまり無い(Static Factoryで事足りることが多い)のですが、少し古いJavaのプロダクトのリファクタをした際にBuilder Patternで生成処理を書き直したので、Scalaの場合はどうなるのか見ていこうと思います。

Generalized Typed Constraint の勉強にも一部なるところもあったりします。

JavaにおけるBuilder Pattern

JavaにおけるBuilder Pattern は GoF と Effective Java が有名かなと思います。Builder Patternの説明としては、Effective Javaの方が使い所とかが個人的には分かりやすい気がします。コードとしても、Effective Javaの書き方の方が、コードが散らばらないので好みですね。※Effective Javaはクラス内に `static class` として、`Builder` クラスを定義するが、GoFの方は、`Person` クラスと別に、`PersonBuilder` のようなクラスを作る。

JavaにおけるBuilder Pattern を使いたくなるときと、BuilderPattern以前の書き方

Builder Patternが使いたくなるのは、必須では無いけれど場合に応じて外から値を渡してオブジェクトを生成したい時、オプショナルパラメータが多い時に使いたくなります。オプショナルパラメータが多い時に選択される伝統的なパターンとして、Effective Javaではテレスコーピング・コンストラクタ(Telescoping Constructor)とJavaBeansパターンが紹介されていました。

テレスコーピング・コンストラクタは、単純にコンストラクタを複数用意するパターンです。

6個のパラメータでこれなので、コンストラクタがいくつあっても足りないですし、上から順番にしか対応できてないので、例えば、`carbohydrate` は設定したいけど、他のパラメータはデフォルトパラメータで良いとかには対応できません。

私がこの業界に入った時にはすでにJavaBeansパターンは廃れてましたが、古いプロダクトだとやはりよく使われています。JavaBeans パターンは setter を使って行うパターンです。そもそも不変でないので、バグが入り込みやすいですし、Scalaではまず出会わないですね。でもやっぱり古いJavaのプロダクトだとよく見ます。setterを排除したい時に、Builder Patternに置き換えるのはリファクタとしてはやりやすいなと思います。

Scalaを使っていると、ほぼ直面しない、値がミュータブルな状態なってしまうのがJavaBeansパターンの最悪の欠点ですね。最新のJavaは知らないですが、基本的に値がミュータブルになってるJavaといえど、イミュータブルの方が扱いやすいことには変わりありません。しかし、JavaBeansパターンはテレスコーピングパターンよりも柔軟です。値変えたいもののsetterのみを呼び出せば良いわけなので。

Builder Pattern (Java

テレスコーピングパターンも、JavaBeansパターンもどちらも使いづらい。そこで使われるのがBuilder Patternです。(Effective Java ver.)

呼び出しのところを見ればわかるように、Builderからメソッドチェーンのように生成処理が書けます。

ScalaにおけるBuilder Pattern

Javaの方を見てわかるように、基本的にはオプショナルパラメータはデフォルト値で良くて、時々オプショナルパラメータの値を外部から渡したい。そしてそのようなオプショナルパラメータが多い時にBuilder Patternは活躍します。

case class のデフォルトパラメータを使う

Scalaでデフォルトパラメータを使う場合は、すごく簡単にかけます。

JavaのBuilder Patternとこれで同じことができています。コード量、可読性、段違いですね。

呼び出しも、オプショナルのところは名前付きで渡してあげれば自由に書けます。

Java likeに書いてみる

上のように簡単に書けるのはわかりましたが、Javaっぽく書くとどうなるのかも簡単に見ておきます。

`static class` だった `Builder` がScalaの場合はコンパニオンオブジェクトに移動するくらいですね。Javaと違って面倒なだけで、特に恩恵がないです。

Generalized type constrains

なんならここからが本題。Generalized type constrains を利用してさらにレベルアップさせます。

Scala 系のブログを漁ってたら少し古い下記の記事を見つけて知りました。コップ本にも、多分載って無い内容ですね。なので使い道とか調べるの難しい。。

yuroyoro.hatenablog.com

上記の記事に記載がありますが

Scala2.8から、Predefに<:<とか=:=とかが定義されていて、これなんだろ?とずーっと疑問だった訳ですよ。で、ついったーで質問投げてたらやっと理解できました。

"generalized type constraints"というヤツで、型パラメータに与えられた型が、特定の条件を満たす場合にのみ呼び出せるメソッドを定義できるというものです。しかもコンパイル時に静的にチェックされる!! これはスゴい!!

https://yuroyoro.hatenablog.com/entry/20100914/1284471301

type-safe builder

すこし話は戻って、オブジェクトの生成時に、例えば特定の順序で初期化する必要があったりすることがあります(あるそうです。自分はそういうシーンにはまだ遭遇したことがない)。これまでの Builder Pattern の実装では、実行時にしかそのことを検証できませんでしたが、type-safe builder と呼ばれる実装方法を使うと、コンパイル時に検証できるようになります。

gist.github.com

これで、例えば setFirstName を抜いて build しようとしてもIntelliJ なら静的解析の段階でエラーを出してくれますし、当然コンパイルエラーにもなります。順序を保証したい時とかに便利ですね。

最後に

Java like なBuilder PatternをScalaで使うことは基本的にはそんななく、多くはstatic factoryや、case class のデフォルトパラメータで十分だろうなと思います。type-safe builder に関してなかなかこれが必要な制約下になることはあんまり想像つきませんが、有用かなと思います。

Haskell 勉強メモ9

セクション

中置関数に対して、セクションという機能を使って部分適用をすることができる。

divideByTen :: (Floating a) => a -> a
divideByTen = (/10)

*Main> divideByTen 30
3.0

これは 30 / 10 と同義。

高階関数

関数を受け取り、2回適用する関数を書いてみる。先にScalaで書いてみよう。

def applyTwice[T](f: T => T)(x: T): T = f(f(x))

applyTwice { v: Int => v * 2 }(10) // 40

これを Haskell で書くとこうなる。

applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

*Main> applyTwice (\a -> a * 2) 10
40

`(\a -> a * 2)` は Haskell無名関数

zipWith

標準ライブラリの zipWith

まずはHaskell標準の zipWith の挙動を確認する。

*Main> :t zipWith
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]

zipWith は 2つの引数をとって1つの値を返す関数と、2つのリストを引数に取り、2つのリストの各要素に関数を適用することで、1つに結合する。

*Main> zipWith (\a b -> a + b) [1,2,3] [4,5,6]
[5,7,9]

Scalaには zipWith に相当するものはないが、zip メソッドが collection 系には実装されている

List(1, 2, 3) zip List(2, 3, 4) // res2: List[(Int, Int)] = List((1,2), (2,3), (3,4))

2つのリストを引数にとって、前から順に同じ位置にある要素で構成されたタプルのリストを作って返す。これに map を適用してあげれば、先程の haskellzipWith 相当のことが可能だ。

List(1, 2, 3) zip List(2, 3, 4) map { case (a, b) => a + b } // res2: List[Int] = List(3, 5, 7)

zipWith を実装してみる。※ 途中で、gist を使えば haskell もsyntax highlight いけることに気づいたので少し行数があるやつは試しに。

Scalaで同じようなことを書くとこんな感じ。最初の zipmap を連携させる方が読みやすいし書きやすい。

flip

普段Scalaを使っていて、さっきの zipWith なんかは似たようなもの、似たようなことを Scala でもやったことがあるが、これはhaskell本で初めてみる。

flip は関数を引数にとり、引数が入れ替わった関数を返す。※正直、何が嬉しいのか全く分からない。

Main> :t flip
flip :: (a -> b -> c) -> b -> a -> c
*Main> zip [1,2,3,4,5] "hello"
[(1,'h'),(2,'e'),(3,'l'),(4,'l'),(5,'o')]
*Main> flip zip [1,2,3,4,5] "hello"
[('h',1),('e',2),('l',3),('l',4),('o',5)]

zip は1つ目の配列と2つ目の配列の同じ位置にある要素で作ったタプルのリストを返す関数だ。1つ目の通常の zip では左の配列にあったものがタプルの左に、右の配列にあったものがタプルの右側にあるのが分かる。

flip を適用した zip はそれが逆になっている。:t で見てみるとよく分かるが、引数が入れ替わっていることが分かる。

*Main> :t zip
zip :: [a] -> [b] -> [(a, b)]
*Main> :t flip zip
flip zip :: [b] -> [a] -> [(a, b)]

自分で実装すると下記のようになる。

木勉強メモ

木構造の取扱はプログラマなら基本なのかもだけれど、アルゴリズムとデータ構造をちゃんと勉強したことなかったので、必要ベースで勉強していく。

用語

用語説明
根(root)親を持たないノード。1つの木に対して1つ根。
辺(edge)親から子へのリンク。
葉(leaf)子を持たないノード。
兄弟(siblings)親が同じ子たち。
祖先(ancestor)p → q への経路がある場合、pをqの祖先と呼ぶ。
子孫(descendant)p → q への経路がある場合、qをpの子孫と呼ぶ。
レベル(level)ある木の深さの全ノードの集合を木のレベルと呼ぶ。
(ノードの)深さ(depth)根からノードまでの経路の長さ。
(ノードの)高さ(height)ノードから、一番深いノードまでの経路の長さ
木の高さ(Height of the tree)
木の深さ(depth of the tree)
木の全ノードの最大の高さ。
ノードのサイズ自分自身を含めた子孫の数
木に関する用語

二分木

どのノードも、子を、1または2しか持たない時、二分木(binary tree)と呼ぶ。

二分木の構造はデータフィールドの脇に左右の子を指すリンクを持たせる形。参考資料に合わせてCだと下記のようになる。

struct BinaryTreeNode {
    int data;
    struct BinaryTreeNode *left;
    struct BinaryTreeNode *right;
};

Scalaの場合は、leftright プロパティとして自身と同じ型の値を持つと考えて。

case class BinaryTreeNode(
    data: Int,
    left: Option[BinaryTreeNode],
    right: Option[BinaryTreeNode]
)

二分木の演算

二分木には基本演算と補助的演算が存在する

基本演算

  • 木に要素を挿入
  • 木から要素を削除
  • 要素を探索
  • 木を横断

補助的演算

  • 木のサイズを求める
  • 木の高さを求める
  • 最大和をもつレベルを求める
  • あるノード対の最小共通祖先(LCA)を求める。など。

二分木を横断する

前順序横断

  1. 根を訪問
  2. 前順序で左部分木を横断
  3. 前順序で右部分木を横断

Cでの実装(Cちゃんと書いたことないから変かもしれない)

#include &lt;stdio.h&gt;

struct BinaryTreeNode {
    int data;
    struct BinaryTreeNode *left;
    struct BinaryTreeNode *right;
};

void PreOrder(struct BinaryTreeNode *root) {
    if (root) {
        printf("%d", root-&gt;data);
        PreOrder(root-&gt;left);
        PreOrder(root-&gt;right);
    }
}

struct BinaryTreeNode makeBinaryTreeNode(int data, struct BinaryTreeNode *left, struct BinaryTreeNode *right) {
    struct BinaryTreeNode temp;
    temp.data = data;
    temp.left = left;
    temp.right = right;
    return temp;
}

int main() {

    struct BinaryTreeNode node7 = makeBinaryTreeNode(7, NULL, NULL);
    struct BinaryTreeNode node6 = makeBinaryTreeNode(6, NULL, NULL);
    struct BinaryTreeNode node5 = makeBinaryTreeNode(5, NULL, NULL);
    struct BinaryTreeNode node4 = makeBinaryTreeNode(4, NULL, NULL);
    struct BinaryTreeNode node3 = makeBinaryTreeNode(3, &amp;node6, &amp;node7);
    struct BinaryTreeNode node2 = makeBinaryTreeNode(2, &amp;node4, &amp;node5);
    struct BinaryTreeNode node1 = makeBinaryTreeNode(1, &amp;node2, &amp;node3);

    PreOrder(&amp;node1);

    return 0;
}

// 実行結果
// 1245367</pre>

Scalaで同じことをする。

def preOrder(root: BinaryTreeNode): Unit = {
  print(root.data)
  root.left.foreach(preOrder)
  root.right.foreach(preOrder)
}

val node7 = BinaryTreeNode(7, None, None)
val node6 = BinaryTreeNode(6, None, None)
val node5 = BinaryTreeNode(5, None, None)
val node4 = BinaryTreeNode(4, None, None)
val node3 = BinaryTreeNode(3, Some(node6), Some(node7))
val node2 = BinaryTreeNode(2, Some(node4), Some(node5))
val node1 = BinaryTreeNode(1, Some(node2), Some(node3))

preOrder(node1) // 1245367

もう少し、Scalaらしく書いて見る。Scalaで二分木を表現するならば、sealed trait を用いて

sealed trait Tree[+A]
case class Leaf[A](value: A) extends Tree[A]
case class Branch[A](value: A, left: Tree[A], right: Tree[A]) extends Tree[A]</pre>

のように表現できる。trait Tree のメソッドとして preOrder を定義すると

sealed trait Tree[+A] {
  def preOrder: Unit = this match {
    case Leaf(value) =&gt; print(value)
    case Branch(value, left, right) =&gt;
      print(value)
      left.preOrder
      right.preOrder
  }
}

val leaf7 = Leaf(7)
val leaf6 = Leaf(6)
val leaf5 = Leaf(5)
val leaf4 = Leaf(4)
val branch3 = Branch(3, leaf6, leaf7)
val branch2 = Branch(2, leaf4, leaf5)
val branch1 = Branch(1, branch2, branch3)

branch1.preOrder // 1245367
branch2.preOrder // 245
branch3.preOrder // 367</pre>

全要素を走査できるならば、map が定義できそう。

sealed trait Tree[+A] {
  ...

  def map[B](f: A =&gt; B): Tree[B] = this match {
    case Leaf(value) =&gt; Leaf(f(value))
    case Branch(value, left, right) =&gt; Branch(f(value), left.map(f), right.map(f))
  }
}

branch1.map(v =&gt; v * 10).preOrder // 10204050306070</pre>

foreach を定義してあげれば、preOrder 不要。

sealed trait Tree[+A] {
  ...

  def foreach[U](f: A =&gt; U): Unit = {
    this match {
      case Leaf(value) =&gt; f(value)
      case Branch(value, left, right) =&gt;
        f(value)
        left.foreach(f)
        right.foreach(f)
    }
  }
}

二分木で要素を探索する。C言語版。要素が存在したら、1を返し、無ければ0を返す。

int FindInBinaryTreeUsingRecursion(struct BinaryTreeNode *root, int data) {
    int temp;
    if (root == NULL) return 0;
    else {
        if (data == root-&gt;data)
            return 1;
        else {
            temp = FindInBinaryTreeUsingRecursion(root-&gt;left, data);
            if (temp != 0) return temp;
            else return (FindInBinaryTreeUsingRecursion(root-&gt;right, data));
        }
    }
}

これを参考にScala版。引数に A を引数にとって Boolean を返す関数をとり、Option[A] を返す。左から順に調べる。

def find(f: A =&gt; Boolean): Option[A] = {
    this match {
      case Leaf(value) =&gt; if (f(value)) Some(value) else None
      case Branch(value, left, right) =&gt;
        if (f(value)) Some(value)
        else {
          val temp = left.find(f)
          if (temp.isDefined) temp else right.find(f)
        }
    }
  }

参考

Haskell 勉強メモ8

高階関数

Haskell は関数を引数にとったり、返り値として関数を返すことができる。このような関数を高階関数と呼ぶ。Scalaにもある。

カリー化関数

Haskellの全ての関数は、引数を1つだけ取る。今まで、複数引数を取っているように見えたものは全てカリー化された関数だ。

Prelude> :t max
max :: Ord a => a -> a -> a

Prelude> max 4 5
5

max 関数は2つの引数を取り、大きい方を返す関数のように見えるが実際はカリー化された関数。まず、max 4 が適用され、max 4 は1つの値を引数にとる別の関数を返す。返ってきた関数に対して、5が適用されるという流れだ。

Prelude> let max' = max 4
Prelude> :t max'
max' :: (Ord a, Num a) => a -> a
Prelude> 

Scalaもカリー化できる。

def max(a: Int)(b: Int): Int = if (a >= b) a else b

max(4)(5) val res0: Int = 5
val max2 = max(4)(_) // val max2: Int => Int = <function>

Scalaの場合はHaskellほど素直ではなく、適用しない部分には _ を入れる必要があるようだ。また、関数部分適用はカリー化されていなくてもできて

def multiThree(x: Int, y: Int, z: Int): Int = x * y * z

val m2 = multiThree(3, _, _) // val m2: (Int, Int) => Int = <function>
m2(2, 4) // val res2: Int = 24

のように書くこともできる。

今度は、Int を引数にとって100と比較する関数を作る。これは単純に書くとこうだが

compareWithHundred :: Int -> Ordering
compareWithHundred x = compare 100 x

*Main> compareWithHundred 99
GT

実際には下記のように書くことができる

compareWithHundred :: Int -> Ordering
compareWithHundred = compare 100

compare 100 が引数を一つとって、Ordering を返す関数であるからだ。

Apache POI 調査

Excelを操作したいみたいな状況は割とよくある。Pythonとかのスクリプト言語だと結構対処法をが見つかるけど、Scalaだと、Java資産のApache POIくらいしかない上に情報もそんなにない(poi.scala のようなラッパーライブラリもあるようだけど、プロダクションで使うには早計感ある)。Apache POIの使い方を調べて、人並みに使えるようになろうと思う。参考記載のJavaでのApache POIの使い方を元に、Scalaで書いて感じを掴む。

準備

必要なライブラリを build.sbt に追記する。

libraryDependencies ++= Seq(
      "org.apache.poi" % "poi"       % "5.0.0",
      "org.apache.poi" % "poi-ooxml" % "5.0.0"
)

Excelファイルの作成

build.sbt の準備ができたら、Excelファイルを作成していく。

XLS ファイルの作成

今だと使う機会は少ないかもしれないが一応。XLSファイルを扱うのは、上記のライブラリのうち poi の方だ。

package dev.tchiba.researchApachePoi

import org.apache.poi.hssf.usermodel.HSSFWorkbook

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

object XLS {
  def main(args: Array[String]): Unit = {
    val fileName = "demo.xls"
    val workbook = new HSSFWorkbook() // XLSにはHSSFWorkbookを使う
    workbook.createSheet("Tab1")
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }
}

XLSX ファイルの作成

XLSXファイルを作成するには、build.sbt 記載のライブラリのうち、後者の poi-ooxml が必要だ。処理はほとんど同じで、生成するインスタンスのクラスのみが異なる。

package dev.tchiba.researchApachePoi

import org.apache.poi.xssf.usermodel.XSSFWorkbook

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

object XLSX {
  def main(args: Array[String]): Unit = {
    val fileName = "demo.xlsx"
    val workbook = new XSSFWorkbook() // XLSX を生成する場合は、XSSFWorkbookを使う
    workbook.createSheet("Tab1")
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }
}

Excelファイルにデータを書き込む

空のExcelファイルが作れても一ミリも嬉しくないので、データを書き込んでいこう。サンプルに下記のようなデータモデルを用意する。

case class Person(
    firstName: String,
    lastName: String,
    birthDay: LocalDate,
    email: String,
    phoneNumber: String,
    married: Boolean
)

これをExcelに書き込むクラスを用意しよう。参考がJavaなので、あんまり Scala っぽくないけど、下手に外れると詰まるから諦める。

package dev.tchiba.researchApachePoi

import models.Person

import org.apache.poi.ss.usermodel.{Sheet, Workbook}
import org.apache.poi.xssf.usermodel.{XSSFSheet, XSSFWorkbook}

import java.io.FileOutputStream
import scala.util.{Failure, Success, Try}

class PersonExcelWriter {

  def write(fileName: String, personList: List[Person]): Unit = {
    val workbook = prepareWorkbook(personList)
    Try(new FileOutputStream(fileName)) match {
      case Failure(exception) => exception.printStackTrace()
      case Success(outputStream) =>
        workbook.write(outputStream)
        println(s"An Excel file $fileName has been created")
        workbook.close()
    }
  }

  private def prepareWorkbook(personList: List[Person]): Workbook = {
    val workbook: XSSFWorkbook = new XSSFWorkbook()
    val personSheet: XSSFSheet = workbook.createSheet("Persons")
    prepareHeader(personSheet)
    prepareTable(personSheet, personList)
    workbook
  }

  private def prepareHeader(personSheet: Sheet): Unit = {
    // ヘッダー行を作成する
    val headerRow = personSheet.createRow(0)

    // セルを作成する
    val firstNameHeaderCell = headerRow.createCell(0)
    val lastNameHeaderCell  = headerRow.createCell(1)
    val birthdayHeaderCell  = headerRow.createCell(2)
    val emailHeaderCell     = headerRow.createCell(3)
    val phoneNumberCell     = headerRow.createCell(4)
    val marriedHeaderCell   = headerRow.createCell(5)

    // セルに値をセットする
    firstNameHeaderCell.setCellValue("First name")
    lastNameHeaderCell.setCellValue("Last name")
    birthdayHeaderCell.setCellValue("Birthday")
    emailHeaderCell.setCellValue("Email")
    phoneNumberCell.setCellValue("Phone number")
    marriedHeaderCell.setCellValue("Married")
  }

  private def prepareTable(personSheet: Sheet, personList: List[Person]): Unit = {
    personList.zipWithIndex.foreach { case (person, index) =>
      // 行を作成する
      val row = personSheet.createRow(index + 1)

      // セルを作成する
      val firstNameCell   = row.createCell(0)
      val lastNameCell    = row.createCell(1)
      val birthdayCell    = row.createCell(2)
      val emailCell       = row.createCell(3)
      val phoneNumberCell = row.createCell(4)
      val marriedCell     = row.createCell(5)

      // セルに値をセットする
      firstNameCell.setCellValue(person.firstName)
      lastNameCell.setCellValue(person.lastName)
      birthdayCell.setCellValue(person.birthDay.toString)
      emailCell.setCellValue(person.email)
      phoneNumberCell.setCellValue(person.phoneNumber)
      marriedCell.setCellValue(person.married)
    }
  }
}

手順としては

  1. ワークブックを用意する
  2. シートを用意する
  3. カラムにデータを埋めていく
  4. 最後に、ワークブックをファイルに保存する

となる。

これを使う処理を書こう。

package dev.tchiba.researchApachePoi

import models.Person

import java.time.LocalDate

object Main {
  def main(args: Array[String]): Unit = {
    val person1 = Person(
      firstName = "太郎",
      lastName = "田中",
      birthDay = LocalDate.of(1990, 2, 2),
      email = "taro.tanaka@xxx.com",
      phoneNumber = "090XXXXXXX",
      married = true
    )

    val person2 = Person(
      firstName = "花子",
      lastName = "佐藤",
      birthDay = LocalDate.of(1992, 9, 9),
      email = "hanako.sato@xxx.com",
      phoneNumber = "090YYYYYYY",
      married = false
    )

    val personList = List(person1, person2)

    val writer = new PersonExcelWriter()
    writer.write("person.xlsx", personList)
  }
}

実行して出力されたファイルを開いてみると、(私のPCにはExcel入っていないので、Numbersを利用)、想定通りに値が入っていることが分かる。

参考

Apache POI に関する体系的な情報が探しづらいなか、下記の本がKindle Unlimited にあったのが救いだった。

Haskell勉強メモ 7

クイックソート

アルゴリズムの定番クイックソート

といっても、私はアルゴリズムをちゃんと勉強したことないので名前しか知らないのだけれど。。

これを関数型ではどうやって解くのかを見ていく。

が、先に手続き型ではどうやるのか。アルゴリズム本を見ながら慣れてるScalaで手続き的に書いてみる。

クイックソートとは

配列を分割して、それぞれ再帰的に解いてまとめる分割統治法にのっとったアルゴリズム

適当にpivotを選び出し、配列全体を「pivot未満のグループ」と「pivot以上のグループ」に分割してそれぞれ再帰的に解く。

Scalaで手続き的に書く場合(参考

def quickSort(xs: Array[Int]): Array[Int] = {
  def swap(i: Int, j: Int): Unit = {
    val t = xs(i)
    xs(i) = xs(j)
    xs(j) = t
  }
  def sort(left: Int, right: Int): Unit = {
    val pivot = xs((left + right) / 2)
    var i     = left
    var j     = right

    while (i <= j) {
      while (xs(i) < pivot) i += 1
      while (xs(j) > pivot) j -= 1
      if (i <= j) {
        swap(i, j)
        i += 1
        j -= 1
      }
    }
    if (left < j) sort(left, j)
    if (j < right) sort(i, right)
  }
  sort(0, xs.length - 1)
  xs
}

写経してみたが、手続き的な実装はいまいち分からん。。

Haskell

quicksort :: (Ord a) => [a] -> [a]
quicksort [] = []
quicksort (x : xs) =
  let smallerOrEqual = [a | a <- xs, a <= x]
      larger = [a | a <- xs, a > x]
   in quicksort smallerOrEqual ++ [x] ++ quicksort larger

*Main> quicksort [10,8,1,3,4,12,4,1,3,9,8]
[1,1,3,3,4,4,8,8,9,10,12]

なんと分かりやすいことか。。ピボット a より小さいものと大きいものに分けてそれを再帰する。とても分かりやすい。

これをScalaで書いてみる。

def quickSort(list: List[Int]): List[Int] = {
  list match {
    case Nil => Nil
    case ::(x, xs) =>
      val smallerOrEqual = for (a <- xs; if a <= x) yield a
      val larger = for (a <- xs; if a > x) yield a
      quickSort(smallerOrEqual) ++ List(x) ++ quickSort(larger)
  }
}

ロジックがそのまま記述されており、手続き的な実装と比べて明らかに読みやすい。