Skip to content
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b02e347
feat : #10 add the empty file of "20. Valid Parentheses"
Apr 7, 2025
2a2aed0
feat : #10 test commit with new gpg signkey
syoshida20 Apr 8, 2025
8a197ff
feat : #10 finish the STEP1/STEP2/STEP3 of "20. Valid Parentheses"
syoshida20 Apr 9, 2025
9ee3225
feat : #10 add the impression of "20. Valid Parentheses"
syoshida20 Apr 9, 2025
d9b24ea
feat : #10 add the impression of "20. Valid Parentheses"
syoshida20 Apr 9, 2025
e671954
Merge remote-tracking branch 'origin/main' into feature/stack/valid-p…
syoshida20 Apr 9, 2025
b24bea8
feat : #10 add unnecessary comment in "20. Valid Parentheses"
syoshida20 Apr 9, 2025
cbea1b2
feat : #10 add unnecessary comment in "20. Valid Parentheses"
syoshida20 Apr 9, 2025
cb778a8
Merge remote-tracking branch 'origin/main' into feature/stack/valid-p…
syoshida20 Apr 9, 2025
96aa98b
feat : #10 update the comment
syoshida20 Apr 17, 2025
38c934f
feat : #10 invalid input error handling
syoshida20 Apr 20, 2025
06b0afe
feat : #10 add the javascript comment in each script
syoshida20 Apr 20, 2025
807fd0d
feat : #10 add the javascript code in each script
syoshida20 Apr 20, 2025
f9ece5c
feat : #10 add the javascript code in each script
syoshida20 Apr 20, 2025
18dbce1
feat : #10 add the javascript code in each script
syoshida20 Apr 20, 2025
6c05c9f
feat : #10 add the javascript code in each script
syoshida20 Apr 20, 2025
cad2d1c
feat : #10 add the javascript code in each script
syoshida20 Apr 20, 2025
ee3aaa5
feat : #10 add the code after the comment(handle pop() functionwith e…
syoshida20 Apr 20, 2025
8aeb275
feat : #10 make the markdown clean
syoshida20 Apr 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
373 changes: 373 additions & 0 deletions stack/valid-parentheses/answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,373 @@
# 20. Valid Parentheses

## STEP 1

### 手作業でやってみる。

* `{`, `[`, `(`, `}`, `]`, `)` と書いた紙が一列に並んでいる
* 一番最初の紙から作業をしていく。
* 紙が`{`, `[`, `(` だったら、机の上に縦に積んでいく。
* 紙が`}`, `]`, `)` だったら、机の上の一番上から紙を取り、同じ種類の紙かを確認する。
* 作業が完了して、机の上に何も残っていない場合には、終了する。
* 机の上に何か残っている場合には、異常を報告する。

```javascript
var isValid = function(characters) {
Copy link

@huyfififi huyfififi Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"aaa"みたいな文字列が入力された際にtrueを返しそうですね。LeetCodeではそこまで予期せぬ入力について考えなくても良いでしょうし、JavaScriptのベストプラクティスはよくわからないのですが、予期せぬ値が来た時に true or falseを返してしまうと、本当は意図しない動作なのに関数を呼び出した側からしたらうまくいったように見えてわかりづらいので、業務ではエラーを吐いた方が逆に親切な気がします。

Copy link
Owner Author

@syoshida20 syoshida20 Apr 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

38c934f でエラーハンドリングのコードを追加しました

const container = []
const open_bracket_chars = ["(", "{", "["]
for (const character of characters) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bracketなどの命名でもいいのかなと思いました。
characterだと予約語として存在しそうで、引っかかりました。
※Javaには大文字ですが、Characterクラスが存在しております。

Copy link
Owner Author

@syoshida20 syoshida20 May 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

予約語を変数名として避けるという意識ができておりませんでした。ありがとうございます。
確かに、charactersにはbracketの文字が入るという前提条件があるので、bracketでも良いですね。

@nodachipのコメントだと、C++の予約語を避けて、charを避けて ch, c, characterを選ぶというコメントを見つけました。言語によってもよく使う変数名があるというのが気付きでした。

以下がJavasciprtの予約語だそうです。

await break case catch class const continue debugger default
delete do else enum export extends false finally for function if
import in instanceof new null return super switch this throw true try typeof var void while with yield

https://262.ecma-international.org/#sec-keywords-and-reserved-words

Javaだと charも予約語なのですね。
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html

if (open_bracket_chars.includes(character)) {
container.push(character)
}
if (character === ")") {
const last_character = container.pop()
if (last_character !== "(") {
return false
}
}
if (character === "]") {
const last_character = container.pop()
if (last_character !== "[") {
return false
}
}
if (character === "}") {
const last_character = container.pop()
if (last_character !== "{") {
return false
}
}
}
if (container.length === 0) {
return true
}
return false
};
```
## STEP 2

### やったこと
* 関数を分けた.

```javascript
function doesMatchBracket(candidate, close_bracket) {
if (candidate === "[" && close_bracket === "]") {
return true
}
if (candidate === "{" && close_bracket === "}") {
return true
}
if (candidate === "(" && close_bracket === ")") {
return true
}

return false
}
var isValid = function(characters) {
const container = []
const open_bracket_chars = ["(", "{", "["]

for (const character of characters) {
// 開き括弧の場合
if (open_bracket_chars.includes(character)) {
container.push(character)
continue
}

// 閉じ括弧の場合
const open_bracket_candidate = container.pop()
const close_bracket = character
if (!doesMatchBracket(open_bracket_candidate, close_bracket)) {
return false
}
}
if (container.length === 0) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return container.length === 0でいいかなと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かにその方が読みやすいですね。 ありがとうございます。

return true
}

return false
};
```

## STEP 3

```javascript
function doesMatchBracket(candidate, close_bracket_character) {
if (candidate === "(" && close_bracket_character === ")") {
return true
}
if (candidate === "[" && close_bracket_character === "]") {
return true
}
if (candidate === "{" && close_bracket_character === "}") {
return true
}
return false
}
var isValid = function(characters) {
const container =[]
const open_bracket_characters = ["(", "[", "{"]

for (const character of characters) {
if (open_bracket_characters.includes(character)) {
container.push(character)
continue
}
const open_bracket_candidate = container.pop()
Copy link

@huyfififi huyfififi Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ご自身でご指摘の通り、containerが空の時open_bracket_candidate = undefinedとなって、それでも続く処理 (doesMatchBracket)は問題なく進むのでしょうが、open_bracket_candidatestringである時とundefinedである時の2パターンの型を考えなければいけないのが、個人的にはやや負荷に感じました。containerが空だったとき早めにfalseを返してしまう方が親切かもしれません。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

確かに早めにreturnする方が脳の負荷が減りますね。

ee3aaa5

const close_bracket_character = character
if (!doesMatchBracket(open_bracket_candidate, close_bracket_character)) {
return false
}
}

if (container.length === 0) {
Copy link

@huyfififi huyfififi Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return container.length === 0の方が、同じ条件を表現できている上、4行も読まなくてもいいので個人的にはわかりやすく感じます

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

僕も同感です!

return true
}
return false
};
```

## 感想

### 他の人のコードを読んで


* BumbuShoji のPR https://github.com/BumbuShoji/Leetcode/pull/7
* 開き括弧と閉じ括弧の対応関係を表すMapを用意することも可能。 (`*1`で解法を追加)

* はじめに、閉じ括弧があるケースを想定できていなかった。
* 配列が要素数0の時に、pop()で、undefinedを返すため、たまたま上手く行った。
参考 : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop#return_value

```javascript
// BEFORE
const open_bracket_candidate = container.pop()
```

```javascript
// AFTER
const open_bracket_candidate = container.pop() || ""
const open_bracket_candidate = container.length > 0 ? container.pop() : ""
```

* 最後のif文は、`return container.length === 0`、`return !arr.length` でも良い

```javascript
// BEFORE
if (container.length === 0) {
return true
}
return false
```

```javascript
// AFTER
return container.length === 0
```

* Odaのコメント https://discord.com/channels/1084280443945353267/1225849404037009609/1231646037077131315
* 不正な入力に対して、エラーハンドリングをどうするかという視点がなかった。
https://github.com/lilnoahhh/leetcode/pull/7#discussion_r1948110757

> open_to_close でデータは持ちたいです。文字列にカッコ以外が来たときに落ちないで欲しいからです。

* 括弧以外の文字が入ってきた際のあるべきは、エラーログを吐き、exit 1で異常終了することと考えた。 https://discord.com/channels/1084280443945353267/1225849404037009609/1231648833914802267
* 異常終了、続行する(例外を投げる、特殊な値を返す)の選択肢を意識して、選べると良い。
* 異常終了
* エラーに気づきやすい。 https://github.com/mura0086/arai60/pull/11#discussion_r1986104852
* 重要なエラー
* 続行
* 重要でないエラー

参考 : https://discord.com/channels/1084280443945353267/1226508154833993788/1227171332131786774

* 異常な入力への対応を頭に入れてコードを書く。
参考 : https://discord.com/channels/1084280443945353267/1316770883729100810/1335077966954369095

* 質問されたら、結果と選択肢と理由を回答できる状態にする。

* lilnoahhhのPR https://github.com/lilnoahhh/leetcode/pull/7
* Stringで判定する方法がある。

```javascript
// BEFORE
const open_bracket_chars = ["(", "{", "["]
if (open_bracket_chars.includes(character)) {
//
}
```

```javascript
// AFTER
const open_brackets = "{(["
if (open_brackets.includes(character)) {
//
}
```


* SanakoMeine のPR https://github.com/SanakoMeine/leetcode/pull/7
* 簡潔で読みやすい.

## その他の解法

* `*1` 括弧の対応関係を表すMapを使う方法

```javascript
var isValid = function(characters) {
const open_to_close = new Map()
open_to_close.set("(", ")")
open_to_close.set("{", "}")
open_to_close.set("[", "]")

const container =[]
for (const character of characters) {
if (open_to_close.has(character)) {
container.push(character)
continue
}
const converted_last_character = open_to_close.get(container.pop())
Copy link

@huyfififi huyfififi Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit データをある形から違う形に変換 (convert) している、というよりかは対応する括弧をとってきているので、converted_*よりは expected_closingのようにして if (character == expected_closing) {としてしまう方がわかりやすく感じました。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

変数名と内容が合致していなかったですね!

以下の変数名もあるよと、ChatGPTが教えてくれました。

  • corresponding_*
  • matching_*

const close_bracket_character = character
if (converted_last_character !== close_bracket_character) {
return false
}
}
return container.length === 0
};
```

* `*2` 番兵をおく方法
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python の場合は、空であったときに pop すると undefined が返らずに IndexError になります。
JavaScript は、Map も undefined が返るので、自動的に番兵になっているともいえるかと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントありがとうございます。

今後は、スクリプト言語のPythonの仕様も確認して、言語間の違いも意識しようと思います。


* 以下の方法でさらに簡潔にかける.

```javascript
const bracket_pairs = new Map([
["{", "}"],
["(", ")"],
["[", "]"],
["*", ""],
])
const container = ["*"]
```

```javascript
function doesMatchBracket(candidate, close_bracket_character) {
if (candidate === "(" && close_bracket_character === ")") {
return true
}
if (candidate === "[" && close_bracket_character === "]") {
return true
}
if (candidate === "{" && close_bracket_character === "}") {
return true
}
return false
}
var isValid = function(characters) {
const container =[]
const open_bracket_characters = ["(", "[", "{"]
container.push("SENTINEL")

for (const character of characters) {
if (open_bracket_characters.includes(character)) {
container.push(character)
continue
}
const open_bracket_candidate = container.pop()
const close_bracket_character = character
if (!doesMatchBracket(open_bracket_candidate, close_bracket_character)) {
return false
}
}
return container.length === 1 && container[container.length-1] === "SENTINEL"
Copy link

@huyfififi huyfififi Apr 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このようなやり方もあるんですね、興味深いです 💡
これは個人的な感想ですが、このような 尖兵? 番兵の値がどのような処理 (doesMatchBracket)を通っていくのか想像するのがやや難しく感じるので、読む際に覚えることを減らす意味でも line 274 で if (container.length === 0) return falseとreturn earlyした方がわかりやすいなと思います。が、私の感覚が一般のものであるかどうかは自信がありません。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huyfififi

mapを使った場合には、Mapのkeyでundefinedが入るケースを検討しないといけなかったのでreturn earlyが有効だと思ったのですが、

今回だとdoesMatchBracketに SENTINELが渡されるケースでfalseが返却されることは自明なので、僕の意見としてはどちらでも良いのかなと思いました。

};
```

* `*3` 不正な入力のエラーハンドリングを行う解法
* 正常終了(エラーログ)と異常終了の選択肢のうち、異常終了を選択する。

```javascript
const isValid = function(characters) {
const open_to_close = new Map([
["{", "}"],
["[", "]"],
["(", ")"]
])
const container = []
const expected_characters = ["(", ")", "{", "}", "[", "]"]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

些細ですが、エラーメッセージで"Invalid character"と書いてあるので、変数名もvalid_charactersでよいのかなと思いました!

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalidかどうかの判定をするための配列なので、valid_charactersの方がexpected_charactersよりも読みやすいですね!

for (const character of characters) {
if (!expected_characters.includes(character)) {
throw new Error(`Invalid character. Expected : ${expected_character.join(",")}. Current : ${character}`) // UPDATE
}
if (open_to_close.has(character)) {
container.push(character)
continue
}
const expected_close_bracket = open_to_close.get(container.pop())
const close_bracket_character = character
if (expected_close_bracket !== close_bracket_character) {
return false
}
}
return container.length === 0
};
```

### レビューを受けて

#### レビューコメント1

* 変更前

```javascript
const isValid = function(characters) {
const open_to_close = new Map([
["{", "}"],
["[", "]"],
["(", ")"]
])
const container = []
const expected_characters = ["(", ")", "{", "}", "[", "]"]
for (const character of characters) {
if (open_to_close.has(character)) {
container.push(character)
continue
}
const expected_close_bracket = open_to_close.get(container.pop())
const close_bracket_character = character
if (expected_close_bracket !== close_bracket_character) {
return false
}
}
return container.length === 0
};
```

* 変更後 (conatiner.pop()をする際にundefinedとstringの可能性を考慮する必要がある。)

```javascript
const isValid = function(characters) {
const open_to_close = new Map([
["{", "}"],
["[", "]"],
["(", ")"]
])
const container = []
const expected_characters = ["(", ")", "{", "}", "[", "]"]
for (const character of characters) {
if (open_to_close.has(character)) {
container.push(character)
continue
}
// 箱の中身が空で、閉じ括弧が挿入された際には、falseを返す。
if (container.length === 0) {
return false
}
const expected_close_bracket = open_to_close.get(container.pop())
const close_bracket_character = character
if (expected_close_bracket !== close_bracket_character) {
return false
}
}
return container.length === 0
};