LoginSignup
9
0

ABC332DをPythonとElixirで復習してみる

Last updated at Posted at 2023-12-13

はじめに

ABC332Dは、順列を全部ためして最小値を求める問題です。

Pythonで復習してみました。

itertoolsを使って書いてみたら、とてもシンプルに記述できました。

Pythonの場合

Pythonで書いたコードはこんな感じです。

inf = 1<<60

H,W = TII()
A = [LII() for _ in range(H)]
B = [LII() for _ in range(H)]

def count_swap(p):
    L = len(p)
    ret = 0
    for i in range(L-1):
        for j in range(i+1,L):
            if p[i]>p[j]:
                ret += 1
    return ret

ans = inf
for ph, pw in product(
    permutations(range(H),H),
    permutations(range(W),W)
):
    for i,j in product(range(H),range(W)):
        if A[ph[i]][pw[j]] != B[i][j]:
            break
    else:
        # print(ph,pw)
        ans = min(ans, count_swap(ph)+count_swap(pw))

if ans == inf:
    ans = -1
print(ans)

H,Wで二重のループになるんですが、productを使うと二重ループを一度に書けるので、気に入っています。
二重のループで書くのと本質的には同じです。

permutations()これが便利なんです。

>>> list(permutations(range(3),3))
[(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]

range(3)を並べ替えたものを返してくれます。

abc332Dでやってほしいことは、この関数がやってくれます。

for文のelseについて、Pythonを使っててもあまり見かけないので補足しておきます。
elseの中は、forのループ中がbreakされずに終了した時だけ実行されます。
今回のような処理にはピッタリです。
332DのPythonの回答としては、整理されたものになってると思います。

Elixirの場合

Pythonのfor文はElixirのfor文と同じでイテレータなので、同じ構造で書けそうです。
permutations自分で実装しようかと思ったんですが、検索してみたら、

素晴らし!
今回は、この関数を使用してみる事にします。

方針

  • 構造はPythonのコードと同じ
  • A,Bの二次元配列は、Tupleでもつ(時間が厳しいのでMapでは間に合わないだろう)
  • count_swapの繰り返し部分は再帰で実現する
defmodule Main do
  import Bitwise
  def next_token(acc \\ "") do
      case IO.getn(:stdio, "", 1) do
        " " -> acc
        "\n" -> acc
        x -> next_token(acc <> x)
      end
  end
  def input(), do: IO.read(:line) |> String.trim()
  def ii(), do: next_token() |> String.to_integer()
  def li(), do: input() |> String.split(" ") |> Enum.map(&String.to_integer/1)

  @inf 100100100

  def count_less_than_head([h|t]) do
      Enum.count(t, fn e -> e < h end)
  end

  def count_swap(p, h \\ 0)
  def count_swap(p, h) when h == tuple_size(p), do: 0
  def count_swap(p, h), do: count_less_than_val(p, h, h+1) + count_swap(p, h+1)

  def count_less_than_val(p, _h,i) when i == tuple_size(p) do
    0
  end

  def count_less_than_val(p, h, i) do
    (if elem(p,h) > elem(p,i), do: 1, else: 0) + count_less_than_val(p, h, i+1)
  end

  def is_matched(h,w,a,b,ph, pw) do
    for i<- 0..h-1, j <-0..w-1, reduce: :true do
      acc ->
        i1 = elem(ph,i)
        j1 = elem(pw,j)
        if a|> elem(i1)|> elem(j1) != b|> elem(i)|> elem(j), do: :false, else: acc
    end
  end

  def to_tuple_2d(list) do
    Enum.map(list, fn x -> List.to_tuple(x) end)
    |> List.to_tuple()
  end

  def solve(h, w, a, b) do

    for ph <- Combinatorics.permutations(0..h-1),
        pw <- Combinatorics.permutations(0..w-1),
        reduce: @inf
    do
      acc ->
        if is_matched(h, w, a, b, ph, pw) do
          min(acc, count_swap(ph) + count_swap(pw))
        else
          acc
        end
    end
    |> then(fn x -> if x == @inf, do: -1, else: x end)
    |> IO.puts()
  end

  def main() do
      h = ii()
      w = ii()
      a = for _ <- 1..h, do: li()
      b = for _ <- 1..h, do: li()

      tuple_a = to_tuple_2d(a)
      tuple_b = to_tuple_2d(b)

      solve(h, w, tuple_a, tuple_b)
  end

end

defmodule Combinatorics do
#~~省略~~  
#https://github.com/antimon2/combinatorics.ex/blob/master/lib/combinatorics.ex
#このコードを使いました
end

1754 msでACできました。

まとめ

  • Pythonのfor文をitertoolsを使うことでシンプルに記述できた
  • この記述がElixirのプログラムでもそのまま表現できる構造だった
  • 関数型プログラミング言語の良いところを感じられる問題だった

参考
https://qiita.com/antimon2/items/bd4c3c2f6bd02654fe10

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