0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Python to RubyAdvent Calendar 2021

Day 21

[py2rb] 辞書クラスの拡張2

Last updated at Posted at 2022-01-27

はじめに

移植やってます。
( from python 3.7 to ruby 2.7 )

辞書クラスの拡張 (Python)

from collections import defaultdict

class BasicComposition(defaultdict):
    def __init__(self, *args, **kwargs):
        defaultdict.__init__(self, int)
        for k, v in list(self.items()):
            if not v:
                del self[k]

    def __missing__(self, key):
        return 'nil'

    def __setitem__(self, key, value):
        if isinstance(value, float):
            value = int(round(value))
        elif not isinstance(value, int):
            print('Error')
        if value:
            super(BasicComposition, self).__setitem__(key, value)
        elif key in self:
            del self[key]

d = BasicComposition()
d.update({'A': 1, 'B': 2.1, 'C': 0})
d['D'] = 0
d['E'] = 1.2
for k, v in d.items():
    print(k, v)

print(d['F'])

# output
A 1
B 2.1
C 0  
E 1  
nil

コメント欄にてご指摘いただいた通り、非整数を代入することが可能です。
運用で逃げようとも思いましたが、別件の多重継承をどうするかという話もありますので、考えてみました。

継承案 (Ruby)

class BasicComposition < Hash
  def initialize(*args, **kwargs)
    self.default = 0

    if args
      args.each do |x|
        self[x] += 1
      end
    end
    if kwargs
      kwargs.each do |k, v|
        self[k] = v
      end
    end
  end

  def []=(key, value)
    if value.instance_of?(Float)
      value = value.round
    elsif value.instance_of?(Integer).!
      raise TypeError, 'IntegerもしくはFloat以外が指定されました。'
    end
    if value != 0 # reject 0's
      self.merge!({key => value})
    elsif self.include?(key)
      self.delete(key)
    end
  end
end

d = BasicComposition.new
d.merge!({'A' => 1, 'B' => 2.1, 'C' => 0})
d['D'] = 0
d['E'] = 1.2
d.each do |k, v|
  p [k, v]
end

puts d['F']

# output
["A", 1]
["B", 2.1]
["C", 0]  
["E", 1]  
0

これは、単に移植しただけですので、非整数の代入が可能です。

委譲案1 (Ruby)

class BasicComposition
  def initialize(*args, **kwargs)
    @h = Hash.new(0)

    if args
      args.each do |x|
        @h[x] += 1
      end
    end
    if kwargs
      kwargs.each do |k, v|
        @h[k] = v
      end
    end
  end

  def []=(key, value)
    if value.instance_of?(Float)
      value = value.round
    elsif value.instance_of?(Integer).!
      raise TypeError, 'IntegerもしくはFloat以外が指定されました。'
    end
    if value != 0 # reject 0's
      @h.merge!({key => value})
    elsif self.include?(key)
      @h.delete(key)
    end
  end

  def merge!(**kwargs)
    if kwargs
      kwargs.each do |k, v|
        self[k] = v
      end
    end
  end

  def include?(key)
    @h.include?(key)
  end

  def [](key)
    @h[key]
  end

  def h
    @h
  end
end

d = BasicComposition.new
d.merge!('A' => 1, 'B' => 2.1, 'C' => 0)
d['D'] = 0
d['E'] = 1.2
d.h.each do |k, v|
  p [k, v]
end

puts d['F']

# output
["A", 1]
["B", 2]
["E", 1]
0  

独習Ruby 450p に委譲の説明があります。

# class BasicComposition < Hash

    @h = Hash.new(0)

Hash継承するのではなく、内部に保持(has)します。
d.h.eachのところが格好悪いのですが、'B'の値が整数となり、'C' => 0が削除されました。

委譲案2 (Ruby)

  def merge!(**kwargs)
    # if kwargs
    #   kwargs.each do |k, v|
    #     self[k] = v
    #   end
    # end
  end

# output
["E", 1]
0

委譲案1のコードで、def merge!を空のメソッドにします。
結果として、merge!メソッドからの値変更をスルーすることにより、非整数の代入を防ぎます。

forwardable (Ruby)

require 'forwardable'

class BasicComposition
  extend Forwardable

  def initialize(*args, **kwargs)
    @h = Hash.new(0)

    if args
      args.each do |x|
        @h[x] += 1
      end
    end
    if kwargs
      kwargs.each do |k, v|
        @h[k] = v
      end
    end
  end

  def_delegators :@h, :[], :include?, :each

  def []=(key, value)
    if value.instance_of?(Float)
      value = value.round
    elsif value.instance_of?(Integer).!
      raise TypeError, 'IntegerもしくはFloat以外が指定されました。'
    end
    if value != 0 # reject 0's
      @h.merge!({key => value})
    elsif self.include?(key)
      @h.delete(key)
    end
  end

  def merge!(**kwargs)
    if kwargs
      kwargs.each do |k, v|
        self[k] = v
      end
    end
  end
end

d = BasicComposition.new
d.merge!('A' => 1, 'B' => 2.1, 'C' => 0)
d['D'] = 0
d['E'] = 1.2
d.each do |k, v|
  p [k, v]
end

puts d['F']

# output
["A", 1]
["B", 2]
["E", 1]
0 

require 'forwardable'つよいですね。
d.eachになってスッキリしています。

@Nabetani さん、ご指摘ありがとうございました。

メモ

  • Python の 辞書クラスの拡張2 を学習した
  • 百里を行く者は九十里を半ばとす
0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?