14

一段时间以来,我一直想知道,连接字符串数组的一个漂亮、干净的解决方案可能是什么样的。示例:我有 ["Alpha", "Beta", "Gamma"] 并且想将字符串合并为一个,用逗号分隔 - “Alpha, Beta, Gamma”。

现在我知道大多数编程语言为此提供了某种连接方法。我只是想知道如何实现这些。当我上入门课程时,我经常尝试单干,但始终没有找到令人满意的算法。一切看起来都相当混乱,问题是你不能只循环遍历数组,连接字符串,因为你会添加一个太多的逗号(在最后一个字符串之前或之后)。我不想检查循环中的条件。我真的不想在循环之前/之后添加第一个或最后一个字符串(我想这可能是最好的方法?)。

有人可以告诉我一个优雅的解决方案吗?或者告诉我为什么没有更优雅的东西?

4

16 回答 16

21

我为此类问题找到的最优雅的解决方案是这样的(在伪代码中)

separator = ""
foreach(item in stringCollection)
{
    concatenatedString += separator + item
    separator = ","
}

您只需运行循环,并且仅在设置分隔符的第二次之后。所以第一次它不会被添加。它不像我希望的那样干净,所以我仍然会添加注释,但它比 if 语句或在循环外添加第一个或最后一个项目要好。

于 2008-09-12T07:15:44.047 回答
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();。通过重用相同的字符串构建器对象,我们可以减少我们产生的分配和垃圾的数量。

在有人插话优化是邪恶的之前,我们正在谈论实现一个通用的库函数。可接受的、可扩展的性能是它们的要求之一。需要很长时间的连接是不会经常使用的连接。

于 2008-09-12T07:33:35.127 回答
5

现在大多数语言——例如 perl(Jon Ericson 提到)、php、javascript——都有一个 join() 函数或方法,这是迄今为止最优雅的解决方案。更少的代码是更好的代码。

作为对 Mendelt Siebenga 的回应,如果您确实需要手动解决方案,我会选择三元运算符,例如:

separator = ","
foreach (item in stringCollection)
{
    concatenatedString += concatenatedString ? separator + item : item
}
于 2008-09-12T08:39:50.773 回答
3

我通常会选择类似...

list = ["Alpha", "Beta", "Gamma"];
output = "";
separator = "";
for (int i = 0; i < list.length ; i++) {
  output = output + separator;
  output = output + list[i];
  separator = ", ";
}

这是有效的,因为在第一遍中,分隔符为空(因此您在开始时不会得到逗号,但在随后的每一遍中,您在添加下一个元素之前添加一个逗号。

您当然可以将其展开一点以使其更快一些(一遍又一遍地分配给分隔符并不理想),尽管我怀疑这是编译器可以自动为您做的事情。

最后,我怀疑这就是大多数语言级别的连接函数归结为的原因。只不过是语法糖,但它确实很甜。

于 2008-09-12T07:18:48.987 回答
3

对于纯粹的优雅,典型的递归函数式语言解决方案非常好。这不是实际的语言语法,但你明白了(它也被硬编码为使用逗号分隔符):

加入([])=“”

加入([x])=“x”

加入([x,休息])=“x”,+加入(休息)

实际上,您会以更通用的方式编写它,以重用相同的算法,但抽象出数据类型(不必是字符串)和操作(不必在中间用逗号连接) . 然后它通常被称为“reduce”,许多函数式语言都内置了它,例如在 Lisp 中将列表中的所有数字相乘:

(减少#'*'(1 2 3 4 5)) => 120

于 2008-09-12T07:21:29.917 回答
2

@Mendelt Siebenga

字符串是编程语言中的基石对象。不同的语言以不同的方式实现字符串。的实现join()强烈依赖于字符串的底层实现。伪代码不反映底层实现。

join()在 Python 中考虑。它可以很容易地使用:

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

以及如何join()在 C 中实现方法(取自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
于 2008-09-14T13:14:51.750 回答
1

' 伪代码假设从零开始

结果字符串 = 输入数组 [0]
n = 1
而 n (小于) Number_Of_Strings
    结果字符串(连接)“,”
    ResultString(连接) InputArray[n]
    n = n + 1
环形
于 2008-09-12T07:38:29.547 回答
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的大小。

您必须对第一个条目或最后一个条目进行特殊处理。

于 2008-09-12T08:21:42.673 回答
1

收集不同的语言实现?
这是一个 Smalltalk 版本,供您娱乐:

join:collectionOfStrings separatedBy:sep

  |buffer|

  buffer := WriteStream on:''.
  collectionOfStrings 
      do:[:each | buffer nextPutAll:each ]
      separatedBy:[ buffer nextPutAll:sep ].
  ^ buffer contents.

当然,上面的代码已经在标准库中找到了:

集合 >> asStringWith:

所以,使用它,你会写:

#('A' 'B' 'C') asStringWith:','

但这是我的主要观点

我想更加强调一个事实,即强烈推荐使用 StringBuilder(或 Smalltalk 中所谓的“WriteStream”)。不要在循环中使用“+”连接字符串 - 结果将是许多中间丢弃的字符串。如果你有一个好的垃圾收集器,那很好。但有些不是,需要回收大量内存。StringBuilder(和WriteStream,它的祖父)使用缓冲区加倍甚至自适应增长算法,它需要更少的临时内存。

但是,如果您要连接的只是几个小字符串,则不在乎,然后“+”它们;使用 StringBuilder 的额外工作实际上可能适得其反,直到实现和语言相关的字符串数量。

于 2009-01-12T22:25:35.190 回答
0

Perl 6

sub join( $separator, @strings ){
  my $return = shift @strings;
  for @strings -> ( $string ){
    $return ~= $separator ~ $string;
  }
  return $return;
}

是的,我知道这是没有意义的,因为 Perl 6 已经有一个连接函数。

于 2008-09-15T16:10:26.657 回答
0

join()Ruby 中的函数:

def join(seq, sep) 
  seq.inject { |total, item| total << sep << item } or "" 
end

join(["a", "b", "c"], ", ")
# => "a, b, c"
于 2008-09-14T19:21:54.923 回答
0

join()在 Perl 中:

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 ''
 }
于 2008-09-14T20:37:40.063 回答
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"));
    }
}
于 2008-09-16T20:36:36.210 回答
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();
}
于 2008-09-12T07:40:44.177 回答
0

在 C# 中使用 String.join 方法

http://msdn.microsoft.com/en-us/library/57a79xd0.aspx

于 2009-04-05T17:39:59.060 回答
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"))

我尝试将分而治之的策略应用于该问题,但我想这并没有比普通迭代产生更好的结果。请让我知道这是否可以做得更好。

我还对算法获得的递归进行了分析,可在此处获得。

于 2008-09-17T06:14:03.873 回答