Skip to content

Commit

Permalink
better join
Browse files Browse the repository at this point in the history
  • Loading branch information
cgay committed Jan 21, 2008
1 parent 2781acb commit 3bb1431
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 34 deletions.
70 changes: 47 additions & 23 deletions strings.dylan
Expand Up @@ -39,9 +39,6 @@ define open generic digit-to-integer (c :: <character>, #key) => (i :: <integer>

define open generic trim (string :: <string>, #key) => (new-string :: <string>);

define open generic join
(items :: <sequence>, separator :: <string>, #key) => (new-string :: <string>);

define open generic replace
(original :: <string>, pattern :: <object>, replacement :: <string>, #key)
=> (new-string :: <string>, num-replacements :: <integer>);
Expand Down Expand Up @@ -783,35 +780,62 @@ define method digit-to-integer
end method digit-to-integer;

// ----------------------------------------------------------------------
define open generic join
(items :: <sequence>, separator :: <sequence>, #key key, conjunction)
=> (joined :: <sequence>);

// join(range(from: 1, to: 3), ", ",
// key: integer-to-string,
// conjunction: " and ");
// => "1, 2 and 3"

define method join
(seq :: <sequence>, separator :: <string>,
(sequences :: <sequence>, separator :: <sequence>,
#key key :: <function> = identity,
conjunction :: false-or(<string>))
=> (result :: <string>)
with-output-to-string (out)
let len-1 :: <integer> = seq.size - 1;
for (i :: <integer> from 1,
item in seq)
let v = key(item);
select (v by instance?)
<string> => write(out, v);
<character> => write-element(out, v);
otherwise => write(out, as(<string>, v));
end;
//write(out, as(<string>, key(item)));
if (i < len-1)
write(out, separator);
elseif (i == len-1)
write(out, conjunction | separator);
end;
conjunction :: false-or(<sequence>))
=> (joined :: <sequence>)
let length :: <integer> = sequences.size;
if (length == 0)
error("Attempt to join an empty sequence.")
elseif (length == 1)
key(sequences[0])
else
let result-size :: <integer>
= (reduce(method (len, seq)
len + seq.size
end,
0,
sequences)
+ (separator.size * (length - 1))
+ if (conjunction)
// the last separator is replaced by the conjunction
conjunction.size - separator.size
else
0
end);
let first = key(sequences[0]); // don't call key > once on sequences[0]
let result = make(object-class(first), size: result-size);
let result-index :: <integer> = 0;
local method copy-to-result (seq :: <sequence>)
result := replace-subsequence!(result, seq, start: result-index);
result-index := result-index + seq.size;
end;
copy-to-result(first);
let max-index :: <integer> = length - 1;
for (i :: <integer> from 1 to max-index)
let seq :: <sequence> = sequences[i];
copy-to-result(if(conjunction & i == max-index)
conjunction
else
separator
end);
copy-to-result(key(seq));
end;
end
result
end if
end method join;



// In common-dylan library...
// Split a sequence into parts at each occurrance of the 'separator'
Expand Down
24 changes: 13 additions & 11 deletions tests/strings-test-suite.dylan
Expand Up @@ -208,17 +208,19 @@ define strings function-test trim ()
end function-test trim;

define strings function-test join ()
for (item in list(list(list("", "-"), ""),
list(list(#("a", "b", "c"), "-"), "a-b-c"),
list(list(#("a", "b", "c"), ", ", conjunction:, " and "),
"a, b and c"),
list(list("abc", ", ", conjunction:, " and ", key:, uppercase),
"A, B and C")))
let (join-args, expected-result) = apply(values, item);
let test-name = fmt("join(%s)",
join(join-args, ", ", key: method (x) fmt("%=", x) end));
check-equal(test-name, apply(join, join-args), expected-result);
end for;
let abc = #["a", "b", "c"];
check-equal("join one element", join(#["foo"], "-"), "foo");
check-equal("join with empty separator", join(abc, ""), "abc");
check-equal("join with non-empty separator", join(abc, "-"), "a-b-c");
check-equal("join with conjunction",
join(abc, ", ", conjunction: " and "),
"a, b and c");
check-equal("join with conjunction and key",
join(abc, ", ", conjunction: " and ", key: uppercase),
"A, B and C");
check-condition("join an empty sequence is an error",
<error>,
join(#[], "-"));
end function-test join;

define strings function-test integer-to-digit ()
Expand Down

0 comments on commit 3bb1431

Please sign in to comment.