-
Notifications
You must be signed in to change notification settings - Fork 0
83. Remove Duplicates from Sorted List #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
|
|
||
|
|
||
| ## 使用言語 | ||
| - Go | ||
|
|
||
| ## STEP1 | ||
| 実装後の後付けになりますが、コンピューターにお願いしたいことを整理しました。 | ||
| - 空の箱を作る | ||
| - 紐でつながっている品物をばらして、箱の中に入れる | ||
| - 箱の中にある品物で、重複している品物は一個にする | ||
| - 紐でつなぎなおす | ||
|
|
||
| 「紐でつなぎなおす」部分の実装がパッと思いつきませんでしたが、 | ||
| 過去に二分木の実装で再帰関数を使用しているケースをみかけたことがあったのを思い出したため、 | ||
| 過去の例を見ながら取り入れました。 | ||
|
|
||
| ```Go | ||
| /** | ||
| * Definition for singly-linked list. | ||
| * type ListNode struct { | ||
| * Val int | ||
| * Next *ListNode | ||
| * } | ||
| */ | ||
| import "slices" | ||
|
|
||
| func deleteDuplicates(head *ListNode) *ListNode { | ||
| var newList *ListNode | ||
| node := head | ||
| sortedList := make([]int, 0) | ||
|
|
||
| for node != nil { | ||
| sortedList = append(sortedList, node.Val) | ||
| node = node.Next | ||
| } | ||
|
|
||
| noDuplicates := slices.Compact(sortedList) | ||
|
|
||
| for _, v := range noDuplicates { | ||
| newList = insert(newList, v) | ||
| } | ||
|
|
||
| return newList | ||
| } | ||
|
|
||
| func insert(l *ListNode, v int) *ListNode { | ||
| if l == nil { | ||
| return &ListNode{Val: v} | ||
| } | ||
|
|
||
| l.Next = insert(l.Next, v) | ||
|
|
||
| return l | ||
| } | ||
| ``` | ||
|
|
||
| 個人的には、「リストの順序は保証されている」という制約が、この関数と強い依存関係あることに引っかかりを覚えまして、この依存関係を分かるようにしないとバグの温床になりそうだなと思いました。 | ||
| 公式パッケージ「slices」の関数「Compact」も、順番が保証されないと重複を削除しないので、`head`の順番が保証されないのなら動かないコードになってしまうと思いました。 | ||
| 順序が保証されていないのであれば、「slices.Compact」の前に「slices.Sort」で箱の中身をソートかけると思います。 | ||
|
|
||
| また、再帰関数は読み解くのに認知負荷が高いと思いました。(自分が例を見て理解するのに時間がかかった為) | ||
|
|
||
|
|
||
| ## STEP2 | ||
| 他解答を見て、下記がシンプルな方法だと感じました。 | ||
| 自分なりの改良として、下記を行いました。 | ||
| - `head`はソート済みであることを指す「sorted」に変更 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この関数の正しさは「同じ値が連続して現れる」前提に依存しています(問題で保証される昇順ソートはそのために十分ですが、必要ではないです)。 ただし変数名でその前提を強調する必要はないと思うので、sortedよりnodeなどの方が読み手に余計な期待を持たせません。sorted := headとあると、以降の実装が.Valの昇順な順序特有のロジックを含むように見える気がします。 つまり問題側でソート済みなのは、同値を連続区間として扱いやすくするためで、ここではその連続区間を1回の走査で圧縮しているだけなので、変数名はnodeなどが無難と考えました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. レビューいただき、ありがとうございます。 関数自体を変えられるのであれば、メソッドにする、あるいは引数の型にソート済みであることの情報を含ませる、などでなるべく関数の外で制限をかける(型で制約をかける)方法を考えていました。 type Sorted *ListNode
// Sorted型に付随する関数
func (s Sorted) DeleteDuplicates(head *ListNode) *ListNode {
...
}
// あるいは、引数の型で制約する
func deleteDuplicates(head Sorted) *ListNode {
...
}この関数はソート済みのリストが与えられないと上手く機能しませんよ、ということを伝えたかった(制約を課したかった)のですが、 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます。
というより、deleteDuplicatesで入力されるheadの連結リストは必ずしもソート済みでなくてもいいと自分は思っています。
そうでしょうか?必ずしもソート済みでなくても上手く機能したと思える入力は自分的にはあります。2つ例を挙げます。
そしてStep 2の実装も実際このように動くと思いますが、その上でsortedと命名するのは個人的には変数に対する制約として強すぎるかなと感じた次第です。特に、1つ目は上手く機能すると言える範疇ではないでしょうか。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ご返信ありがとうございます! また、この制約の背景としては、複数人で開発するときを想定して、制約を知らない、あるいはうっかり忘れた人がこの関数を使ったらどうなるか、を考えていました。 ただ、個人的には、リストがどうであれ、重複はすべて消去されてほしい(ある時は消去されて、ある時は消去されないみたいに出力がブレてほしくない)ですが、その代わり柔軟性は損なわれますね。 大変貴重なご意見、ありがとうございます🙇♂️ |
||
| - `else`の代わりに`continue`を使用 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. elseを使うことで対比的にかけるのはcontinueにない良さと思います。 ケースバイケースですが、本問についてはどっちでもいいかなと感じます。 if sorted.Val == sorted.Next.Valが、例外的であり、先に処理しておく、と考えるならcontinueであり、==の場合と!=の場合の、場合分けをしているという気持ちならelseですね。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. あとは、以下のようにも書けます。 func deleteDuplicates(head *ListNode) *ListNode {
sorted := head
for sorted != nil {
for sorted.Next != nil && sorted.Val == sorted.Next.Val {
sorted.Next = sorted.Next.Next
}
sorted = sorted.Next
}
return head
}
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 例を挙げていただきありがとうございます。 |
||
|
|
||
| ```Go | ||
| func deleteDuplicates(head *ListNode) *ListNode { | ||
| sorted := head | ||
|
|
||
| for sorted != nil && sorted.Next != nil { | ||
| if sorted.Val == sorted.Next.Val { | ||
| sorted.Next = sorted.Next.Next | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. インデントにタブとスペースが混在しているように思います。統一されることをおすすめします。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. レビューいただき、ありがとうございます。 |
||
| continue | ||
| } | ||
| sorted = sorted.Next | ||
| } | ||
|
|
||
| return head | ||
| } | ||
| ``` | ||
|
|
||
| 順序が絶対保証されているのであれば、この方法がシンプルで覚えやすいと思いました。 | ||
| 「sorted.Next」がポインタであることが抜けてましたので、そこに気が付けば、headを返す理由の理解が進みました。 | ||
|
|
||
|
|
||
| ## STEP3 | ||
| 10分以内に3回何も見ないでコードを書けました。 | ||
| ```Go | ||
| func deleteDuplicates(head *ListNode) *ListNode { | ||
| sorted := head | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. メソッド名のdeleteDuplicatesという文脈からsortedという単語が出てきたときに混乱してしまいました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. レビューいただき、ありがとうございます。 |
||
|
|
||
| for sorted != nil && sorted.Next != nil { | ||
| if sorted.Val == sorted.Next.Val { | ||
| sorted.Next = sorted.Next.Next | ||
| continue | ||
| } | ||
| sorted = sorted.Next | ||
| } | ||
|
|
||
| return head | ||
| } | ||
| ``` | ||
|
|
||
|
|
||
| ## STEP4 | ||
| テストを作成するのに時間がかかりました。 | ||
| 特に、ランダムに数を生成しその値をソートしてListNodeの形にするところが、 | ||
| 難航しました。 | ||
|
|
||
|
|
||
|
|
||
| - 時間計算量:O(n) | ||
| - 空間計算量:O(1) | ||
|
|
||
| **Constraints** | ||
| > - The number of nodes in the list is in the range [0, 300]. | ||
| > - 100 <= Node.val <= 100 | ||
| > - The list is guaranteed to be sorted in ascending order. | ||
|
|
||
|
|
||
|
|
||
| ``` | ||
| Benchmark | ||
|
|
||
| goos: windows | ||
| goarch: amd64 | ||
| pkg: bench | ||
| cpu: Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz | ||
| BenchmarkDeleteDuplicates-8 5955951 181.4 ns/op 0 B/op 0 allocs/op | ||
| PASS | ||
| ok bench 1.633s | ||
| ``` | ||
|
|
||
| - BenchmarkDeleteDuplicates-8 | ||
| ベンチマーク名 - ベンチマークのGOMAXPROCSの値 | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package bench | ||
|
|
||
| // Definition for singly-linked list. | ||
| type ListNode struct { | ||
| Val int | ||
| Next *ListNode | ||
| } | ||
|
|
||
| func DeleteDuplicates(head *ListNode) *ListNode { | ||
| sorted := head | ||
|
|
||
| for sorted != nil && sorted.Next != nil { | ||
| if sorted.Val == sorted.Next.Val { | ||
| sorted.Next = sorted.Next.Next | ||
| continue | ||
| } | ||
| sorted = sorted.Next | ||
| } | ||
|
|
||
| return head | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package bench | ||
|
|
||
| import ( | ||
| "math/rand" | ||
| "slices" | ||
| "testing" | ||
| "time" | ||
| ) | ||
|
|
||
| var blackhole *ListNode | ||
|
|
||
| const ( | ||
| MAXVALUE = 100 | ||
| MINVALUE = -100 | ||
| MAXRANGE = 300 | ||
| HEADPOSITION = 0 | ||
| ) | ||
|
|
||
| func makeList(size int) *ListNode { | ||
| var nodeValue int | ||
| nodes := make([]*ListNode, size) | ||
| list := make([]int, 0) | ||
|
|
||
| // シード値の設定 | ||
| rand.New(rand.NewSource(time.Now().UnixNano())) | ||
|
|
||
| for i := 0; i < size; i++ { | ||
| // 半開区間[0,201)の乱数(0~200の間) - 100 | ||
| nodeValue = rand.Intn(MAXVALUE-MINVALUE+1) + MINVALUE | ||
| list = append(list, nodeValue) | ||
| } | ||
|
|
||
| slices.Sort(list) | ||
|
|
||
| for i, v := range list { | ||
| nodes[i] = &ListNode{Val: v} | ||
| if 0 < i { | ||
| // 一個前のnodeのNextは、現在のnode | ||
| nodes[i-1].Next = nodes[i] | ||
| } | ||
| } | ||
|
|
||
| return nodes[HEADPOSITION] | ||
| } | ||
|
|
||
| func BenchmarkDeleteDuplicates(b *testing.B) { | ||
| nodes := makeList(MAXRANGE) | ||
|
|
||
| b.ResetTimer() | ||
| for i := 0; i < b.N; i++ { | ||
| result := DeleteDuplicates(nodes) | ||
| blackhole = result | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| module bench | ||
|
|
||
| go 1.24.2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
入力を破壊する(in-place)か否か、という観点もあります。
Step 2のコードはin-placeになっています。入力が返ってくる関数で引数にも変更が起きると使用者はびっくりするかもしれません。(必ず悪、という意味ではないです)