LoginSignup
6
3

More than 5 years have passed since last update.

存在しないキーの値をインクリメントすると

Last updated at Posted at 2016-10-25

こんばんは:whale2:
各言語で辞書[キー]+=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さんに教えていただきました。

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}

おわり

各言語の人たちから刺されそう:whale2:

参考と注釈

6
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3