7.15.2013

Python: Random Partition of Integer

Python: ランダムな整数分割の実装

目的

  • 非負整数 n を 和を保ったまま m 個のバケットにランダムに分割したい
  • そのとき、各バケットの内容が上限 k を超えないようにする。
  • 制約
    • 0 <= n < 2^31
    • 1 <= m < 2^19
    • 0 <= k < 2^31
    • n <= m * k
  • 言い換えれば、n, m, k が与えられた時に、以下を満たす数値のリスト x[i] (0 <= i < m) を
    ランダムに求めたい。
    • ∀i (0 <= x[i] <= k)
    • Σ x[i] = n
 

アイデア

二分木と再帰を使ってリストを作る。

動作イメージは以下のとおり。

 Blog 20130715

m = 1 ならばそれは葉なので計算終了。(値は n)
それ以外は、ほぼ半数になるように m を左右に振り分ける。

振り分ける個数を左から順に m1, m2、振り分ける数値を n1, n2 とした場合、制約から

  • a) n1 + n2 = n
  • b) 0 <= n1
  • c) 0 <= n2
  • d) n1 <= m1 * k
  • e) n2 <= m2 * k
を満たす必要がある。
 
e) に a) の n2 = n - n1 を代入すれば、n1 >= n - m2 * k という不等式が導けるので
n1 は以下の制約を持つことになる。 
  • 0 <= n1 <= n    a), b), c) から
  • n - m2 * k <= n1 <= m1 * k    a), d), e) から

この範囲内でランダムに n1 を選択すれば、再帰的に木を構築することができる。

 

なお、木構造ではなく線形に順次リストを作っていく方法も可能だが
再帰にした場合にスタック溢れが起きてしまうこと、また大きな値が前方に偏ってしまうことから採用を見送った。

  

コード

import random


def random_partition(size, total, upper):
    assert(1 <= size <= 0x0007ffff)
    assert(0 <= total <= 0x7fffffff)
    assert(0 <= upper <= 0x7fffffff)
    assert(total <= size * upper)

    if size == 1:
        return [total]

    s1 = size / 2
    s2 = size - s1
    t1 = random.randint(max(0, total - s2 * upper), min(total, s1 * upper))
    t2 = total - t1

    return random_partition(s1, t1, upper) + random_partition(s2, t2, upper)
    • 実行例
print(random_partition(1, 0, 0))
print(random_partition(1, 1, 1))
print(random_partition(10, 1000, 100))
print(random_partition(10, 1, 1))
print(random_partition(7, 10, 10000))
print(random_partition(0x0007ffff, 0x7fffffff, 0x7fffffff)[:10])
print(random_partition(0x0007ffff, 0x7fffffff, 0x7fffffff)[-10:])
print(random_partition(0x0007ffff, 0, 0)[:10])
print(random_partition(10000, 1000000000, 100000)[:10])
print(random_partition(1 << 18, 0, 1 << 12)[:10])
print(random_partition(1 << 18, 1, 1 << 12)[:10])
print(random_partition(1 << 18, 100000000, 1 << 12)[:10])
print(random_partition(1 << 18, 1000000000, 1 << 12)[:10])
print(random_partition(1 << 18, 1 << 18, 1)[:10])
print(random_partition(1 << 18, 1 << 30, 1 << 12)[:10])
    • 出力例
[0]
[1]
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100]
[0, 0, 0, 0, 0, 0, 0, 1, 0, 0]
[1, 3, 3, 0, 0, 0, 3]
[16, 14, 5, 2, 5, 3, 4, 0, 1, 0]
[1, 0, 0, 1, 0, 1, 0, 1, 0, 1]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
[176, 546, 366, 86, 1032, 169, 28, 155, 518, 188]
[3934, 3915, 4078, 4082, 4095, 4095, 4094, 4095, 4038, 4015]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096, 4096]

How to Copy Text in Vim with iTerm

iTerm の Vim 上でテキストをコピーする

デフォルトの状態では、iTerm で Vim 等を開いた時
マウスで範囲を選択してもコピー・ペーストを行うことができない。 

プロファイルのターミナル設定で、「Enable xterm mouse reporting」のチェックを外したら
コピペできるようになった。

Preferences 2

 

追記: 2013-07-16

@todashuta 様より追加情報を頂きました。ありがとうございます。

7.11.2013

How to Send an IRC Message with SSL in Python

Python: SSL 経由で IRC メッセージを送信する方法

  • IRC ライブラリをインストール
    irc 8.3.1 : Python Package Index

    $ sudo easy_install irc

    または

    $ sudo pip install irc
  • 実装例
    この辺りを参考に
    jaraco / irc / source / scripts / ssl-cat.py — Bitbucket
    # -*- coding: utf-8 -*-
    
    import ssl
    
    SERVER_ADDRESS = 'xxx.xxx.xxx.xxx'
    SERVER_PORT = 6667
    SERVER_PASS = 'xxxxxx'
    
    LOGIN_NAME = 'xxxxxx'
    LOGIN_CHANNEL = '#xxxxxx'
    
    
    def send(message):
        try:
            import irc.client
            import irc.connection
    
            client = irc.client.IRC()
            server = client.server()
            factory = irc.connection.Factory(wrapper=ssl.wrap_socket)
    
            c = server.connect(
                SERVER_ADDRESS, SERVER_PORT, LOGIN_NAME, SERVER_PASS,
                connect_factory=factory)
    
            c.privmsg(LOGIN_CHANNEL, message)
            c.disconnect()
        except Exception as e:
            print('WARN: Failed to send IRC message. (%s)' % e)

チャンネル宛にプライベートメッセージを送ると、メンバー全員へ通知される。

サーバのパスワードを間違えてもExceptionは発生しない。
どうやって気づけばよいのだろう。

 

Related Posts

mog project: Python: Sending IRC Message, Improved

7.09.2013

AWS: More Listings of EC2 Information

AWS: EC2 情報の簡易一覧表示

こちらの改良版。
mog project: AWS: Brief Listing of EC2 Instances 

インスタンスの他に、ボリューム、スナップショットの一覧も表示。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import subprocess
try:
    import json
except ImportError:
    print('You need python 2.6 or later to run this script.')
    sys.exit(1)


def list_instances():
    print('Instances:')
    command_args = ['describe-instances']
    headers = [
        ('Name', 16),
        ('Instance', 16),
        ('Type', 10),
        ('Launch Time', 26),
        ('State', 12),
    ]

    def generator(output):
        for i in output['Reservations'][0]['Instances']:
            yield [
                get_name(i),
                i['InstanceId'],
                i['InstanceType'],
                i['LaunchTime'],
                i['State']['Name'],
            ]
    print_list(command_args, headers, generator)


def list_volumes():
    print('Volumes:')
    command_args = ['describe-volumes']
    headers = [
        ('Name', 16),
        ('Volume ID', 16),
        ('Cap', 8),
        ('Type', 11),
        ('Zone', 17),
        ('State', 12),
    ]

    def generator(output):
        for i in output['Volumes']:
            yield [
                get_name(i),
                i['VolumeId'],
                '%d GB' % i['Size'],
                i['VolumeType'],
                i['AvailabilityZone'],
                i['State'],
            ]
    print_list(command_args, headers, generator)


def list_snapshots():
    print('Snapshots:')
    command_args = ['describe-snapshots', '--owner-ids', 'self']
    headers = [
        ('Name', 16),
        ('Snapshot ID', 16),
        ('Cap', 8),
        ('Start Time', 28),
        ('State', 12),
    ]

    def generator(output):
        for i in output['Snapshots']:
            yield [
                get_name(i),
                i['SnapshotId'],
                '%d GB' % i['VolumeSize'],
                i['StartTime'],
                i['State'],
            ]
    print_list(command_args, headers, generator)


def get_name(obj):
    return ''.join(
        [x['Value'] for x in obj.get('Tags', []) if x['Key'] == 'Name'])


def print_list(command_args, headers, generator):
    # Print header.
    print(''.join([x[0].ljust(x[1]) for x in headers]))
    print('-' * sum([x[1] for x in headers]))

    # Run command.
    stdout = subprocess.check_output(['aws', 'ec2'] + command_args)
    output = json.loads(stdout)

    # Print result.
    for result in generator(output):
        print(''.join(
            [result[i][:x[1] - 1].ljust(x[1]) for i, x in enumerate(headers)]))

    # Print footer.
    print('')


if __name__ == '__main__':
    list_instances()
    list_volumes()
    list_snapshots()

7.07.2013

How to Set Up IRC Server on CentOS

CentOS で IRC サーバの構築

以下、簡単なメモのみ。

  • 今回選んだサーバは ngIRCd
    ngIRCd: Next Generation IRC Daemon
  • EPEL リポジトリへ接続可能な状態にして yum コマンドでインストール
    EPEL/ja - FedoraProject 
    # yum install ngircd
  • /etc/ngircd.conf を編集
    最新のサンプル(http://ngircd.barton.de/doc/sample-ngircd.conf)を参考にカスタマイズ。
    バージョンアップに伴い、[Limits] や [SSL] が使えるようになっている。 
  • /etc/ngircd.motd を作成
    Message Of The Day。空ファイルでもよい。 
  • 構文チェック
    # ngircd --configtest
     
  • サービス追加、起動
    # chkconfig ngircd --add
    # service ngircd start
     
  • 必要に応じて iptables 等の設定を変更

 

Setup Shogi-server on AWS with Fabric

AWS: shogi-server を Fabric で構築する

目的

コンピュータ将棋のネット対局用サーバ shogi-server を AWS の EC2 インスタンス上に構築する。

このとき、全ての設定作業を Fabric で完結させたい。

 

前提

 

準備するもの

 

iptables

shogi-server (CSAプロトコル) で使う 4081 ポートを許可、ロギング設定など。

# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 4081 -j ACCEPT
-N LOGGING
-A INPUT -j LOGGING
-A LOGGING -j LOG --log-level warning --log-prefix "DROP:" -m limit
-A LOGGING -j DROP
COMMIT

 

sudoers

sudo の際に環境変数 PATH を引き継ぐようにしないと、なぜか Ruby のインストールに失敗してしまう。
env_keep に PATH を追記した sudoers ファイルをあらかじめ用意しておく。

# diff -u ./sudoers.20130707 ./sudoers
--- ./sudoers.20130707	2013-05-10 03:53:29.636195742 +0900
+++ ./sudoers	2013-07-07 11:47:48.647961526 +0900
@@ -76,6 +76,7 @@
 Defaults    env_keep += "LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES"
 Defaults    env_keep += "LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE"
 Defaults    env_keep += "LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY"
+Defaults    env_keep += "PATH"
 
 #
 # Adding HOME to env_keep may enable a user to run unrestricted

 

起動スクリプト

shogi-server をサービスとして登録し、起動/停止を行うためのスクリプト。
/etc/init.d/shogi-server として配備。

PIDファイルを /var/run、ログを /var/log/shogi-server 配下に作成している。

サービスは専用ユーザ shogi で実行される。

#!/bin/bash
#
# Start/Stop shogi-server daemon
#
# chkconfig: 2345 85 15
# description: shogi-server daemon mode

PROC_NAME=shogi-server
PROC_USER=shogi
PROC_GROUP=shogi
PROC_BIN=/opt/shogi-server/shogi-server
PID_FILE=/var/run/shogi-server.pid
DAEMON_LOG_DIR=/var/log/shogi-server
PLAYER_LOG_DIR=$DAEMON_LOG_DIR/player-logs
CONSOLE_LOG=$DAEMON_LOG_DIR/console.log

EVENT_NAME=event1
PORT_NUMBER=4081

# Sanity checks
[ -x $PROC_BIN ] || exit 1

# Source function library & LSB routines
. /etc/rc.d/init.d/functions

RETVAL=0

start()
{
    echo -n $"Starting $PROC_NAME: "

    mkdir -p $DAEMON_LOG_DIR $PLAYER_LOG_DIR
    touch $PID_FILE
    chown $PROC_USER:$PROC_GROUP $DAEMON_LOG_DIR $PLAYER_LOG_DIR $PID_FILE

    su - $PROC_USER -c \
        "ruby $PROC_BIN --daemon $DAEMON_LOG_DIR --pid-file $PID_FILE \
        --player-log-dir $PLAYER_LOG_DIR $EVENT_NAME $PORT_NUMBER \
        > $CONSOLE_LOG 2>&1"
    RETVAL=$?
    if [ $RETVAL -eq 0 ]; then
        success
    else
        rm -f $PID_FILE
        failure
    fi
    echo
}

stop()
{
    echo -n $"Stopping $PROC_NAME:"
    if [ -f $PID_FILE ]; then
        killproc -p $PID_FILE $PROC_NAME -TERM
        rm -f $PID_FILE
    fi
    RETVAL=$?
    [ $RETVAL -eq 0 ] && success || failure
    echo
}

case "$1" in
    start)
        start
        RETVAL=$?
        ;;
    stop)
        stop
        RETVAL=$?
        ;;
    status)
        status -p $PID_FILE $PROC_BIN
        RETVAL=$?
        ;;
    restart)
        stop
        start
        ;;
    *)
        echo $"Usage: $0 {start|stop|status|restart}"
        RETVAL=2
        ;;
esac

exit $RETVAL

 

logrotate 定義ファイル

ログファイルの洗い替えは logrotate を利用する。
/etc/logrotate.d/shogi-server として配備。

/var/log/shogi-server/shogi-server.log
/var/log/shogi-server/player-logs/*.log {
    weekly
    rotate 4
    missingok
    notifempty
    copytruncate
    compress
}

 

Fabric 用スクリプト

Fabric で使う Python スクリプト。
env.hosts はハードコーディングして使ってもよい。

標準APIの put 関数にも sudo 機能があるものの、SELinux 対応やバックアップ作成などが物足りなかったので
独自の _put という関数でラップしている。

EC2 のファイアウォール設定もこのスクリプトに含めているが、分離させた方がいいかもしれない。

# -*- coding: utf-8 -*-
"""
Installing shogi-server and its dependencies.
"""

import os
import json
from fabric.api import *
from fabric.decorators import runs_once, roles, task

env.user = 'ec2-user'
# env.hosts = []
env.use_ssh_config = True

WORK_DIR = os.path.dirname(os.path.abspath(__file__))

APL_USER = 'shogi'
APL_GROUP = 'shogi'
APL_HOME = '/home/%s' % APL_USER

GIT_RBENV = 'git://github.com/sstephenson/rbenv.git'
GIT_RUBY_BUILD = 'git://github.com/sstephenson/ruby-build.git'
GIT_SHOGI_SERVER = \
    'git://git.sourceforge.jp/gitroot/shogi-server/shogi-server.git'

RUBY_VERSION = '1.9.3-p448'

EC2_SECURITY_GROUP = 'quick-start-1'


@task
def setup():
    """Setup tasks for shogi-server."""

    setup_os_user()
    setup_firewall()
    install_ruby()
    install_shogi_server()


@task
def setup_os_user():
    """Add 'shogi' user and 'shogi' group."""

    if sudo('id %s' % APL_USER, quiet=True).succeeded:
        return

    # Add user and group.
    sudo('groupadd -g 501 %s' % APL_GROUP)
    sudo('useradd -u 501 -g %s %s' % (APL_GROUP, APL_USER))

    # Copy sudoers.
    _put('%s/sudoers' % WORK_DIR, '/etc/sudoers', '440', 'root', 'root')


@task
def setup_firewall():
    """Configure linux and EC2 firewall.
        AWS CLI and its connection settings are required."""

    # Linux iptables
    _put('%s/iptables' % WORK_DIR, '/etc/sysconfig/iptables',
         '600', 'root', 'root')
    sudo('service iptables restart')

    # EC2 firewall
    port = 4081
    cidr = '0.0.0.0/0'
    old = local('aws ec2 describe-security-groups', capture=True)
    j = json.loads(old)

    found = (EC2_SECURITY_GROUP, port, cidr) in (
        (a['GroupName'], b['FromPort'], c['CidrIp'])
        for a in j['SecurityGroups']
        for b in a['IpPermissions']
        for c in b['IpRanges'])

    if not found:
        opts = '--group-name %s --ip-protocol tcp ' % EC2_SECURITY_GROUP
        opts += '--from-port %d --to-port %d --cidr-ip %s' % (port, port, cidr)
        local('aws ec2 authorize-security-group-ingress %s' % opts)


@task
def install_ruby():
    """Install git, rbenv, ruby-build and ruby."""

    # Install git.
    sudo('yum -y install git')

    # Install rbenv.
    with cd('/usr/local'):
        sudo('git clone %s %s' % (GIT_RBENV, 'rbenv'))
        sudo('mkdir rbenv/shims rbenv/versions')

    # Install ruby-build.
    with cd('/usr/local'):
        sudo('git clone %s %s' % (GIT_RUBY_BUILD, 'ruby-build'))
    with cd('/usr/local/ruby-build'):
        sudo('./install.sh')

    # Create system-level profile.
    profile = '/etc/profile.d/rbenv.sh'
    sudo("""echo 'export RBENV_ROOT="/usr/local/rbenv"' > %s""" % profile)
    sudo("""echo 'export PATH="/usr/local/rbenv/bin:$PATH"' >> %s""" % profile)
    sudo("""echo 'eval "$(rbenv init -)"' >> %s""" % profile)

    # Install requirements.
    sudo('yum -y groupinstall "Development Tools"')
    sudo('yum -y install openssl-devel readline-devel zlib-devel')

    # Install ruby.
    sudo('su - -c "rbenv install 1.9.3-p448"')
    sudo('su - -c "rbenv global 1.9.3-p448"')
    sudo('su - -c "rbenv rehash"')


@task
def install_shogi_server():
    """Install and start shogi-server as a service."""

    shogi_path = '/opt/shogi-server'

    if sudo('test -d %s' % shogi_path, quiet=True).succeeded:
        return

    # Clone from repository.
    sudo('git clone %s %s' % (GIT_SHOGI_SERVER, shogi_path))
    sudo('chown -R %s:%s %s' % (APL_USER, APL_GROUP, shogi_path))

    # Copy init script.
    _put('%s/shogi-server.init' % WORK_DIR, '/etc/init.d/shogi-server',
         '755', 'root', 'root')

    # Add and start service.
    sudo('chkconfig --add shogi-server')
    sudo('service shogi-server start')

    # Copy logrotate setting.
    _put('%s/shogi-server.logrotate' % WORK_DIR,
         '/etc/logrotate.d/shogi-server', '644', 'root', 'root')


def _put(src, dst, mode, owner, group):
    tmp = '/tmp/%s.tmp' % src.split('/')[-1]
    backup_opts = '--backup=simple --suffix=.`date +%Y%m%d`'
    permission_opts = '-m %s -o %s -g %s' % (mode, owner, group)

    put(local_path=src, remote_path=tmp)
    sudo('install %s %s %s %s' % (backup_opts, permission_opts, tmp, dst))
    sudo('rm -f %s' % tmp)

 

実行方法

fab コマンドで実行。Microインスタンスで20分弱で完了。

$ fab -f ./shogi-server.py --list
$ fab -f ./shogi-server.py -H 対象サーバ setup

7.02.2013

AWS: Brief Listing of EC2 Instances

AWS: EC2 インスタンスの簡易一覧表示

 

AWS CLI の出力結果(json)を表形式にフォーマットして見やすくしたい。

 

EC2 インスタンス一覧の表示

jq でやろうと思ったが断念。Python のスクリプトを書いた。 

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys
import subprocess
try:
    import json
except ImportError:
    print('You need python 2.6 or later to run this script.')
    sys.exit(1)


def print_instances():
    headers = [
        ('Name', 24),
        ('Instance', 12),
        ('Type', 12),
        ('State', 12),
        ('LaunchTime', 24),
    ]
    print(''.join([x[0].ljust(x[1]) for x in headers]))
    print('-' * sum([x[1] for x in headers]))

    stdout = subprocess.check_output(['aws', 'ec2', 'describe-instances'])
    output = json.loads(stdout)

    for ins in output['Reservations'][0]['Instances']:
        result = [
            ''.join([x['Value'] for x in ins['Tags'] if x['Key'] == 'Name']),
            ins['InstanceId'],
            ins['InstanceType'],
            ins['State']['Name'],
            ins['LaunchTime'],
        ]
        print(''.join([result[i].ljust(x[1]) for i, x in enumerate(headers)]))

if __name__ == '__main__':
    print_instances()
  • 出力例
Name                    Instance    Type        State       LaunchTime
------------------------------------------------------------------------------------
server1                 i-00000000  t1.micro    running     2013-07-01T10:58:49.000Z
server2                 i-11111111  m1.small    stopped     2013-07-01T11:58:49.000Z
server3                 i-22222222  m1.small    stopping    2013-07-01T12:58:49.000Z
                        i-33333333  t1.micro    pending     2013-07-01T13:58:49.000Z

最終行は名前未設定のインスタンス。