2024.6.24

Paiza様より、「レベルアップ問題集 ソートメニュー応用編 タプルソート Python3編」の考察

Paiza様「レベルアップ問題集 ソートメニュー応用編 タプルソート Python3編」、より問題文です。

            整数 n, m, k と n 行 m 列の表 a が与えられます。以下の条件をすべて満たすように、 a を行単位でソートしてください。
            ・ a の k 列目が昇順になっている
            ・ a の k 列目の値が等しい 2 つの行では、 a の 1 列目の値が昇順になっている
            ・ a の k 列目 と a の 1 列目から i 列目までのすべての値が等しい 2 つの行では、 a の i + 1 列目の値が昇順になっている ( 1 ≦ i ≦ m - 1 )
            
要約すると、初めにk列目で昇順にソートし、ソート済みの列の値が重複する行がある時は、値が重複する行が無くなるまで、1番目の列から順次最終列まで昇順にソートし直す、という感じです。
工夫すれば既存のsort()関数だけで解決できますが、多重にソートするのはどうやるのかな?と考えてしまい、独自にdeep_sortなる関数の自作を試みました。
            
a = []

n , m , k = map(int , input().split())

for _ in range(n):
    
    temp = list(map(int , input().split()))
    a.append(temp)                              # ここまでは標準入力

a.sort(key = lambda x: x[k - 1])                # K列目をソート この時点でソート順を変えてソートし、後で元に戻せばいいと気付けば賢いですね



def deep_sort(x , m , base , init , compare):           # x:ソートする行の範囲 m:列数 base:ソート範囲の最初の行番号 init:最後にソートした列番号 compaire:ソートする列番号

    skip = 0                                            # ソート後、重複部分を再ソートするのですが、重複しなかった部分はソートしないのでスキップ、みたいな意味でskip

    while skip + 1 <= x and compare <= m:               # ソート範囲を超えないようにx以下、ソートする列番号が列数を超えないようにm以下
    
        count = 0                                       # 重複部分を数えるためのcount
    
        if a[base + skip][init] == a[base + skip + 1][init]:
            
            while skip + 1 + count <= x and a[base + skip][init] == a[base + skip + 1 + count][init]:
                
                count += 1                                                                               # ソート範囲に重複部分があればカウント

            for i in range(count):
                
                for j in range(count - i):
                    
                    if a[base + skip + j][compare] > a[base + skip + 1 + j][compare]:
                        
                        a[base + skip + j] , a[base + skip + 1 + j] = a[base + skip + 1 + j] , a[base + skip + j]   # 重複部分を、ソートする列でソート ここでは簡単にバブルソート  
                    
            deep_sort(count , m , base + skip , compare , compare + 1)                                              # ソートした列の重複部分を再ソート

        else:
            
            skip += 1                    # 重複していなければスキップを数えます
    
        skip += count                    # カウントした部分はソート済みなので、skipに加算してしまいます                        
            
            
deep_sort(n - 1 , m - 1 , 0 , k - 1 , 0)     # 最初のdeep_sort ソート範囲はリストが0始まりのためnー1 同様に列数はm−1 最初にソートする行番号は0 最後にソートしたのはk-1列目 次にソートするのは0列目

for i in a:
    
    print(*i)                                    # 最後はPython風にprint
            
            
こんな具合でうまくいきましたが、変数の範囲を適切にするのが難しく、私の余暇時間を1週間使ってしまいました。
Paiza様の解答例はこんな可笑しな方法な訳はなく、k番目の列を一旦先頭に持ってきて、sort()し、後でまた先頭をk番目に挿入し直す、当たり前な方法でした。
そもそもsort()関数がほとんど同じ結果を出力するので、ソートをする関数を自作する必要はありません。
解答例を拝見したところ、私なりに閃きがあったので、Paiza様の解答例を少しアレンジしてみました。
            
n, m, k = map(int, input().split())
k -= 1

a = []
for i in range(n):
    row = list(map(int, input().split()))
    a.append(row)

a.sort(key=lambda x: ([x[k]] , x[:k] , x[k + 1:]))

for i in a:
    print(*i)
            
            
リストをスライスし、lambda関数を用いて、ソート順を順序良く並べればうまくいきました。
簡単ですね。
因みに実行時間は自作ソートとほとんど同じでした。