社内se × プログラマ × ビッグデータ

プログラミングなどITに興味があります。

python クロージャ

忘れた頃にまたやってくるクロージャ

def charge(price):
    def calc(num):
        return price * num
    return calc

child = charge(1000)
adult = charge(1500)

price1 = child(2)
price2 = adult(2)
print(price1)
print(price2)

クロージャでは関数の中に関数を定義する。
上記の例では、charge() の中に、calc() が定義されている。
charge() が呼び出されると、その戻り値として、calc() が返却される。
calc() は関数なので、戻り値は関数オブジェクトになる。

calc() は price * num の結果を返す関数。
charge() が呼び出された時点で、price が引数で与えられている。
そのため、戻り値は price に値が入った calc() 関数になる。

例えば、charge(1000) なら、1000 * num を返却する calc() 関数が戻り値になる。
それが、child に代入されている。

charge(1500) なら、1500 * num を返却する calc() 関数が戻り値になる。
それが、adult に代入されている。

child(2) を呼び出すと、それは calc(2) (※ただし price は 1000) を呼び出すのと同じである。
1000 * num を返すので、2000 が返ることになる。

adult(2) を呼び出すと、それは calc(2) (※ただし price は 1500) を呼び出すのと同じである。
1500 * num を返すので、3000 が返ることになる。

なぜ、sudo rm でワイルドカード指定できないのか

sudo rm でワイルドカード指定したのに動かない。

$ sudo rm /var/log/elasticsearch/elasticsearch.*.log
rm: cannot remove `/var/log/elasticsearch/elasticsearch*log': No such file or directory

ワイルドカード拡張は、rmではなくシェルによって行われる。
また、シェルにはsudo権限はなく、rmのみにある。
したがって、シェルには/var/log/elasticsearch/を読み取る権限がないため
展開は行われず、rmはファイル(ワイルドカードではない)/var/log/elasticsearch/*(存在しない)を削除しようとする。

これを回避するには、rmを実行するsudo権限を持つシェルが必要となる。

sudo sh -c 'rm /var/log/elasticsearch/elasticsearch*.log'

JUnit DataPoint によるテストパラメータ定義

例えば、文字列を引数に取るメソッドをテストするにあたり、色んな文字列でテストしたい場合。
テストする文字列ごとにテストケースを作成するのは大変。
DataPoint を使うことで、それらのパラメータを配列で管理することができ、文字列一つずつを一つのテストケース内で自動的に適用しながらテストすることが可能となる。
※そのメソッドの期待値が引数に依存せずに同一であれば評価しやすいが、もし異なる場合、それぞれの引数に対する期待値を別途定義が必要

@RunWith(Theories.class)
public class DataPointSampleTest2 {
	@DataPoints	// for multi test data
	public static String[] DATA_PARAM = { "JAPAN", "JAPANESE" };
	
	private static int index = 0;
	
	@Theory
	public void test(String param) {
		System.out.println("test param=" + param);
		assertThat(param, is(DATA_PARAM[index++]));
	}
}

このテストでは、引数param として、”JAPAN” と "JAPANESE" が順番にテストされる。
コンソールでの標準出力。

test param=JAPAN
test param=JAPANESE

Python CUIで迷路生成(3)

スタートとゴールの場所を指定するところまで。
set_start_goal
ランダムに座標を指定し、そこが通路ならスタート”S”、またはゴール”G” をそこに設置する。

import sys
import random

class Maze():
    PATH = 0
    WALL = 1
    START = 2
    GOAL = 3

    def __init__(self, height, width):
        self.maze = []
        self.width = width
        self.height = height
        if (self.width % 2) == 0:
            self.width += 1
        if (self.height % 2) == 0:
            self.height += 1
    
    def set_outer_wall(self):
        for y in range(0, self.height):
            row = []
            for x in range(0, self.width):
                if (x == 0 or y == 0 or x == self.width - 1 or y == self.height - 1):
                    cell = self.WALL
                else:
                    cell = self.PATH
                row.append(cell)
            self.maze.append(row)

    def set_inner_wall(self):
        for x in range(2, self.width-1, 2):
            for y in range(2, self.height-1, 2):
                self.maze[y][x] = self.WALL
                while True:
                    wall_x = x
                    wall_y = y
                    if y == 2:
                        direction = random.randrange(0, 4)
                    else:
                        direction = random.randrange(0, 3)

                    if direction == 0:
                        wall_x += 1
                    elif direction == 1:
                        wall_y += 1
                    elif direction == 2:
                        wall_x -= 1
                    else:
                        wall_y -= 1
                    if self.maze[wall_y][wall_x] != self.WALL:
                        self.maze[wall_y][wall_x] = self.WALL
                        break

    def set_start_goal(self):
        # set start
        while True:
            s_x = random.randrange(1, self.width-1)
            s_y = random.randrange(1, self.height-1)
            if self.maze[s_y][s_x] == self.PATH:
                self.maze[s_y][s_x] = self.START
                break
        # set goal
        while True:
            g_x = random.randrange(1, self.width-1)
            g_y = random.randrange(1, self.height-1)
            if self.maze[g_y][g_x] == self.PATH:
                self.maze[g_y][g_x] = self.GOAL
                break

    def print_maze(self):
        for row in self.maze:
            for cell in row:
                if cell == self.PATH:
                    print(' ', end='')
                elif cell == self.WALL:
                    print('#', end='')
                elif cell == self.START:
                    print('S', end='')
                elif cell == self.GOAL:
                    print('G', end='')
            print()

args = sys.argv
maze = Maze(int(args[1]), int(args[2]))
maze.set_outer_wall()
maze.set_inner_wall()
maze.set_start_goal()
maze.print_maze()

出力例

$ python create_maze.py 10 20
#####################
# #       #     #   #
# ### ##### # ### ###
#           #       #
# ##### # # ##### ###
#   #   # #     #  S#
# ##### ####### #####
# #         #       #
# # ##### ### # # ###
# #  G#   #   # #   #
#####################

JUnit Enclosed によるテストケースのグループ化

既に存在しているテストクラスに新しいテストケースを追加するとき、どの位置に入れようか迷うときがあります。
出来れば、後で見たときに分かりやすい位置に入れたいですが、そもそも整理されていなければそれも難しいです。そこで Enclosedを使うことで、テストケースのグループ化が可能です。

@RunWith(Enclosed.class)
public class RunWithTest {
	public static class TestForNumber {
	    @Test
            public void additionTest() {
                int actual = 1 + 2;
                assertThat(actual, is(3)); 
            }
		
	    @Test
	    public void subtractionTest() {
	        int actual = 1 - 2;
                assertThat(actual, is(-1)); 
	    }
	}
	
	public static class TestForCharacter {
	    @Test
            public void additionTest() {
                String actual = "a" + "b";
                assertThat(actual, is("ab")); 
            }
		
	    @Test
	    public void replaceTest() {
	        String actual = "a".replace("a", "b");
                assertThat(actual, is("b")); 
	    }
	}
}

どのような観点でグループ化するか迷いところですが、いくつかヒントはありそうです。

  • 共通のデータで分ける
  • 共通の状態で分ける
  • コンストラクタのテストを分ける など

テスト結果も以下のようにグループ化されて表示されます。
f:id:blueskyarea:20190717233754p:plain

Python CUIで迷路生成(2)

内壁を生成するところまで。
棒倒し法により生成するが、その手順は
1. 迷路全体を構成する2次元配列を、幅高さ5以上の奇数で生成する
2. 迷路の外周を壁とし、それ以外を通路とする
3. 外周の内側に基準となる壁(棒)を1セルおき(x, y ともに偶数の座標)に配置する
4. 内側の壁(棒)を走査し、ランダムな方向に倒して壁とする
※ただし以下に当てはまる方向以外に倒す

  • 1行目の内側の壁以外では上方向に倒してはいけない
  • すでに棒が倒され壁になっている場合、その方向には倒してはいけない
import sys
import random

class Maze():
    PATH = 0
    WALL = 1

    def __init__(self, height, width):
        self.maze = []
        self.width = width
        self.height = height
        if (self.width % 2) == 0:
            self.width += 1
        if (self.height % 2) == 0:
            self.height += 1
    
    def set_outer_wall(self):
        for y in range(0, self.height):
            row = []
            for x in range(0, self.width):
                if (x == 0 or y == 0 or x == self.width - 1 or y == self.height - 1):
                    cell = self.WALL
                else:
                    cell = self.PATH
                row.append(cell)
            self.maze.append(row)
        return self.maze

    def set_inner_wall(self):
        for x in range(2, self.width-1, 2):
            for y in range(2, self.height-1, 2):
                self.maze[y][x] = self.WALL
                while True:
                    wall_x = x
                    wall_y = y
                    if y == 2:
                        direction = random.randrange(0, 4)
                    else:
                        direction = random.randrange(0, 3)

                    if direction == 0:
                        wall_x += 1
                    elif direction == 1:
                        wall_y += 1
                    elif direction == 2:
                        wall_x -= 1
                    else:
                        wall_y -= 1
                    if self.maze[wall_y][wall_x] != self.WALL:
                        self.maze[wall_y][wall_x] = self.WALL
                        break
        return self.maze

    def print_maze(self):
        for row in self.maze:
            for cell in row:
                if cell == self.PATH:
                    print(' ', end='')
                elif cell == self.WALL:
                    print('#', end='')
            print()

args = sys.argv
maze = Maze(int(args[1]), int(args[2]))
maze.set_outer_wall()
maze.set_inner_wall()
maze.print_maze()

出力例

$ python create_maze.py 10 20
#####################
# #   #         # # #
# # # # # # ### # # #
#   #   # # #       #
# ##### ### # ##### #
#   #     # #     # #
# ##### ######### # #
#   #     #       # #
### # ### ##### ### #
#   #   #     #   # #
#####################

Java 例外(Exception) テスト

例外(Exception)のテストの書き方でよく知られているもので、Ruleアノテーションを用いた ExpectedException を使う方法があります。
例外のメッセージまで評価してくれるのがいいですね。

public class RuleTest {
	@Rule
	public ExpectedException expectedException = ExpectedException.none();

	@Test
	public void testThrowException() {
		expectedException.expect(RuntimeException.class);
		expectedException.expectMessage("Rule test");
		throw new RuntimeException("Rule test");
	}
}