さて、前回の続き、今回もPerlによる超手抜き家計簿の実装法を紹介する。
前回は、
2012-03-12 09:00「残高設定 5000円」
2012-03-12 12:00「ココイチ カレー 900円」
2012-03-12 10:00「ドトール コーヒー 150円」
2012-03-12 13:00「使途不明 100円」
というツイート列を、
日付 時刻 店舗 品目 金額 残高
2012-03-12 13:00 ? - 100円 3850円
2012-03-12 12:00 ココイチ カレー 900円 3950円
2012-03-12 10:00 ドトール コーヒー 150円 4850円
2012-03-12 09:00 残高設定 - 5000円 5000円
という家計簿に組み立てる仕様を実装するプログラムに突入した。
先週はある程度まとまったプログラムをドバーンと公開して、上から説明していたが、途中で時間と紙数が尽きてしまった。
上のリストの★イマココというところまで到達している。
処理としては、@out という配列に
2012-03-12 09:00「残高設定 5000円」
2012-03-12 12:00「ココイチ カレー 900円」
2012-03-12 10:00「ドトール コーヒー 150円」
2012-03-12 13:00「使途不明 100円」
というツイートを受けて、
2012-03-12 09:00 残高設定 - 5000
2012-03-12 12:00 ココイチ カレー 900
2012-03-12 10:00 ドトール コーヒー 150
2012-03-12 13:00 ? - 100
というハッシュ配列が入っているはずだ。
ハッシュ配列というのは、配列の1要素1要素が無名ハッシュへの参照になっている状態のことだ。
いや、そういうPerl用語はないが、分かるのでいまオンザフライで作って使ってみたけどどうですか。
今、@outはこうなっている。
ではプログラムの続きである。
まず各明細を日付>時刻の順でソートする。
ツイートは入力時刻順になっているが、この仕様では削除して修正入力が出来るので、date、timeの順でソートする必要がある。
また、残高設定をしたら残高はそのcashの値になり、それ以外はcashの値を引いていく。
$zanは現在の残高の金額を格納するスカラー変数である。
@sortはソート後の明細を格納する配列だ。
は@outを各要素をデリファレンスしたもの(無名ハッシュ)のハッシュキーがdate、timeの要素をつないで文字列比較してソートしている。
これをforループで各要素を$outに入れながら回す。
ちなみに、foreachのことをforとも書ける。
foreach変数の$outと、配列@outを同じ名前にしている。
Perlでは配列とスカラー変数に同じ名前が付けられる(同じ名前でも違うものになる)からだ。
また、@sortとかPerlのキーワードをバンバン変数名に使えるのもPerlの特徴だ。
これは$、@のようなシジルがついているおかげである。
キーワードを避けて変な名前を思いつかなくて良いのである。
は店舗名が「残高照会」であるツイートは、金額を残高に設定している。
は、店舗名が「残高照会」でなくて、かつ、もし残高が設定されていた場合は、残高を買い物の分だけ減らす、という処理である。
もし残高が決まっていない場合は、残高設定より前に普通の買い物が来ている場合だ。
その場合は、金額を決めようがない(マイナスにするのも変である)ので、ここでは処理しない。
最後が、店舗名が残高照会でもなく、残高がまだ設定されていない場合の処理である。
これはさっき書いた、残高設定より前の普通の買い物に当たる。
この場合は本明細を無視して次のfor周回に進む。
ここでは、各明細の残高を、handというハッシュ要素に入れている。
で、配列@sortに$outを追加する。
forに入る前は、@outはこうなっていた。
日付 時刻 店舗 品目 金額
2012-03-12 09:00 残高設定 - 5000
2012-03-12 12:00 ココイチ カレー 900
2012-03-12 10:00 ドトール コーヒー 150
2012-03-12 13:00 ? - 100
で、forループを経て、@sortはこうなっている。
日付 時刻 店舗 品目 金額 残高
2012-03-12 09:00 残高設定 - 5000 5000
2012-03-12 10:00 ドトール コーヒー 150 4850
2012-03-12 12:00 ココイチ カレー 900 4150
2012-03-12 13:00 ? - 100 4050
で、家計簿としてはこれでカンペキだが、実際に携帯端末でちょこちょっと見るときは時刻で降順(最新の明細がトップ)にしたいので、配列を逆転してみた。
あっでもさっきforじゃなくてpushじゃなくてunshiftを使えば配列を逆転できたのね。
いま気づいた。
ということでHTML::Templateに入るが、すみませんまた来週・・・。



2012-03-12 09:00「残高設定 5000円」
2012-03-12 12:00「ココイチ カレー 900円」
2012-03-12 10:00「ドトール コーヒー 150円」
2012-03-12 13:00「使途不明 100円」
というツイート列を、
日付 時刻 店舗 品目 金額 残高
2012-03-12 13:00 ? - 100円 3850円
2012-03-12 12:00 ココイチ カレー 900円 3950円
2012-03-12 10:00 ドトール コーヒー 150円 4850円
2012-03-12 09:00 残高設定 - 5000円 5000円
という家計簿に組み立てる仕様を実装するプログラムに突入した。
先週はある程度まとまったプログラムをドバーンと公開して、上から説明していたが、途中で時間と紙数が尽きてしまった。
#!/usr/bin/perl -w
# mailHTML -- ツイッター家計簿アルファ版
use strict;
use Encode qw(from_to encode);
use Mail::Mailer;
use lib '/home/????????/local/lib';
use lib '/home/????????/local/lib/perl5';
use lib '/home/????????/local/lib/perl5/site_perl';
use Net::Twitter;
use utf8;
use DateTime::Format::Strptime;
use HTML::Template;
my $handle = Net::Twitter->new({
traits => [qw/OAuth API::REST API::Search/],
consumer_key => "????????",
consumer_secret => "????????",
access_token => "????????",
access_token_secret => "????????",
});
my $dt = DateTime->now( time_zone => 'Asia/Tokyo' );
$dt->subtract( days => 1 );
my $screen_name = '????????'; # アカウントのscreen name
my $statuses =
$handle->user_timeline({ id => $screen_name, count => 200, since => $dt });
my @out = ();
for my $status ( @$statuses ) {
my ($date, $time, $shop, $item, $cash);
my @tw = split / /, $status->{text};
for (@tw) {
if (/(\d\d\d\d-\d\d-\d\d)/) {
$date = $1;
} elsif (/(\d\d:\d\d)/) {
$time = $1.':00';
} elsif (/(\d+)円$/) {
$cash = $1;
} elsif (defined $shop) {
$item = $_;
} else {
$shop = $_;
}
}
next unless defined $cash;
my ($nowdate, $nowtime) = &tzChange($status->{created_at});
$date = $nowdate unless defined $date;
$time = $nowtime unless defined $time;
$item = "-" unless defined $item;
$shop = "?" unless defined $shop;
push @out, { date => $date, time => $time, shop => $shop, item => $item, cash => $cash }; # ★イマココ
}
my $zan;
my @sort;
for my $out (sort {$a->{date}.$a->{time} cmp $b->{date}.$b->{time}} @out) {
if ($out->{shop} eq '残高設定') {
$zan = $out->{cash};
} elsif (defined $zan) {
$zan -= $out->{cash};
} else {
next;
}
$out->{hand} = $zan;
push @sort, $out;
}
@sort = reverse @sort;
my $tmpl_file = "kakeibo.tmpl";
my $tmpl;
open (my $template, "<:utf8", $tmpl_file );
$tmpl = HTML::Template->new(filehandle => *$template);
close $template;
$tmpl->param( { Table => \@sort } );
my $from = 'from@example.com';
my $to = '?????@example.com';
my $subject = "家計簿";
my $mailer = Mail::Mailer->new("sendmail");
$mailer->open({ From => $from,
To => $to,
Subject => $subject,
'MIME-Version' => '1.0',
'Content-Type' => "text/html; charset=UTF-8",
'Content-Transfer-Encoding' => '8bit',
}) or die "Can't open $!\n";
print $mailer $tmpl->output;
$mailer->close();
sub tzChange ($) {
my $utc = shift;
#Sat Feb 11 03:54:53 +0000 2012
my $strp = DateTime::Format::Strptime->new(
pattern => '%a %b %d %H:%M:%S %z %Y'
);
my $dt = $strp->parse_datetime($utc);
$dt->set_time_zone('Asia/Tokyo');
return ($dt->strftime("%Y-%m-%d"), $dt->strftime("%H:%M:%S"));
}
上のリストの★イマココというところまで到達している。
処理としては、@out という配列に
2012-03-12 09:00「残高設定 5000円」
2012-03-12 12:00「ココイチ カレー 900円」
2012-03-12 10:00「ドトール コーヒー 150円」
2012-03-12 13:00「使途不明 100円」
というツイートを受けて、
2012-03-12 09:00 残高設定 - 5000
2012-03-12 12:00 ココイチ カレー 900
2012-03-12 10:00 ドトール コーヒー 150
2012-03-12 13:00 ? - 100
というハッシュ配列が入っているはずだ。
ハッシュ配列というのは、配列の1要素1要素が無名ハッシュへの参照になっている状態のことだ。
いや、そういうPerl用語はないが、分かるのでいまオンザフライで作って使ってみたけどどうですか。
push @out, { date => $date, time => $time, shop => $shop, item => $item, cash => $cash }; # ★イマココ
今、@outはこうなっている。
@out = (
{
date => "2012-03-12",
time => "09:00",
shop => "残高設定",
item => "-",
cash => "5000",
},
{
date => "2012-03-12",
time => "12:00",
shop => "ココイチ",
item => "カレー",
cash => "900",
},
...
);
ではプログラムの続きである。
まず各明細を日付>時刻の順でソートする。
ツイートは入力時刻順になっているが、この仕様では削除して修正入力が出来るので、date、timeの順でソートする必要がある。
また、残高設定をしたら残高はそのcashの値になり、それ以外はcashの値を引いていく。
my $zan;
my @sort;
for my $out (sort {$a->{date}.$a->{time} cmp $b->{date}.$b->{time}} @out) {
if ($out->{shop} eq '残高設定') {
$zan = $out->{cash};
} elsif (defined $zan) {
$zan -= $out->{cash};
} else {
next;
}
$out->{hand} = $zan;
push @sort, $out;
}
$zanは現在の残高の金額を格納するスカラー変数である。
@sortはソート後の明細を格納する配列だ。
sort {$a->{date}.$a->{time} cmp $b->{date}.$b->{time}} @out
は@outを各要素をデリファレンスしたもの(無名ハッシュ)のハッシュキーがdate、timeの要素をつないで文字列比較してソートしている。
これをforループで各要素を$outに入れながら回す。
ちなみに、foreachのことをforとも書ける。
foreach変数の$outと、配列@outを同じ名前にしている。
Perlでは配列とスカラー変数に同じ名前が付けられる(同じ名前でも違うものになる)からだ。
また、@sortとかPerlのキーワードをバンバン変数名に使えるのもPerlの特徴だ。
これは$、@のようなシジルがついているおかげである。
キーワードを避けて変な名前を思いつかなくて良いのである。
if ($out->{shop} eq '残高設定') {
$zan = $out->{cash};
は店舗名が「残高照会」であるツイートは、金額を残高に設定している。
} elsif (defined $zan) {
$zan -= $out->{cash};
は、店舗名が「残高照会」でなくて、かつ、もし残高が設定されていた場合は、残高を買い物の分だけ減らす、という処理である。
もし残高が決まっていない場合は、残高設定より前に普通の買い物が来ている場合だ。
その場合は、金額を決めようがない(マイナスにするのも変である)ので、ここでは処理しない。
} else {
next;
}
最後が、店舗名が残高照会でもなく、残高がまだ設定されていない場合の処理である。
これはさっき書いた、残高設定より前の普通の買い物に当たる。
この場合は本明細を無視して次のfor周回に進む。
$out->{hand} = $zan;
ここでは、各明細の残高を、handというハッシュ要素に入れている。
push @sort, $out;
}
で、配列@sortに$outを追加する。
forに入る前は、@outはこうなっていた。
日付 時刻 店舗 品目 金額
2012-03-12 09:00 残高設定 - 5000
2012-03-12 12:00 ココイチ カレー 900
2012-03-12 10:00 ドトール コーヒー 150
2012-03-12 13:00 ? - 100
で、forループを経て、@sortはこうなっている。
日付 時刻 店舗 品目 金額 残高
2012-03-12 09:00 残高設定 - 5000 5000
2012-03-12 10:00 ドトール コーヒー 150 4850
2012-03-12 12:00 ココイチ カレー 900 4150
2012-03-12 13:00 ? - 100 4050
で、家計簿としてはこれでカンペキだが、実際に携帯端末でちょこちょっと見るときは時刻で降順(最新の明細がトップ)にしたいので、配列を逆転してみた。
@sort = reverse @sort;
あっでもさっきforじゃなくてpushじゃなくてunshiftを使えば配列を逆転できたのね。
いま気づいた。
ということでHTML::Templateに入るが、すみませんまた来週・・・。


