我想知道一段时间,加入一个字符串数组的漂亮,干净的解决方案可能看起来像。 示例:我有[“Alpha”,“Beta”,“Gamma”]并希望将字符串连接成一个,用逗号分隔 - “Alpha,Beta,Gamma”。
现在我知道大多数编程语言为此提供了某种连接方法。我只是想知道这些是如何实现的。 当我参加入门课程时,我经常尝试单独进行,但从未找到过令人满意的算法。一切似乎都很混乱,问题是你不能只是循环遍历数组,连接字符串,因为你会添加一个太多的逗号(在最后一个字符串之前或之后)。 我不想检查循环中的条件。我真的不想在循环之前/之后添加第一个或最后一个字符串(我猜这可能是最好的方法吗?)。
有人能给我看一个优雅的解决方案吗?或者告诉我为什么没有更优雅的东西?
答案 0 :(得分:19)
我发现像这样的问题的最优雅的解决方案是这样的(在伪代码中)
separator = ""
foreach(item in stringCollection)
{
concatenatedString += separator + item
separator = ","
}
您只需在第二次设置分隔符后运行循环。所以第一次它不会被添加。它不像我希望的那样干净,所以我仍然会添加注释,但它比if语句或在循环外添加第一个或最后一个项目更好。
答案 1 :(得分:10)
所有这些解决方案都是不错的解决方案,但对于底层库,分离器的独立性和体面的速度都很重要。假设语言具有某种形式的字符串构建器,这是一个符合要求的函数。
public static string join(String[] strings, String sep) {
if(strings.length == 0) return "";
if(strings.length == 1) return strings[0];
StringBuilder sb = new StringBuilder();
sb.append(strings[0]);
for(int i = 1; i < strings.length; i++) {
sb.append(sep);
sb.append(strings[i]);
}
return sb.toString();
}
编辑:我想我应该提一下为什么这会更快。主要原因是因为任何时候你打电话给c = a + b;底层构造通常是c =(new StringBuilder())。append(a).append(b).toString();.通过重用相同的字符串构建器对象,我们可以减少分配量和生成的垃圾。
在有人讨论优化之前是邪恶的,我们谈论的是实现一个通用的库函数。可接受的,可扩展的性能是它们的要求之一。需要很长时间的连接是一个不会被使用的连接。
答案 2 :(得分:5)
现在大多数语言 - 例如perl(由Jon Ericson提及),php,javascript - 有一个join()函数或方法,这是迄今为止最优雅的解决方案。更少的代码是更好的代码。
作为对Mendelt Siebenga的回应,如果你确实需要手动解决方案,我会选择三元运算符:
separator = ","
foreach (item in stringCollection)
{
concatenatedString += concatenatedString ? separator + item : item
}
答案 3 :(得分:3)
我通常喜欢......
list = ["Alpha", "Beta", "Gamma"];
output = "";
separator = "";
for (int i = 0; i < list.length ; i++) {
output = output + separator;
output = output + list[i];
separator = ", ";
}
这是有效的,因为在第一次传递时,分隔符为空(因此您在开始时不会得到逗号,但在每次后续传递时,您都会在添加下一个元素之前添加逗号。
你当然可以稍微展开它以使它更快一些(反复分配到分隔符并不理想),虽然我怀疑编译器可以自动为你做的事情。
最后,我怀疑这是大多数语言级联接功能归结为的。只不过是语法糖,但它肯定是甜蜜的。
答案 4 :(得分:3)
对于纯粹的优雅,典型的递归功能语言解决方案非常好。这不是一个实际的语言语法,但你明白了(它也硬编码使用逗号分隔符):
加入([])=“”
加入([x])=“x”
加入([x,rest])=“x”,+ join(rest)
实际上你会以更通用的方式编写它,重用相同的算法但抽象出数据类型(不必是字符串)和操作(不必与逗号串联)中间)。然后它通常被称为'reduce',并且许多函数语言都内置了这种语言,例如将所有数字乘以Lisp:
(减少#'*'(1 2 3 4 5))=&gt; 120
答案 5 :(得分:2)
@Mendelt Siebenga
字符串是编程语言中的角石对象。不同的语言实现字符串不同join()
的实现很大程度上取决于字符串的底层实现。伪代码不反映底层实现。
在Python中考虑join()
。它很容易使用:
print ", ".join(["Alpha", "Beta", "Gamma"])
# Alpha, Beta, Gamma
可以很容易地实现如下:
def join(seq, sep=" "):
if not seq: return ""
elif len(seq) == 1: return seq[0]
return reduce(lambda x, y: x + sep + y, seq)
print join(["Alpha", "Beta", "Gamma"], ", ")
# Alpha, Beta, Gamma
此处如何在C中实现join()
方法(取自trunk):
PyDoc_STRVAR(join__doc__,
"S.join(sequence) -> string\n\
\n\
Return a string which is the concatenation of the strings in the\n\
sequence. The separator between elements is S.");
static PyObject *
string_join(PyStringObject *self, PyObject *orig)
{
char *sep = PyString_AS_STRING(self);
const Py_ssize_t seplen = PyString_GET_SIZE(self);
PyObject *res = NULL;
char *p;
Py_ssize_t seqlen = 0;
size_t sz = 0;
Py_ssize_t i;
PyObject *seq, *item;
seq = PySequence_Fast(orig, "");
if (seq == NULL) {
return NULL;
}
seqlen = PySequence_Size(seq);
if (seqlen == 0) {
Py_DECREF(seq);
return PyString_FromString("");
}
if (seqlen == 1) {
item = PySequence_Fast_GET_ITEM(seq, 0);
if (PyString_CheckExact(item) || PyUnicode_CheckExact(item)) {
Py_INCREF(item);
Py_DECREF(seq);
return item;
}
}
/* There are at least two things to join, or else we have a subclass
* of the builtin types in the sequence.
* Do a pre-pass to figure out the total amount of space we'll
* need (sz), see whether any argument is absurd, and defer to
* the Unicode join if appropriate.
*/
for (i = 0; i < seqlen; i++) {
const size_t old_sz = sz;
item = PySequence_Fast_GET_ITEM(seq, i);
if (!PyString_Check(item)){
#ifdef Py_USING_UNICODE
if (PyUnicode_Check(item)) {
/* Defer to Unicode join.
* CAUTION: There's no gurantee that the
* original sequence can be iterated over
* again, so we must pass seq here.
*/
PyObject *result;
result = PyUnicode_Join((PyObject *)self, seq);
Py_DECREF(seq);
return result;
}
#endif
PyErr_Format(PyExc_TypeError,
"sequence item %zd: expected string,"
" %.80s found",
i, Py_TYPE(item)->tp_name);
Py_DECREF(seq);
return NULL;
}
sz += PyString_GET_SIZE(item);
if (i != 0)
sz += seplen;
if (sz < old_sz || sz > PY_SSIZE_T_MAX) {
PyErr_SetString(PyExc_OverflowError,
"join() result is too long for a Python string");
Py_DECREF(seq);
return NULL;
}
}
/* Allocate result space. */
res = PyString_FromStringAndSize((char*)NULL, sz);
if (res == NULL) {
Py_DECREF(seq);
return NULL;
}
/* Catenate everything. */
p = PyString_AS_STRING(res);
for (i = 0; i < seqlen; ++i) {
size_t n;
item = PySequence_Fast_GET_ITEM(seq, i);
n = PyString_GET_SIZE(item);
Py_MEMCPY(p, PyString_AS_STRING(item), n);
p += n;
if (i < seqlen - 1) {
Py_MEMCPY(p, sep, seplen);
p += seplen;
}
}
Py_DECREF(seq);
return res;
}
请注意,上述Catenate everything.
代码只是整个功能的一小部分。
在伪代码中:
/* Catenate everything. */
for each item in sequence
copy-assign item
if not last item
copy-assign separator
答案 6 :(得分:1)
'伪代码假设零基础
ResultString = InputArray[0] n = 1 while n (is less than) Number_Of_Strings ResultString (concatenate) ", " ResultString (concatenate) InputArray[n] n = n + 1 loop
答案 7 :(得分:1)
在Perl中,我只使用 join 命令:
$ echo "Alpha
Beta
Gamma" | perl -e 'print(join(", ", map {chomp; $_} <> ))'
Alpha, Beta, Gamma
(地图主要用于创建列表。)
在没有内置的语言中,比如C,我使用简单的迭代(未经测试):
for (i = 0; i < N-1; i++){
strcat(s, a[i]);
strcat(s, ", ");
}
strcat(s, a[N]);
当然,在为其添加更多字节之前,您需要检查 s 的大小。
您需要特殊情况first entry或最后一次。
答案 8 :(得分:1)
收集不同的语言实现?
这是为了您的娱乐,Smalltalk版本:
join:collectionOfStrings separatedBy:sep
|buffer|
buffer := WriteStream on:''.
collectionOfStrings
do:[:each | buffer nextPutAll:each ]
separatedBy:[ buffer nextPutAll:sep ].
^ buffer contents.
当然,上面的代码已经在标准库中找到:
收藏&gt;&gt; asStringWith:
所以,使用它,你会写:
#('A' 'B' 'C') asStringWith:','
但这是我的要点:
我想更加强调使用StringBuilder(或Smalltalk中称为“WriteStream”)的强烈推荐。不要在循环中使用“+”连接字符串 - 结果将是许多中间丢弃字符串。如果你有一个好的垃圾收集器,那很好。但有些不是,需要回收大量内存。 StringBuilder(和WriteStream,它的祖父)使用缓冲加倍甚至自适应增长算法,它需要 MUCH 更少的临时存储器。
然而,如果你只是连接几个小字符串,不关心,并且“+”它们;使用StringBuilder的额外工作实际上可能适得其反,直到依赖于实现和语言的字符串数量。
答案 9 :(得分:0)
以下不再是与语言无关的(但这对于讨论无关紧要,因为实现很容易移植到其他语言)。我尝试用命令式编程语言实现Luke(最好的)解决方案。随便挑选;我的C#。根本不是很优雅。但是,(没有任何测试)我可以想象它的性能相当不错,因为递归实际上是尾递归。
我的挑战:提供更好的递归实现(使用命令式语言)。你说什么“更好”意味着:更少的代码,更快,我愿意接受建议。
private static StringBuilder RecJoin(IEnumerator<string> xs, string sep, StringBuilder result) {
result.Append(xs.Current);
if (xs.MoveNext()) {
result.Append(sep);
return RecJoin(xs, sep, result);
} else
return result;
}
public static string Join(this IEnumerable<string> xs, string separator) {
var i = xs.GetEnumerator();
if (!i.MoveNext())
return string.Empty;
else
return RecJoin(i, separator, new StringBuilder()).ToString();
}
答案 10 :(得分:0)
join()
函数:
def join(seq, sep)
seq.inject { |total, item| total << sep << item } or ""
end
join(["a", "b", "c"], ", ")
# => "a, b, c"
答案 11 :(得分:0)
join()
:
use List::Util qw(reduce);
sub mjoin($@) {$sep = shift; reduce {$a.$sep.$b} @_ or ''}
say mjoin(', ', qw(Alpha Beta Gamma));
# Alpha, Beta, Gamma
或没有reduce
:
sub mjoin($@)
{
my ($sep, $sum) = (shift, shift);
$sum .= $sep.$_ for (@_);
$sum or ''
}
答案 12 :(得分:0)
sub join( $separator, @strings ){
my $return = shift @strings;
for @strings -> ( $string ){
$return ~= $separator ~ $string;
}
return $return;
}
是的我知道这是没有意义的,因为Perl 6已经有了一个连接功能。
答案 13 :(得分:0)
在Java 5中,使用单元测试:
import junit.framework.Assert;
import org.junit.Test;
public class StringUtil
{
public static String join(String delim, String... strings)
{
StringBuilder builder = new StringBuilder();
if (strings != null)
{
for (String str : strings)
{
if (builder.length() > 0)
{
builder.append(delim);
}
builder.append(str);
}
}
return builder.toString();
}
@Test
public void joinTest()
{
Assert.assertEquals("", StringUtil.join(", ", null));
Assert.assertEquals("", StringUtil.join(", ", ""));
Assert.assertEquals("", StringUtil.join(", ", new String[0]));
Assert.assertEquals("test", StringUtil.join(", ", "test"));
Assert.assertEquals("foo, bar", StringUtil.join(", ", "foo", "bar"));
Assert.assertEquals("foo, bar, baz", StringUtil.join(", ", "foo", "bar", "baz"));
}
}
答案 14 :(得分:0)
我在lisp中编写了一个递归版本的解决方案。如果列表的长度大于2,它会尽可能地将列表分成两半,然后尝试合并子列表
(defun concatenate-string(list)
(cond ((= (length list) 1) (car list))
((= (length list) 2) (concatenate 'string (first list) "," (second list)))
(t (let ((mid-point (floor (/ (- (length list) 1) 2))))
(concatenate 'string
(concatenate-string (subseq list 0 mid-point))
","
(concatenate-string (subseq list mid-point (length list))))))))
(concatenate-string '("a" "b"))
我尝试将分而治之的策略应用于问题,但我想这并不能提供比普通迭代更好的结果。如果可以做得更好,请告诉我。
我还对算法获得的递归进行了分析,可用here。
答案 15 :(得分:0)
在C#
中使用String.join方法