Skip to content

Commit f90f9c7

Browse files
committed
Add sort tests
Signed-off-by: James Hamlin <jfhamlin@gmail.com>
1 parent 2c969aa commit f90f9c7

File tree

4 files changed

+266
-11
lines changed

4 files changed

+266
-11
lines changed

internal/persistent/vector/vector.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ type Vector interface {
4646

4747
// Iterator is an iterator over vector elements. It can be used like this:
4848
//
49-
// for it := v.Iterator(); it.HasElem(); it.Next() {
50-
// elem := it.Elem()
51-
// // do something with elem...
52-
// }
49+
// for it := v.Iterator(); it.HasElem(); it.Next() {
50+
// elem := it.Elem()
51+
// // do something with elem...
52+
// }
5353
type Iterator interface {
5454
// Elem returns the element at the current position.
5555
Elem() interface{}

pkg/lang/slices.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,17 @@ func ToSlice(x any) []any {
4242
return res
4343
}
4444

45+
// Handle Set - convert to array of values
46+
if s, ok := x.(*Set); ok {
47+
seq := s.Seq()
48+
res := make([]any, 0, s.Count())
49+
for seq != nil {
50+
res = append(res, seq.First())
51+
seq = seq.Next()
52+
}
53+
return res
54+
}
55+
4556
// Handle string - convert to character array
4657
if s, ok := x.(string); ok {
4758
runes := []rune(s) // Important: use runes for proper Unicode handling

pkg/lang/sort.go

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import (
1010
// - Stable sort (equal elements maintain their relative order)
1111
// - In-place modification of the array
1212
// - Comparator returns -1 for less than, 0 for equal, 1 for greater than
13-
func SortSlice(slice []any, comp any) error {
13+
func SortSlice(slice []any, comp any) {
1414
// comp is a Clojure function that acts as a comparator
1515
compFn, ok := comp.(IFn)
1616
if !ok {
@@ -22,19 +22,24 @@ func SortSlice(slice []any, comp any) error {
2222
// Call the comparator function with the two elements
2323
result := compFn.Invoke(slice[i], slice[j])
2424

25-
// Comparator returns:
25+
// Handle both boolean and numeric comparators
26+
// Boolean comparator: returns true if i < j
27+
// Numeric comparator: returns negative if i < j
28+
if boolResult, ok := result.(bool); ok {
29+
return boolResult
30+
}
31+
32+
// Numeric comparator returns:
2633
// -1 if first arg is less than second
2734
// 0 if args are equal
2835
// 1 if first arg is greater than second
2936
// We return true for "less than" case
3037
resultInt, ok := AsInt(result)
3138
if !ok {
32-
panic(NewIllegalArgumentError(fmt.Sprintf("Comparator must return a number, got %T", result)))
39+
panic(NewIllegalArgumentError(fmt.Sprintf("Comparator must return a boolean or number, got %T", result)))
3340
}
3441
return resultInt < 0
3542
})
36-
37-
return nil
3843
}
3944

4045
// Compare implements Clojure's compare function.
@@ -87,8 +92,8 @@ func Compare(x, y any) int {
8792
}
8893

8994
// Handle symbols
90-
if xSym, xOk := x.(Symbol); xOk {
91-
if ySym, yOk := y.(Symbol); yOk {
95+
if xSym, xOk := x.(*Symbol); xOk {
96+
if ySym, yOk := y.(*Symbol); yOk {
9297
// Compare namespace first
9398
nsComp := Compare(xSym.Namespace(), ySym.Namespace())
9499
if nsComp != 0 {
@@ -99,6 +104,44 @@ func Compare(x, y any) int {
99104
}
100105
}
101106

107+
// Handle characters
108+
if xChar, xOk := x.(Char); xOk {
109+
if yChar, yOk := y.(Char); yOk {
110+
if xChar < yChar {
111+
return -1
112+
} else if xChar > yChar {
113+
return 1
114+
}
115+
return 0
116+
}
117+
}
118+
119+
// Handle vectors (including MapEntry which is a vector)
120+
if xVec, xOk := x.(IPersistentVector); xOk {
121+
if yVec, yOk := y.(IPersistentVector); yOk {
122+
xCount := xVec.Count()
123+
yCount := yVec.Count()
124+
minCount := xCount
125+
if yCount < minCount {
126+
minCount = yCount
127+
}
128+
// Compare element by element
129+
for i := 0; i < minCount; i++ {
130+
cmp := Compare(xVec.Nth(i), yVec.Nth(i))
131+
if cmp != 0 {
132+
return cmp
133+
}
134+
}
135+
// If all compared elements are equal, shorter vector is less
136+
if xCount < yCount {
137+
return -1
138+
} else if xCount > yCount {
139+
return 1
140+
}
141+
return 0
142+
}
143+
}
144+
102145
// If we can't compare, panic with an error
103146
panic(NewIllegalArgumentError(fmt.Sprintf("Cannot compare %T with %T", x, y)))
104147
}

test/glojure/test_glojure/sort.glj

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
;; Tests for sort function
2+
;; Ensures Glojure behavior matches Clojure
3+
4+
(ns glojure.test-glojure.sort
5+
(:require [glojure.test :refer :all]
6+
[glojure.string :as s]))
7+
8+
(deftest test-sort-basic
9+
(testing "Basic sort functionality"
10+
;; Numbers
11+
(is (= '(1 2 3 4 5) (sort [3 1 4 2 5])))
12+
(is (= '(1 1 3 4 5) (sort [3 1 4 1 5]))) ; duplicates preserved
13+
(is (= '(1.0 2.5 3 4.7) (sort [4.7 1.0 3 2.5]))) ; mixed numeric types
14+
15+
;; Strings
16+
(is (= '("apple" "banana" "cherry") (sort ["cherry" "apple" "banana"])))
17+
(is (= '("" "a" "ab" "b") (sort ["b" "a" "" "ab"]))) ; empty string sorts first
18+
19+
;; Keywords
20+
(is (= '(:a :b :c) (sort [:c :a :b])))
21+
(is (= '(:a/x :b/x :c/x) (sort [:c/x :a/x :b/x]))) ; namespaced keywords
22+
23+
;; Symbols
24+
(is (= '(a b c) (sort '[c a b])))
25+
(is (= '(a/x b/x c/x) (sort '[c/x a/x b/x]))) ; namespaced symbols
26+
27+
;; Empty collection
28+
(is (= '() (sort [])))
29+
(is (= '() (sort '())))
30+
(is (= '() (sort nil))) ; nil returns empty seq
31+
32+
;; Single element
33+
(is (= '(42) (sort [42])))
34+
35+
;; Already sorted
36+
(is (= '(1 2 3) (sort [1 2 3])))
37+
38+
;; Reverse sorted
39+
(is (= '(1 2 3) (sort [3 2 1])))))
40+
41+
(deftest test-sort-with-comparator
42+
(testing "Sort with custom comparator"
43+
;; Reverse sort
44+
(is (= '(5 4 3 2 1) (sort (fn [a b] (compare b a)) [3 1 4 5 2])))
45+
(is (= '(5 4 3 2 1) (sort > [3 1 4 5 2]))) ; using > as comparator
46+
47+
;; Case-insensitive string sort
48+
(is (= '("apple" "Banana" "cherry")
49+
(sort (fn [a b] (compare (s/lower-case a) (s/lower-case b)))
50+
["cherry" "apple" "Banana"])))
51+
52+
;; Sort by string length
53+
(is (= '("a" "bb" "ccc" "dddd")
54+
(sort (fn [a b] (compare (count a) (count b)))
55+
["ccc" "a" "dddd" "bb"])))
56+
57+
;; Sort maps by a specific key
58+
(let [data [{:name "John" :age 30}
59+
{:name "Jane" :age 25}
60+
{:name "Bob" :age 35}]]
61+
(is (= [{:name "Jane" :age 25}
62+
{:name "John" :age 30}
63+
{:name "Bob" :age 35}]
64+
(sort (fn [a b] (compare (:age a) (:age b))) data))))))
65+
66+
(deftest test-sort-nil-handling
67+
(testing "Nil handling in sort"
68+
;; nil sorts before everything
69+
(is (= '(nil 1 2 3) (sort [3 nil 1 2])))
70+
(is (= '(nil nil 1 2) (sort [2 nil 1 nil])))
71+
(is (= '(nil "a" "b") (sort ["b" nil "a"])))
72+
73+
;; With custom comparator that handles nil
74+
(is (= '(3 2 1 nil)
75+
(sort (fn [a b]
76+
(cond
77+
(nil? a) 1 ; nil goes to end
78+
(nil? b) -1
79+
:else (compare b a)))
80+
[nil 1 2 3])))))
81+
82+
(deftest test-sort-different-collection-types
83+
(testing "Sort works on different collection types"
84+
;; Vector
85+
(is (= '(1 2 3) (sort [3 1 2])))
86+
87+
;; List
88+
(is (= '(1 2 3) (sort '(3 1 2))))
89+
90+
;; Set
91+
(is (= '(1 2 3) (sort #{3 1 2})))
92+
93+
;; Map entries (sorted as vectors)
94+
(let [result (sort {:b 2 :a 1 :c 3})]
95+
(is (= 3 (count result)))
96+
(is (every? vector? result))
97+
(is (= :a (first (first result)))))
98+
99+
;; String (converts to character sequence)
100+
(is (= '(\a \b \c \d) (sort "dcba")))
101+
102+
;; Range
103+
(is (= '(0 1 2 3 4) (sort (reverse (range 5)))))))
104+
105+
(deftest test-sort-stability
106+
(testing "Sort is stable"
107+
;; Create items that compare equal but are distinguishable
108+
(let [items [{:id 1 :value 1}
109+
{:id 2 :value 2}
110+
{:id 3 :value 1}
111+
{:id 4 :value 2}
112+
{:id 5 :value 1}]
113+
sorted (sort (fn [a b] (compare (:value a) (:value b))) items)]
114+
;; Items with same value should maintain relative order
115+
(is (= [{:id 1 :value 1}
116+
{:id 3 :value 1}
117+
{:id 5 :value 1}
118+
{:id 2 :value 2}
119+
{:id 4 :value 2}]
120+
sorted)))))
121+
122+
(deftest test-sort-metadata-preservation
123+
(testing "Sort preserves metadata"
124+
(let [coll ^{:my-meta true} [3 1 2]
125+
sorted (sort coll)]
126+
(is (= '(1 2 3) sorted))
127+
(is (= true (:my-meta (meta sorted)))))))
128+
129+
(deftest test-sort-edge-cases
130+
(testing "Sort edge cases"
131+
;; Large collection
132+
(let [large (repeatedly 1000 #(rand-int 100))
133+
sorted (sort large)]
134+
(is (= 1000 (count sorted)))
135+
(is (apply <= sorted))) ; verify it's actually sorted
136+
137+
;; All equal elements
138+
(is (= '(1 1 1 1) (sort [1 1 1 1])))
139+
140+
;; Mixed positive/negative numbers
141+
(is (= '(-3 -1 0 1 3) (sort [1 -1 3 0 -3])))))
142+
143+
(deftest test-sort-error-cases
144+
(testing "Sort error cases"
145+
;; Invalid comparator (not a function)
146+
(is (thrown? go/any (sort "not-a-function" [1 2 3])))
147+
148+
;; Comparator returns non-numeric
149+
(is (thrown? go/any
150+
(sort (fn [a b] "not-a-number") [1 2 3])))
151+
152+
;; Uncomparable types (this might throw or might have undefined behavior)
153+
;; Clojure's behavior here is to throw ClassCastException
154+
(is (thrown? go/any
155+
(sort [1 "a" :b])))))
156+
157+
(deftest test-compare-function
158+
(testing "Compare function behavior"
159+
;; Numbers
160+
(is (= -1 (compare 1 2)))
161+
(is (= 0 (compare 2 2)))
162+
(is (= 1 (compare 3 2)))
163+
(is (= -1 (compare 1.5 2)))
164+
(is (= 1 (compare 2.5 2)))
165+
166+
;; Strings
167+
(is (= -1 (compare "a" "b")))
168+
(is (= 0 (compare "hello" "hello")))
169+
(is (= 1 (compare "z" "a")))
170+
171+
;; Keywords
172+
(is (= -1 (compare :a :b)))
173+
(is (= 0 (compare :x :x)))
174+
175+
;; Symbols
176+
(is (= -1 (compare 'a 'b)))
177+
(is (= 0 (compare 'x 'x)))
178+
179+
;; nil handling
180+
(is (= -1 (compare nil 1)))
181+
(is (= -1 (compare nil "a")))
182+
(is (= -1 (compare nil :a)))
183+
(is (= 0 (compare nil nil)))
184+
(is (= 1 (compare 1 nil)))
185+
186+
;; Different numeric types
187+
(is (= 0 (compare 1 1.0)))
188+
(is (= 0 (compare 1.0 1)))))
189+
190+
(deftest test-sort-maps-behavior
191+
(testing "Sorting maps produces map entries"
192+
(let [m {:b 2 :a 1 :c 3}
193+
sorted (sort m)]
194+
;; Each element should be a map entry (vector of [k v])
195+
(is (every? vector? sorted))
196+
(is (every? #(= 2 (count %)) sorted))
197+
;; Should be sorted by key
198+
(is (= [[:a 1] [:b 2] [:c 3]] sorted)))))
199+
200+
;; Run tests
201+
(run-tests)

0 commit comments

Comments
 (0)