【paiza】長テーブルのうなぎ屋 (paizaランクB相当) 問題をPythonで実行・解説

# 本投稿の趣旨

以下の問題をpythonでコーディングの解説を行います。

https://paiza.jp/works/mondai/b_rank_skillcheck_sample/long-table

:::note 
問題

東京の下町に長テーブルで有名な老舗うなぎ屋がありました。

そのうなぎ屋にはとても大きい長テーブルがあり、
テーブルの周りにn個の座席が配置されています。
座席には、時計回りに1, 2, …, nと番号が振られています。
座席はテーブルの周りに配置されているので、
座席番号nの座席と1の座席は隣接しています。（下記図を参照）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3780099/8f2cd6b6-0d7a-6b84-e648-8aa3bc4e82f6.png)

今、m個のグループの人達が座席に順番に座りに来ます。
i番目(1≦i≦m)のグループの人数をa_i人とします。
彼らは、長テーブルに並んだ座席のうち、
ある連続するa_i個の座席に一斉に座ろうとします。

ただしお客さんは江戸っ子なので、それら座席のうち、
いずれか一つでも既に先客に座られている座席があった場合、
一人も座らずにグループ全員で怒って帰ってしまいます。
江戸っ子は気が早いんでぃ。

入力では、i番目のグループが座ろうとする連続した座席の位置は、
整数b_iにより指定されます。
i番目のグループは、座席番号b_iの座席を始点として、
そこから時計回りにa_i個分の座席に座ろうとします。

最後のグループが座りに来た後、
無事に長テーブルの座席に着席出来ている人数を出力するプログラムを作成してください。
:::



# コードの要点

(1). 問題を解く関数の作成 **←ここが1番難点**
　(1-1). 座席の状態を管理
　(1-2). グループが座れるかどうかを確認
　(1-3). 座ることができた場合、座席を埋める →(1.1)に戻る
(2). データを入力
(3). (2)の入力データを行ごとに分割
(4). (1)で作成した関数にて(3)のデータを処理

# コード全体 

```ruby:
# (1).solve_edokko_problem関数の作成
def solve_edokko_problem(n, m, groups):
    # (1-1). 座席の状態を管理
    seats = [0] * n
    total_seated = 0

    # (1-2). グループが座れるかどうかを確認
    for a_i, b_i in groups:
        start = b_i - 1
        can_seat = True
        
        for j in range(a_i):
            if seats[(start + j) % n] == 1:
                can_seat = False
                break

        # (1-3). 座ることができた場合、座席を埋める
        if can_seat == True:
            for j in range(a_i):
                seats[(start + j) % n] = 1
            total_seated += a_i
    
    return total_seated

# (2).データの入力（今回はコードに直接入力しているため例）
data = """
12 6
4 6
4 8
4 10
4 12
4 2
4 4
"""

# (3).入力データを行ごとに分割して処理
lines = data.strip().split('\n')
n, m = map(int, lines[0].split())
groups = [tuple(map(int, line.split())) for line in lines[1:]]

# (4).solve_edokko_problem関数を使用して結果を出力
result = solve_edokko_problem(n, m, groups)
print(result)
```
→→→出力結果
12


# コードの解説

###  (1). 問題を解く関数の作成
```ruby:
def solve_edokko_problem(n, m, groups):
    # n: 座席の数
    # m: グループの数
    # groups: 各グループの人数と座席開始位置のリスト

    # seat:座席状態を管理するリスト
    seats = [0] * n
    # 0:空席 1:満席状態
    # n個の座席を持つリストを作成し、すべての座席を空席0で初期化
    
    # 総座席人数を保持する変数
    total_seated = 0

    # あるグループが来店した時の処理
    # a_i:グループの人数
    # b_i:着席開始座席番号
    for a_i, b_i in groups:
        start = b_i - 1 # 座席番号をPythonリストに対応させるため-1しておく

        # グループが座れるかどうかを確認するためのフラグ
        can_seat = True　# 初期値はTrue

        # 座席の状態の確認
        for j in range(a_i):
            # % n は円形テーブルのための剰余演算
            if seats[(start + j) % n] == 1:
                can_seat = False
                break 
                # 座れない場合:seats[(start + j) % n] == 1) でforループは終了
        
        # グループが座れる場合の処理
        if can_seat == True:
            for j in range(a_i):
                seats[(start + j) % n] = 1
            total_seated += a_i # total_seatedに人数を追加
    
    return total_seated
```

上記では、特に難しい構文や関数は使っていません。
ただ、問題の状況を整理し、処理順序を考えられるか、
そしてその順序をFor構文に落とし込んでどれだけ使えるか
という2点で試されました。
（この関数を作成するまでが大変でしたが、ここまで来ればこの問題は8割方終了です。）



### (2).データの入力（今回はコードに直接入力しているため例）
```ruby:
data = """
12 6
4 6
4 8
4 10
4 12
4 2
4 4
"""
```
ここは、m+1行＊2列の行列を入力するだけです。
最初はinputで操作入力しようとしたのですが、行列の入力方法がわからず、
コード内にdataを定義することで使用できるようにしました。

### (3).入力データを行ごとに分割して処理
```ruby:
#文字列のリスト化
lines = data.strip().split('\n')

# 1行目の座席数とグループ数を抽出
n, m = map(int, lines[0].split())
groups = [tuple(map(int, line.split())) for line in lines[1:]]
```

- strip()：文字列の先頭と末尾にある空白文字（スペース、タブ、改行など）を削除
- split('\n') ：文字列を改行文字（\n）で分割し、各行をリストの要素として取得
→これにより、データが行ごとに分割され、各行が1つの要素になります。

上記2つを通してdataは、
['12 6', '4 6', '4 8', '4 10', '4 12', '4 2', '4 4']　
となります。



- lines[0]:1番目の要素（'12 6'）は、座席の数 n とグループの数 m 
- split()：文字列 '12 6' を空白で分割し、['12', '6'] というリストに変換
- map(int, ...) ：リスト ['12', '6'] の各要素を整数に変換

この変換で [12, 6]のようにリストになります。
この操作により、n に 12 を、m に 6 をそれぞれ代入できます。


- tuple：Pythonのデータ構造の一つで、複数の値を一つのまとまりとして扱える

### (4).solve_edokko_problem関数を使用して結果を出力
```ruby:
result = solve_edokko_problem(n, m, groups)
print(result)
```
→→→出力結果
12



これにて、終了です。