tsucchi’s diary(元はてなダイアリー)

はてなダイアリー(d.hatena.ne.jp/tsucchi1022)から移行したものです

配列の削除と splice

配列を操作してて、「ある条件のばあい、その要素を削除」みたいなことをしたい場合。

良くやるのが、

sub remove_item_in_some_condition {
    my (@input) = @_;
    my @result = ();
    for my $item ( @input ) {
        push @result, $item if( !some_condition($item) );
    }
    return @result;
}

みたいなやつです。(まあ map とか grep 使うほうが多いかも)

ただ、引数に配列リファレンスを渡して、そのリファレンスに対して操作したい(渡したリファレンスの指す先の配列の中身を削除したい)場合、この方法は使えません。

# 使えなくも無いのかもしれませんが、リファレンスに対して向き先を付け替えるようなコーディングが必要になり、これはバグの元なのでやりたくありません。

(リファレンスにこの操作をしたい、というのが間違いのような気もするので)何が正解なのか良く分かりませんが、僕はこの場合は splice を使います。

sub remove_item_in_some_condition2 {
    my ($input_aref) = @_;

    for ( my $i=0; $i < @{ $input_aref} ; $i++ ) {
        if ( some_condition($input_aref->[$i]) ) {
            splice @{ $input_aref }, $i--, 1;
        }
    }
}

っていうか、splice を使いたいとき、ぐぐってみても

splice @array, $i--, 1;

のサンプルがなかなか見つからなくて、いっつも $i のデクリメントを忘れて、「あれれ?」となるので、メモを残してみたのでした。

splice @array, 2, 3;

みたいなサンプル見せられても、「そんなの使う局面ねーだろ!」と思うんですけどね。

以下、動作する完全なサンプルです。

#!/usr/bin/perl
use strict;
use warnings;
use feature qw(say);

my @aaa = (1, 2, 3);
my @bbb = remove_item_in_some_condition(@aaa);
say @bbb;#=> (1, 3)

my @xxx = (1, 2, 3);
remove_item_in_some_condition2(\@xxx);
say @xxx;#=> (1, 3)

sub some_condition {
    my ($item) = @_;
    # 条件はなんでもいいのだけど、とりあえず
    # 「中身が2の場合消す」、とする
    return 1 if ( $item == 2 );
    return 0;
}

sub remove_item_in_some_condition {
    my (@input) = @_;
    my @result = ();
    for my $item ( @input ) {
        push @result, $item if( !some_condition($item) );
    }
    return @result;
}

sub remove_item_in_some_condition2 {
    my ($input_aref) = @_;

    for ( my $i=0; $i < @{ $input_aref} ; $i++ ) {
        if ( some_condition($input_aref->[$i]) ) {
            splice @{ $input_aref }, $i--, 1;
        }
    }
}