Skip to content

Commit 31228f9

Browse files
committed
feat(stdlib): improve String module with functional API and optimizations
- Added String.map, String.filter, String.any?, String.all?, String.reduce - Added String.replace utility - Optimized starts-with? and ends-with? with efficient C implementations - Integrated tests for new functionality into test/string.carp - Updated CHANGELOG.md
1 parent 98a9e2e commit 31228f9

4 files changed

Lines changed: 105 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
- fix: #1347 by ignoring generically typed symbols on printing C (#1373)
2121
- fix: nix install by using correct pkg-configDepends config key (#1372)
2222
- fix: type signature of Array.unsafe-raw (#1375)
23+
- feat: add map, filter, any?, all?, reduce, and replace to String (#XXXX)
24+
- perf: optimize starts-with? and ends-with? in String (#XXXX)
2325
- docs: Instructions to ensure correct handling of utf-8 (#1367)
2426

2527
# 0.5.4

core/String.carp

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -101,16 +101,8 @@
101101
(defn suffix [s b]
102102
(from-chars &(Array.suffix &(chars s) b)))
103103

104-
(doc starts-with? "Check if the string `s` begins with the string `sub`.")
105-
(defn starts-with? [s sub]
106-
(let [ls (length sub)]
107-
(and (>= (length s) ls) (= sub &(prefix s ls)))))
108-
109-
(doc ends-with? "Check if the string `s` ends with the string `sub`.")
110-
(defn ends-with? [s sub]
111-
(let [ls (length s)
112-
lsub (length sub)]
113-
(and (>= ls lsub) (= sub &(suffix s (- ls lsub))))))
104+
(register starts-with? (Fn [&String &String] Bool))
105+
(register ends-with? (Fn [&String &String] Bool))
114106

115107
(doc zero "The empty string.")
116108
(defn zero [] @"")
@@ -121,7 +113,7 @@
121113
(let-do [sum 0
122114
lstrings (Array.length strings)]
123115
(for [i 0 lstrings]
124-
(set! sum (+ sum (String.length (Array.unsafe-nth strings i)))))
116+
(set! sum (+ sum (length (Array.unsafe-nth strings i)))))
125117
sum))
126118

127119
(doc concat "Returns a new string which is the concatenation of the provided `strings`.")
@@ -189,6 +181,47 @@
189181
(doc ascii-to-upper "converts each character in this string to upper case using toupper() from standard C library. Note: this will only work for ASCII characters.")
190182
(defn ascii-to-upper [s]
191183
(String.from-bytes &(Array.endo-map &(fn [c] (Byte.toupper- c)) (String.to-bytes s))) )
184+
185+
(doc map "Maps a function over the characters of a string, returning a new string.")
186+
(defn map [f s]
187+
(from-chars &(Array.copy-map f &(chars s))))
188+
189+
(doc filter "Filters the characters of a string using a predicate, returning a new string.")
190+
(defn filter [f s]
191+
(from-chars &(Array.copy-filter f &(chars s))))
192+
193+
(doc any? "Checks if any character in a string satisfies a predicate.")
194+
(defn any? [f s]
195+
(Array.any? f &(chars s)))
196+
197+
(doc all? "Checks if all characters in a string satisfy a predicate.")
198+
(defn all? [f s]
199+
(Array.all? f &(chars s)))
200+
201+
(doc reduce "Reduces a string using a function and an initial value.")
202+
(defn reduce [f init s]
203+
(Array.reduce f init &(chars s)))
204+
205+
(doc replace "Replaces all occurrences of a substring in a string with another string.")
206+
(defn replace [s needle replacement]
207+
(if (empty? needle)
208+
@s
209+
(let [parts []
210+
start 0
211+
len (length s)
212+
nlen (length needle)]
213+
(do
214+
(while (< start len)
215+
(let [idx (index-of-string &(suffix s start) needle)]
216+
(if (= idx -1)
217+
(do
218+
(Array.push-back! &parts (suffix s start))
219+
(set! start len))
220+
(do
221+
(Array.push-back! &parts (slice s start (+ start idx)))
222+
(Array.push-back! &parts @replacement)
223+
(set! start (+ start (+ idx nlen)))))))
224+
(concat &parts)))))
192225
)
193226

194227
(defmodule StringCopy
@@ -526,4 +559,3 @@
526559
(cdr (String.split-on "" s)))
527560
)
528561
)
529-

core/carp_string.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,20 @@ String String_tail(const String* s) {
191191
return news;
192192
}
193193

194+
bool String_starts_MINUS_with_QMARK_(const String* s, const String* sub) {
195+
size_t ls = strlen(*s);
196+
size_t lsub = strlen(*sub);
197+
if (lsub > ls) return false;
198+
return memcmp(*s, *sub, lsub) == 0;
199+
}
200+
201+
bool String_ends_MINUS_with_QMARK_(const String* s, const String* sub) {
202+
size_t ls = strlen(*s);
203+
size_t lsub = strlen(*sub);
204+
if (lsub > ls) return false;
205+
return memcmp(*s + (ls - lsub), *sub, lsub) == 0;
206+
}
207+
194208
String String_empty() {
195209
String s = CARP_MALLOC(1);
196210
s[0] = '\0';

test/string.carp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,4 +400,49 @@
400400
3
401401
(Dynamic.length (Dynamic.String.to-array "abc"))
402402
"Dynamic.String.to-array works as expected (check length)")
403+
404+
(assert-equal test
405+
"bcde"
406+
&(map &(fn [c] (Char.from-int (Int.inc (Char.to-int @c)))) "abcd")
407+
"map works as expected")
408+
(assert-equal test
409+
"bd"
410+
&(filter &(fn [c] (or (= @c \b) (= @c \d))) "abcd")
411+
"filter works as expected")
412+
(assert-true test
413+
(any? &(fn [c] (= @c \c)) "abcd")
414+
"any? works as expected I")
415+
(assert-false test
416+
(any? &(fn [c] (= @c \z)) "abcd")
417+
"any? works as expected II")
418+
(assert-true test
419+
(all? &(fn [c] (not (= @c \z))) "abcd")
420+
"all? works as expected I")
421+
(assert-false test
422+
(all? &(fn [c] (= @c \a)) "abcd")
423+
"all? works as expected II")
424+
(assert-equal test
425+
4
426+
(reduce &(fn [acc c] (Int.inc acc)) 0 "abcd")
427+
"reduce works as expected (counting characters)")
428+
(assert-equal test
429+
"hEllo world"
430+
&(replace "hello world" "e" "E")
431+
"replace works as expected I")
432+
(assert-equal test
433+
"hi there hi"
434+
&(replace "hello there hello" "hello" "hi")
435+
"replace works as expected II")
436+
(assert-equal test
437+
"aaaaa"
438+
&(replace "aba" "b" "aaa")
439+
"replace works as expected III")
440+
(assert-equal test
441+
"xyz"
442+
&(replace "xyz" "" "abc")
443+
"replace with empty needle returns original string")
444+
(assert-equal test
445+
""
446+
&(replace "aaaaa" "a" "")
447+
"replace with empty replacement works")
403448
)

0 commit comments

Comments
 (0)