こんばんは
各言語で辞書[キー]+=1
のような処理を書くサンプルと、その処理で「そのようなキーはありません」といった旨のメッセージが出る言語についてどうすればよいのか。(伝わりづらい)
環境
実行環境は以下のとおりです。
環境
# コンピュータ
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.12.1
BuildVersion: 16B2555
# Ruby
$ ruby -v
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin16]
# Perl
$ perl -v | grep v5
This is perl 5, version 24, subversion 0 (v5.24.0) built for darwin-thread-multi-2level
# Perl6
$ perl6 -v
This is Rakudo version 2016.07.1 built on MoarVM version 2016.07
implementing Perl 6.c.
# Python
$ python --version
Python 3.5.1 :: Anaconda 4.1.0 (x86_64)
# PHP5
$ php -v
PHP 5.6.25 (cli) (built: Sep 6 2016 16:37:16)
Copyright (c) 1997-2016 The PHP Group
Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
Ruby
エラーになるパターン
エラー
target = %w[a a b b c 0 1 1 1]
count = {}
target.each{|var|
count[ var ] += 1
}
puts count
=begin
# => エラー
inc.rb:5:in `block in <main>': undefined method `+' for nil:NilClass (NoMethodError)
from inc.rb:4:in `each'
from inc.rb:4:in `<main>'
=end
回避策
Rubyきれい。
inc.rb
target = %w[a a b b c 0 1 1 1]
count = Hash.new(0) # 初期値を設定
target.each{|var|
count[ var ] += 1
}
puts count #=> {"a"=>2, "b"=>2, "c"=>1, "0"=>1, "1"=>3}
inc2.rb
target = %w[a a b b c 0 1 1 1]
count = {}
count.default = 0 # 初期値を設定
target.each{|var|
count[ var ] += 1
}
puts count #=> {"a"=>2, "b"=>2, "c"=>1, "0"=>1, "1"=>3}
Python
エラーになるパターン
エラー
target = 'a a b b c 0 1 1 1'.split(' ')
count = {}
for var in target:
count[ var ] += 1
print( count )
'''
# => エラー
Traceback (most recent call last):
File "inc.py", line 6, in <module>
count[ var ] += 1
KeyError: 'a'
'''
回避策1
Pythonはもっと良い方法がありそう。
inc.py
target = 'a a b b c 0 1 1 1'.split(' ')
count = {}
for var in target:
count.setdefault( var, 0 )
count[ var ] += 1
print( count ) #=> {'a': 2, '1': 3, '0': 1, 'c': 1, 'b': 2}
回避策2
defaultdict() を用いる方法。
コメント欄で@mpywさんに教えていただきました。
:inc2.py
from collections import defaultdict
target = 'a a b b c 0 1 1 1'.split(' ')
count = defaultdict(int)
for var in target:
count[ var ] += 1
print( count ) #=> defaultdict(<class 'int'>, {'b': 2, '1': 3, '0': 1, 'a': 2, 'c': 1})
回避策3
例外処理を書く方法。どの言語にも適用できそう。素敵。
inc3.py
target = 'a a b b c 0 1 1 1'.split(' ')
count = {}
for var in target:
try:
count[ var ] += 1
except KeyError:
count[ var ] = 1
print( count )
PHP
エラーになるパターン
エラーではないもののUndefined indexなNoticeメッセージが出る。
error_reporting( -1 );
を書かなければ出ない
Notice
<?php
error_reporting( -1 );
$target = explode(' ', 'a a b b c 0 1 1 1');
$count = [];
foreach( $target as $var ){
$count[ $var ] += 1;
}
echo json_encode( $count ) . PHP_EOL ;
/*
# =>
Notice: Undefined index: a in /Users/yuhei/inc.php on line 8
Notice: Undefined index: b in /Users/yuhei/inc.php on line 8
Notice: Undefined index: c in /Users/yuhei/inc.php on line 8
Notice: Undefined offset: 0 in /Users/yuhei/inc.php on line 8
Notice: Undefined offset: 1 in /Users/yuhei/inc.php on line 8
{"a":2,"b":2,"c":1,"0":1,"1":3}
*/
回避策1(非推奨)
エラーを抑止してはいけない(戒め)
非推奨
<?php
error_reporting( -1 );
$target = explode(' ', 'a a b b c 0 1 1 1');
$count = [];
foreach( $target as $var ){
@$count[ $var ] += 1; # エラー抑止
}
echo json_encode( $count ) . PHP_EOL ; #=> {"a":2,"b":2,"c":1,"0":1,"1":3}
回避策2
キーがない場合1を代入。という条件分岐を追加。
isset()
<?php
error_reporting( -1 );
$target = explode(' ', 'a a b b c 0 1 1 1');
$count = [];
foreach( $target as $var ){
if( isset( $count[ $var ] ) ){
$count[ $var ] += 1;
}
else{
$count[ $var ] = 1;
}
}
echo json_encode( $count ) . PHP_EOL ; #=> {"a":2,"b":2,"c":1,"0":1,"1":3}
回避策3
この例題については組み込み関数 array_count_values()
のが良いと思う。
array_count_values()
<?php
error_reporting( -1 );
$target = explode(' ', 'a a b b c 0 1 1 1');
$count = array_count_values( $target );
echo json_encode( $count ) . PHP_EOL ; #=> {"a":2,"b":2,"c":1,"0":1,"1":3}
備考
array_count_values()
の中でもキーがなければ1を代入。キーがあればインクリメント。という処理が行われている様子。
( キーが数値か文字列であることもチェックしていて素敵滅法 )
php-5.6.27/ext/standard/array.c
/* {{{ proto array array_count_values(array input)
Return the value as key and the frequency of that value in input as value */
PHP_FUNCTION(array_count_values)
{
zval *input, /* Input array */
**entry, /* An entry in the input array */
**tmp;
HashTable *myht;
HashPosition pos;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &input) == FAILURE) {
return;
}
/* Initialize return array */
array_init(return_value);
/* Go through input array and add values to the return array */
myht = Z_ARRVAL_P(input);
zend_hash_internal_pointer_reset_ex(myht, &pos);
while (zend_hash_get_current_data_ex(myht, (void **)&entry, &pos) == SUCCESS) {
if (Z_TYPE_PP(entry) == IS_LONG) {
if (zend_hash_index_find(Z_ARRVAL_P(return_value), Z_LVAL_PP(entry), (void **)&tmp) == FAILURE) {
zval *data;
MAKE_STD_ZVAL(data);
ZVAL_LONG(data, 1);
zend_hash_index_update(Z_ARRVAL_P(return_value), Z_LVAL_PP(entry), &data, sizeof(data), NULL);
} else {
Z_LVAL_PP(tmp)++;
}
} else if (Z_TYPE_PP(entry) == IS_STRING) {
if (zend_symtable_find(Z_ARRVAL_P(return_value), Z_STRVAL_PP(entry), Z_STRLEN_PP(entry) + 1, (void**)&tmp) == FAILURE) {
zval *data;
MAKE_STD_ZVAL(data);
ZVAL_LONG(data, 1);
zend_symtable_update(Z_ARRVAL_P(return_value), Z_STRVAL_PP(entry), Z_STRLEN_PP(entry) + 1, &data, sizeof(data), NULL);
} else {
Z_LVAL_PP(tmp)++;
}
} else {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Can only count STRING and INTEGER values!");
}
zend_hash_move_forward_ex(myht, &pos);
}
}
/* }}} */
Perl
エラーにならないPerl最高。
inc.pl
use v5.24;
use warnings;
use Data::Dumper;
my @target = qw[a a b b c 0 1 1 1];
my %count;
for my $var ( @target ){
$count{ $var } += 1;
}
print Dumper \%count;
__DATA__
# =>
$VAR1 = {
'c' => 1,
'1' => 3,
'b' => 2,
'a' => 2,
'0' => 1
};
Perl6
エラーにならないPerl6最(ry
inc.pl6
use v6;
my @target = <a a b b c 0 1 1 1>;
my %count;
for @target -> Str $var {
%count{ $var } += 1;
}
%count.say; #=> {0 => 1, 1 => 3, a => 2, b => 2, c => 1}
おわり
各言語の人たちから刺されそう
参考と注釈
- Perl 5 version 24.0 documentation > Language reference > perlsyn
- Perl 6 Documentation > class Hash
- Ruby 2.3.0 リファレンスマニュアル > ライブラリ一覧 > 組み込みライブラリ > Hashクラス
- Ruby | ハッシュの各要素に対して いきなりインクリメント ( += ) する - Qiita
- Python 3.5.2 ドキュメント » Python 標準ライブラリ » 4. 組み込み型
- How can I pre-increment at an empty array index without throwing a notice? - stackoverflow
- エルビス演算子でスマートに変数を初期化 - Qiita
- isset, empty, is_null の動作まとめ - Qiita
- PHP マニュアル > 関数リファレンス > 変数・データ型関連 > 配列 > 配列 関数 > array_count_values
- プログラマが持つべき心構え (The Zen of Python) - Qiita