2010年1月28日木曜日

Rubyでブロックを受け取るArray#uniq, Array#uniq!

ブロックの評価結果にしたがって重複する要素を削除するArray#uniq_byが必要になった。提案はされたけど、Array#uniqがブロックを受け付けていないので、uniq_byを実装するよりもuniqがブロックを取るようにするべき、ということで却下されたようだ。

1.9.1でもブロックを受け取るuniqは実装されていなかったので自分でやってみた。sortsort_byの関係にならって、ブロックが二つの要素を取るようにしたのだけど、この場合は重複判定にHashを利用することはできない。オリジナルの実装と同じように、重複した要素は後ろにあるほうが削除されるようにした。

class Array
  def uniq_with_block!
    return uniq_without_block! unless block_given?

    size = self.size
    self.each_with_index do |a, i|
      (self.size - 1).downto(i + 1) do |j|
        self.delete_at(j) if yield a, self[j]
      end
    end
    self.size < size ? self : nil
  end
  alias_method :uniq_without_block!, :uniq!
  alias_method :uniq!, :uniq_with_block!

  def uniq_with_block(&block)
    if block_given?
      dup = self.dup
      dup.uniq_with_block!(&block)
      dup
    else
      uniq_without_block
    end
  end
  alias_method :uniq_without_block, :uniq
  alias_method :uniq, :uniq_with_block
end

テスト。

require "test/unit"

class UniqWithBlockTest < Test::Unit::TestCase
  def setup
    @block = lambda {|a,b| a.casecmp(b) == 0 }
  end

  def test_uniq_with_block
    assert_equal([], [].uniq(&@block))
    assert_equal(%w(a), %w(a).uniq(&@block))
    assert_equal(%w(a b), %w(a b).uniq(&@block))

    assert_equal(%w(a), %w(a A).uniq(&@block))
    assert_equal(%w(a B), %w(a B A b).uniq(&@block))

    a = %w(a B A a b c)
    b = a.dup
    assert_equal(%w(a B c), a.uniq(&@block))
    assert_equal(b, a)

    assert_equal(%w(a B A b c), %w(a B A a b c).uniq)
  end

  def test_uniq_bang_with_block
    ary = []
    assert_nil(ary.uniq!(&@block))
    assert_equal([], ary)

    ary = %w(a)
    assert_nil(ary.uniq!(&@block))
    assert_equal(%w(a), ary)

    ary = %w(a b)
    assert_nil(ary.uniq!(&@block))
    assert_equal(%w(a b), ary)

    ary = %w(a A)
    assert_same(ary, ary.uniq!(&@block))
    assert_equal(%w(a), ary)

    ary = %w(a B A b)
    assert_same(ary, ary.uniq!(&@block))
    assert_equal(%w(a B), ary)

    ary = %w(a B A a b c)
    assert_same(ary, ary.uniq!(&@block))
    assert_equal(%w(a B c), ary)

    assert_equal(%w(a B A b c), %w(a B A a b c).uniq!)
  end
end