Repository: j178/leetgo (language: python)

Issue:
support better answer judgement
1. judgement for questions without return value, they change the input parameter in-place, like https://leetcode.cn/problems/reorder-list/
2. judgement for random order sequence, like https://leetcode.cn/problems/two-sum/, 


Discussion / hints:
Another kind of problem: the output string is not standard
https://leetcode.cn/problems/linked-list-cycle-ii/

> 1. judgement for questions without return value, they change the input parameter in-place, like https://leetcode.cn/problems/reorder-list/

For this type of questions, there is actually a `paramindex` in questions' metadata:
```
{
  "name": "reorderList",
  "params": [
    {
      "name": "head",
      "type": "ListNode"
    }
  ],
  "return": {
    "type": "void"
  },
  "output": {
    "paramindex": 0
  }
}
```
we can use this info to serialize the corresponding paramater as output.
a new special case: https://leetcode.cn/problems/find-positive-integer-solution-for-a-given-equation/
this function takes a function as first parameter 🤯 
https://leetcode.cn/problems/first-bad-version/
https://leetcode.cn/problems/implement-rand10-using-rand7/

Also there are problems with pre-defined APIs.
We can use a little script to get some hints about these pre-definied function:
<img width="628" alt="image" src="https://user-images.githubusercontent.com/10510431/220006030-6c07f5bc-d018-4b1a-98db-4fb7cdf8d98b.png">


Fix patch:
diff --git a/go.mod b/go.mod
index 9b2c6aa7..bc96494a 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
 	github.com/dop251/goja v0.0.0-20230216180835-5937a312edda
 	github.com/fatih/color v1.15.0
 	github.com/goccy/go-json v0.10.2
+	github.com/grokify/html-strip-tags-go v0.0.1
 	github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b
 	github.com/hexops/gotextdiff v1.0.3
 	github.com/ivanpirog/coloredcobra v1.0.1
diff --git a/go.sum b/go.sum
index 3d19105e..925f4bbc 100644
--- a/go.sum
+++ b/go.sum
@@ -208,6 +208,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGa
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
 github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
+github.com/grokify/html-strip-tags-go v0.0.1 h1:0fThFwLbW7P/kOiTBs03FsJSV9RM2M/Q/MOnCQxKMo0=
+github.com/grokify/html-strip-tags-go v0.0.1/go.mod h1:2Su6romC5/1VXOQMaWL2yb618ARB8iVo6/DR99A6d78=
 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b h1:wDUNC2eKiL35DbLvsDhiblTUXHxcOPwQSCzi7xpQUN4=
 github.com/hako/durafmt v0.0.0-20210608085754-5c1018a4e16b/go.mod h1:VzxiSdG6j1pi7rwGm/xYI5RbtpBgM8sARDXlvEvxlu0=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
diff --git a/lang/go.go b/lang/go.go
index 5efab3aa..66c0b445 100644
--- a/lang/go.go
+++ b/lang/go.go
@@ -69,7 +69,7 @@ func addMod(code string, q *leetcode.QuestionData) string {
 	if q.MetaData.SystemDesign {
 		return code
 	}
-	content, _ := q.GetContent()
+	content, _ := q.GetPreferContent()
 	if !needsMod(content) {
 		return code
 	}
@@ -251,7 +251,7 @@ func (g golang) generateSystemDesignTestCode(q *leetcode.QuestionData) (string,
 %s
 		}
 	}
-	fmt.Println("%s " + JoinArray(output))
+	fmt.Println("%s ", JoinArray(output))
 }
 `
 	var prepareCode string
diff --git a/lang/judge.go b/lang/judge.go
index 88a3b40c..d8d34e83 100644
--- a/lang/judge.go
+++ b/lang/judge.go
@@ -1,10 +1,103 @@
 package lang
 
 import (
+	"strings"
+
+	strip "github.com/grokify/html-strip-tags-go"
+
 	"github.com/j178/leetgo/leetcode"
+	goutils "github.com/j178/leetgo/testutils/go"
 )
 
-func judgeResult(q *leetcode.QuestionData, actual, expected string) bool {
-	// TODO compare by question rules
+type Judger interface {
+	Judge(actual, expected string) bool
+}
+
+type judgeFunc func(actual, expected string) bool
+
+func (f judgeFunc) Judge(actual, expected string) bool {
+	return f(actual, expected)
+}
+
+type systemDesignJudger struct {
+	q *leetcode.QuestionData
+}
+
+func (systemDesignJudger) Judge(actual, expected string) bool {
+	return stringCompare(actual, expected)
+}
+
+func stringCompare(actual, expected string) bool {
 	return actual == expected
 }
+
+type sliceJudger struct {
+	ignoreOrder bool
+}
+
+func newSliceJudger(q *leetcode.QuestionData) *sliceJudger {
+	ignoreOrder := shouldIgnoreOrder(q)
+	return &sliceJudger{ignoreOrder}
+}
+
+func (j *sliceJudger) Judge(actual, expected string) bool {
+	if actual == expected {
+		return true
+	}
+
+	if j.ignoreOrder {
+		return j.compareIgnoringOrder(actual, expected)
+	}
+
+	return false
+}
+
+// TODO improve the detection of "any order"
+func shouldIgnoreOrder(q *leetcode.QuestionData) bool {
+	content := q.GetEnglishContent()
+	content = strip.StripTags(content)
+	// nolint: gosimple
+	if strings.Contains(content, "return the answer in any order") {
+		return true
+	}
+	return false
+}
+
+func (j *sliceJudger) compareIgnoringOrder(actual, expected string) bool {
+	a, _ := goutils.SplitArray(actual)
+	b, _ := goutils.SplitArray(expected)
+	if len(a) != len(b) {
+		return false
+	}
+	cnt := map[string]int{}
+	for _, v := range a {
+		cnt[v]++
+	}
+	for _, v := range b {
+		cnt[v]--
+		if cnt[v] < 0 {
+			return false
+		}
+	}
+	for _, v := range cnt {
+		if v != 0 {
+			return false
+		}
+	}
+	return true
+}
+
+func GetJudger(q *leetcode.QuestionData) Judger {
+	// TODO compare by question rules
+
+	var judger Judger = judgeFunc(stringCompare)
+	if q.MetaData.SystemDesign {
+		judger = &systemDesignJudger{q}
+	} else {
+		resultType := q.MetaData.ResultType()
+		if strings.HasSuffix(resultType, "[]") {
+			judger = newSliceJudger(q)
+		}
+	}
+	return judger
+}
diff --git a/leetcode/question.go b/leetcode/question.go
index 7494e373..81b3af11 100644
--- a/leetcode/question.go
+++ b/leetcode/question.go
@@ -343,7 +343,17 @@ func (q *QuestionData) GetTitle() string {
 	return q.Title
 }
 
-func (q *QuestionData) GetContent() (string, config.Language) {
+func (q *QuestionData) GetEnglishContent() string {
+	if q.Content != "" && !strings.Contains(
+		q.Content,
+		"English description is not available for the problem.",
+	) {
+		return q.Content
+	}
+	return ""
+}
+
+func (q *QuestionData) GetPreferContent() (string, config.Language) {
 	if config.Get().Language == config.ZH && q.TranslatedContent != "" {
 		return q.TranslatedContent, config.ZH
 	}
@@ -357,7 +367,7 @@ func (q *QuestionData) GetContent() (string, config.Language) {
 }
 
 func (q *QuestionData) GetFormattedContent() string {
-	content, lang := q.GetContent()
+	content, lang := q.GetPreferContent()
 
 	// Convert to markdown
 	converter := md.NewConverter("", true, nil)


Test patch:
diff --git a/lang/test.go b/lang/test.go
index 72c69161..0c40bd66 100644
--- a/lang/test.go
+++ b/lang/test.go
@@ -326,6 +326,8 @@ func runTest(q *leetcode.QuestionData, genResult *GenerateResult, args []string,
 		return false, fmt.Errorf("no test cases found")
 	}
 
+	judger := GetJudger(q)
+
 	var ran, passed int
 	for _, c := range tc.Cases {
 		func() {
@@ -389,7 +391,7 @@ func runTest(q *leetcode.QuestionData, genResult *GenerateResult, args []string,
 				return
 			}
 
-			if judgeResult(q, actualOutput, c.Output) {
+			if judger.Judge(actualOutput, c.Output) {
 				passed++
 				l.AppendItem(fmt.Sprintf("Case %d:    %s", c.No, passedStyle.Render("Accepted")))
 			} else {