有限群の実装(Ruby)

クラス Group#initialize の set は定義された集合(配列)、op は定義された演算(Proc オブジェクト)。@ie単位元、@order は位数、インスタンスメソッド inverse(a) は a の逆元を与えます。(結合法則の確認はひどく時間がかかる場合があるので、引数 cck = false でスキップを選択できます。)

group.rb

class Group
  def initialize(set, op, cck = true)
    @set = set
    @op = op
    @order = @set.size    #位数
    
    raise "Array クラスを使ってください" if @set.class != Array
    raise "演算の引数の数は2でなければなりません" unless @op.arity == 2
    
    @set.repeated_permutation(2) do |ar|
      raise "群が閉じていません" unless @set.include?(@op[ar[0], ar[1]])
    end
    
    if cck    #結合法則のチェックをするか
      @set.repeated_permutation(3) do |ar|
        unless @op[@op[ar[0], ar[1]], ar[2]] == @op[ar[0], @op[ar[1], ar[2]]]
          raise "結合法則が成立していません"
        end
      end
    end
    
    ar = @set.select {|a| ie?(a)}
    raise "単位元が存在しません" unless ar.size == 1
    @ie = ar[0]
    
    @set.each do |a|
      raise "逆元が存在しません" unless @set.include?(inverse(a))
    end
  end
  attr_reader :ie, :order
  
  def inverse(a)
    @set.each do |x|
      return x if @op[a, x] == @ie and @op[x, a] == @ie
    end
    raise "逆元が存在しません"
  end
  
  def ie?(a)
    @set.each do |x|
      return false unless x == @op[a, x] and x == @op[x, a]
    end
    true
  end
end

3次の対称群(参照)を実装してみました。

perm = lambda do |num1, num2|
  n1 = num1.chars.map(&:to_i)
  n2 = num2.chars.map(&:to_i)
  n1.map {|i| n2[i]}.join
end

ar = ["012", "021", "102", "120", "201", "210"]
g = Group.new(ar, perm)

p g.ie                #=>"012"  単位元
p g.inverse("120")    #=>"201"  逆元
p ar.each_with_object([]) {|x, a| a << "#{x}#{g.inverse(x)}"}
#["012→012", "021→021", "102→102", "120→201", "201→120", "210→210"] すべての逆元

4次の対称群もまったく同様です。

ar = [0, 1, 2, 3].permutation(4).map(&:join)
g = Group.new(ar, perm)

p g.ie                 #=>"012"  単位元
p g.order              #=>24     位数
p g.inverse("1203")    #=>"201"  逆元
p ar.each_with_object([]) {|x, a| a << "#{x}#{g.inverse(x)}"}
#["0123→0123", "0132→0132", "0213→0213", "0231→0312", "0312→0231",
# "0321→0321", "1023→1023", "1032→1032", "1203→2013", "1230→3012",
# "1302→2031", "1320→3021", "2013→1203", "2031→1302", "2103→2103",
# "2130→3102", "2301→2301", "2310→3201", "3012→1230", "3021→1320",
# "3102→2130", "3120→3120", "3201→2310", "3210→3210"]
#すべての逆元

クラインの4元群。

ar = %w(e a b c)
op = lambda do |x, y|
  if x == "e" then y
    elsif y == "e" then x
    elsif (x == "a" and y == "b") or (x == "b" and y == "a") then "c"
    elsif (x == "b" and y == "c") or (x == "c" and y == "b") then "a"
    elsif (x == "c" and y == "a") or (x == "a" and y == "c") then "b"
    else "e" 
  end
end

g = Group.new(ar, op)
p g.ie    #=>"e"    単位元
p ar.each_with_object([]) {|x, a| a << "#{x}#{g.inverse(x)}"}
#=>["e→e", "a→a", "b→b", "c→c"] すべての逆元

st = ""
ar.repeated_permutation(2) do |a|
  st += "(#{a[0]}, #{a[1]})→#{op[a[0], a[1]]} "
end
p st
#"(e, e)→e (e, a)→a (e, b)→b (e, c)→c (a, e)→a (a, a)→e (a, b)→c (a, c)→b
# (b, e)→b (b, a)→c (b, b)→e (b, c)→a (c, e)→c (c, a)→b (c, b)→a (c, c)→e "

いい加減に群を定義するとエラーが出る。これは演算については閉じているが、単位元がない。
その他、色々と集合と演算を定義して、群になるか遊んでみて下さい。

ar = %w(a b)
op = lambda {|x, y| x}
g = Group.new(ar, op)   #=>**.rb:**:in `initialize': 単位元が存在しません (RuntimeError)

 
 
※注記
2017/6/30 に全面的に書き直しました。