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 3 years have passed since last update.

ビット操作メソッド三題(Ruby)

Last updated at Posted at 2021-01-13

正統的なビット操作の話ではありません。

Ruby を使っているとビット操作はあまり必要なのだが、競技プログラミングをやっていたりすると時にはビット操作したくなることもある。で、Ruby をあらためて見てみると(当り前だが)ビット操作も一通り揃っているわけだ。競プロで便利だなと思うのは例えば「ビットの立っている」ものを数える、つまり整数を二進数表現に直して "1" の数を数えるというもので、Ruby では

to_s(2).chars.count { _1 == "1" }

と簡潔に書ける。
 また、いわゆる「ビット全探索」は、Ruby ではビットを使わなくても簡単に書ける。「ビット全探索」でよく使われるのが部分集合の列挙で、例えば、[0, 1, 2, 3, 4]のすべての部分集合を列挙する場合は、

ary = [0, 1, 2, 3, 4]
co = 0
(0..ary.size).each do |i|
  ary.combination(i) do |sub_set|
    p [co, sub_set]
    co += 1
  end
end

などと書ける。結果は

[0, []]
[1, [0]]
[2, [1]]
[3, [2]]
[4, [3]]
[5, [4]]
[6, [0, 1]]
[7, [0, 2]]
[8, [0, 3]]
[9, [0, 4]]
[10, [1, 2]]
[11, [1, 3]]
[12, [1, 4]]
[13, [2, 3]]
[14, [2, 4]]
[15, [3, 4]]
[16, [0, 1, 2]]
[17, [0, 1, 3]]
[18, [0, 1, 4]]
[19, [0, 2, 3]]
[20, [0, 2, 4]]
[21, [0, 3, 4]]
[22, [1, 2, 3]]
[23, [1, 2, 4]]
[24, [1, 3, 4]]
[25, [2, 3, 4]]
[26, [0, 1, 2, 3]]
[27, [0, 1, 2, 4]]
[28, [0, 1, 3, 4]]
[29, [0, 2, 3, 4]]
[30, [1, 2, 3, 4]]
[31, [0, 1, 2, 3, 4]]

となって、確かにすべての部分集合が列挙されている(空集合を含む場合)。Ruby の組み込みメソッドにないのが不思議なくらいだが、これはだから

class Array
  def each_subset(empty_set=true)
    if block_given?
      n = empty_set ? 0 : 1
      (n..size).each do |i|
        combination(i) { yield _1 }
      end
    else
      to_enum(__method__, empty_set)
    end
  end
end

とでもして Array クラスに追加してやったらよい。で、上のと同じ結果を得ようと思えば

[0, 1, 2, 3, 4].each_subset.with_index do |ary, i|
  p [i, ary]
end

とでもすればよいのである。これがあったら競プロに便利だろうね。組み込みに追加されないかね。

#本題っぽいもの
で、以下が本題というほどでもないのだが、Ruby にビット操作メソッドを三つ追加してみた。いずれも実用性はないものと考えられ、たんなる遊び。
こんな感じである。

class Integer
  def each_bit
    if block_given?
      to_s(2).chars.reverse.map { _1 == "1" }.each { yield _1 }
    else
      to_enum(__method__)
    end
  end
  
  def bit_reverse
    to_s(2).chars.reverse.join.to_i(2)
  end
end

class Array
  def to_i
    map { _1 ? "1" : "0" }.reverse.join.to_i(2)
  end
end

まず、Integer#each_bitはビットごとにブロックを実行し、ブロック変数にはビットが 1 ならtrue、0 ならfalseが入るというもの。

0b10101101.each_bit { p _1 }

を実行すると

true
false
true
true
false
true
false
true

が出力となる。下位ビットから実行されていくのに注意。だから最後は必ずtrueになる。

173.each_bit.map(&:itself)    #=>[true, false, true, true, false, true, false, true]
0.each_bit.map(&:itself)      #=>[false]

173.to_s(2)    #=>"10101101"

など。
 

Array#to_iはこれの反対。配列の中身で真のものを 1、偽のものを 0 として二進表現と見做し、Integer に変換する。これも配列の左から下位ビットに詰められていくのに注意。

[true, false, true, true, false, true, false, true].to_i    #=>173
[:a, nil, 1, -1, 1 == 0, 1.integer?, 0.1 < 0, true].to_i    #=>173
173.each_bit.map(&:itself).to_i    #=>173

 

Integer#bit_reverseはテキトーに思いついたもの。Integer のビットを左右反転させた Integer を作る。まぎらわしいが、ビット反転(否定、Integer#~)とは全然ちがうのです。

173.bit_reverse    #=>181
173.to_s(2)    #=>"10101101"
181.to_s(2)    #=>"10110101"

なお、bit_reverse.bit_reverseは必ずしも元に戻らないので注意(わかりますね?)。
 

おそまつ様でした。

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