Pythonでリストからユニークな要素だけ削除する方法

Python Non-unique Python

今日もPythonの問題を解いていきましょう。
今回の問題はこちら。

問題(Non-unique Elements

空でない整数のリストからユニークな要素を取り除いたリストを返す関数を作る。
リストの順番を変更してはいけない。

例)[1,2,3,1,3]を入力すると[1,3,1,3]を返す。

僕のコード

def checkio(data: list) -> list:
    delcnt = 0
    
    for i in range(len(data)):
        cnt = 0
        for j in range(len(data)):
            if data[i] == data[j]:
                cnt += 1
        if cnt == 1:
            data[i]="*"
            delcnt += 1

    for k in range(delcnt):
        data.remove("*")

    return data

考えたこと

最初は、popメソッドでユニークな要素を削除する方法を考えました。
しかし、For文でリストの要素数だけ回しているため、要素数が減ると問題が発生する気がします。

ユニークな要素はとりあえず目印として”*”で置き換えておくことにしました。
さらに、ユニークな要素の数をカウントしておきます。

removeメソッドは指定した値と同じ要素を削除できます。
先程置き換えた”*”を指定すれば削除できますね。

ただし、removeメソッドは検索に引っかかった最初の要素しか削除してくれません。
そのため、ユニークな要素の数だけループさせています。

室井
オリジナリティ溢れるコードになりました!
みゅう
無駄な処理が多いだけでは…

改善点

2重ループを使って各要素の個数を調べましたが、全く同じことをしてくれるメソッドがありました。
countメソッドです。

これを使えば処理を短くして、if文の条件分岐の中で書くことができそうです。
使用する変数も1つ減らすことができます。

改善したコードがこちらです。

def checkio(data: list) -> list:
    delcnt = 0
    
    for i in range(len(data)):
        if data.count(data[i]) == 1:
            data[i]="*"
            delcnt += 1

    for k in range(delcnt):
        data.remove("*")

    return data

4行ほど短くすることができました。

しかし、一旦”*”で置き換える処理はどう考えても無駄があります。
まだまだ改善の余地がありそうですが、現時点の僕の知識ではこのコードが限界です。

While文で書けばpopやらdeleteやら使えそうな気もしますが、面倒になってきました。

室井
間違ってはいないから、こんなもんでいいよね!

定番の回答

この問題の定番の書き方は下の2行のようです。

def checkio(data):
    return [i for i in data if data.count(i) > 1]

たった、2行で書けるんですね。
僕のコードの5分の1しかないじゃないですか…

処理の内容は推測できるけど、僕の知らない書き方です。
リスト内包表記という書き方らしいですよ。

forでdataリストの要素を順番にリストに入れるけど、ifでユニークな要素は弾いています。

あとがき

できる人のコードは読んでいて美しいです。
僕のコードを見直すとクソみたいにダサいなという感想しか出てきません。

が、きっと最初はみんなこんなもんでしょう。
まだ入門書数冊読んだだけですから、知らないものは書けません。

できる人の技術を吸収していけるよう頑張って勉強していきます。