【问题标题】:Limiting the times that .split() splits, rather than truncating the resulting array限制 .split() 拆分的时间,而不是截断结果数组
【发布时间】:2015-05-02 04:20:07
【问题描述】:

真的,就像标题所说的那样。

假设你有这个字符串:

var theString = "a=b=c=d";

现在,当您运行theString.split("=") 时,结果是["a", "b", "c", "d"],正如预期的那样。当然,当你运行theString.split("=", 2) 时,你会得到["a", "b"],在阅读the MDN page for String#split() 之后,这对我来说是有意义的。

但是,我正在寻找的行为更像是 Java 的 String#split():它不是正常构建数组,然后返回第一个 n 元素,而是构建第一个 元素的数组>n-1 匹配,然后将所有剩余的字符添加为数组的最后一个元素。请参阅the relevant docs 以获得更好的描述。

如何在 Javascript 中获得这种效果?

我正在寻找与 Java 实现类似的最佳性能的答案,尽管它的实际工作方式可能不同。

我会发布我的尝试,但我根本不知道该怎么写。

【问题讨论】:

  • 我认为可能有一个非常简单的一行 es6 代码来实现这个?
  • @Snow 第一次登录 SE...天哪,几个月,现在?钱币。但是,是的,我可能会将其更改为明确指出 SE 失败的内容。

标签: javascript split query-string


【解决方案1】:

如果您想要与 Java 实现完全等效(没有错误检查或保护子句等):

function split(str, sep, n) {
    var out = [];

    while(n--) out.push(str.slice(sep.lastIndex, sep.exec(str).index));

    out.push(str.slice(sep.lastIndex));
    return out;
}

console.log(split("a=b=c=d", /=/g, 2)); // ['a', 'b', 'c=d']

正如您在问题中提到的那样,这具有不预先计算完整拆分的额外好处。

【讨论】:

  • 您能否快速解释一下这是如何工作的?
  • @QPaysTaxes 我迭代“限制”次数,并且在每次迭代中,我使用有状态的正则表达式来查找下一次出现的拆分字符。正则表达式记住它的最后一场比赛在哪里,一旦它执行,我就知道下一场比赛在哪里。我在每次迭代时在这两个位置之间添加字符串切片。在循环之外,我在最后一个正则表达式匹配和字符串结尾之间添加了字符串切片。
  • 而且由于 OP 询问了性能 - 这个实现的速度大约是其他两个的两倍(在 Chrome 中)。让我大吃一惊!
  • @SMcCrohan 这确实令人惊讶。我确信这会随着字符串和拆分数量逐渐变大而表现得更好,但我假设 JS 正则表达式中的恒定开销会使它比其他小输入慢。
  • @Asad - 是的,这是针对字符串中包含大量分隔符的测试。对于非常短的输入字符串,它落后于子字符串方法,但相差不大 - 14%。
【解决方案2】:

我会使用这样的东西:

function JavaSplit(string,separator,n) {
    var split = string.split(separator);
    if (split.length <= n)
        return split;
    var out = split.slice(0,n-1);
    out.push(split.slice(n-1).join(separator));
    return out;
}

我们在这里做的是:

  1. 完全拆分字符串
  2. 如上所述,取前 n-1 个元素。
  3. 重新加入剩余的元素。
  4. 将它们附加到第 2 步中的数组并返回。

人们可能会合理地认为您可以将所有这些调用链接在一起,但.push() 会改变一个数组而不是返回一个新数组。这样你也容易一些。

【讨论】:

  • 你好,也许你需要在切片前检查分割结果长度是否大于n,否则只返回分割结果。
  • 我喜欢这个简单,但我接受另一个是因为(据我所知,没有基准测试)它更有效。
  • 是的,是的。我很好奇有多少,所以我运行了一个快速的 jsperf - 大约有 25% 的差异。
  • 这假定分隔符是固定长度的。使用 string.split(/\s+/); 之类的东西会导致它崩溃。
【解决方案3】:

另一种可能的实现方式:

function split(s, separator, limit) {
  // split the initial string using limit
  var arr = s.split(separator, limit);
  // get the rest of the string...
  var left = s.substring(arr.join(separator).length + separator.length);
  // and append it to the array
  arr.push(left);
  return arr;
}

小提琴是here

【讨论】:

  • 虽然我不太确定length + 1 - 我想这取决于你的要求 - 你是否想在最后一个元素中有一个领先的separator
  • + 1 替换为+separator.length,以防出现多字符分隔符。
  • 这假定分隔符是固定长度的。使用 s.split(/\s+/, limit); 之类的东西会导致它崩溃。
【解决方案4】:

来自 Asad 的 answer 非常出色,因为它允许使用可变长度的正则表达式分隔符(例如 /\s+/g,沿任意长​​度的空格分割,包括换行符)。但是,它存在一些问题。

  1. 如果分隔符不使用全局标志,它将中断。
  2. exec 可以返回 null 并导致其中断。如果分隔符没有出现在输入字符串中,就会发生这种情况。
  3. 如果限制大于分隔点,您最终会在字符串上循环,可能会产生意想不到的结果。
  4. 限制是必需的,因此很难找到最大拆分。

以下解决了这些问题,同时具有同样的性能:

/**
 * Split a string with a RegExp separator an optionally limited number of times.
 * @param {string} input
 * @param {RegExp} separator
 * @param {number} [limit] - If not included, splits the maximum times
 * @returns {string[]}
 */
function split(input, separator, limit) {
  // Ensure the separator is global
  separator = new RegExp(separator, 'g');
  // Allow the limit argument to be excluded
  limit = limit ?? -1;

  const output = [];
  let finalIndex = 0;

  while (limit--) {
    const lastIndex = separator.lastIndex;
    const search = separator.exec(input);
    if (search === null) {
        break;
    }
    finalIndex = separator.lastIndex;
    output.push(input.slice(lastIndex, search.index));
  }

  output.push(input.slice(finalIndex));

  return output;
}
split("foo bar baz quux", /\s+/, 3)
// ["foo", "bar", "baz", "quux"]
split("foo bar baz quux", /\s+/, 2)
// ["foo", "bar", "baz quux"]
split("foo bar baz quux", /\s+/, 1)
// ["foo", "bar baz quux"]
split("foo bar baz quux", /\s+/, 0)
// ["foo bar baz quux"]

// A higher limit than possible splits
split("foo bar baz quux", /\s+/, 4)
// ["foo", "bar", "baz", "quux"]

// A split that doesn't exist
split("foo bar baz quux", /p/, 2)
// ["foo bar baz quux"]

// Not providing a limit finds the maximum splits
split("foo bar baz quux", /\s+/)
// ["foo", "bar", "baz", "quux"]

注意事项:

在生产代码中,建议不要改变函数参数。 separatorlimit 都在发生变异。如果需要,您可以选择在函数顶部创建新变量以避免这种情况。我选择不这样做是为了保持示例代码简短。这不是生产代码。

我没有包含任何防御性代码来检查函数参数类型。这对于生产代码来说是一件好事,或者考虑 TypeScript ;)

如果提供的分隔符没有设置全局标志,我最初会抛出Error。请参阅下面的 cmets,了解可能需要为用户添加全局标志而不是抛出的原因。感谢@Stephen P 的建议。

【讨论】:

  • 很好,我没想到这些。
  • 而不是要求调用者记住使用全局标志,如果不使用则抛出错误,您可以从他们创建另一个正则表达式:const splitter = new RegExp(separator, 'g'); 然后使用splitter.exec(input) — 没有害处如果他们已经提供了全局标志,并且它的工作方式更像是不需要/不需要全局标志的常规 string.split(regex)
  • @StephenP 我认为这是个好主意
【解决方案5】:

如果你想用更少的行数做到这一点并避免循环:

const theString = "some=string=with=separators";
const limit = 2;
const parts = theString.split('=', limit);
parts.push(theString.slice(parts.join('').length + limit));

【讨论】:

  • 这假定分隔符是固定长度的。使用 theString.split(/\s+/, limit); 之类的东西会导致它崩溃。
【解决方案6】:

您是否正在寻找更接近 PHP 的 explode 的东西?

这是我设计的一种方法:

String.prototype.explode = function(sep, n) {
  var arr = this.split(sep, n)
  if (arr[n-1] != undefined) arr[n-1] += this.substring(arr.join(' ').length);
  return arr;
}

这个方法像往常一样分割字符串,确定我们是否达到了限制,并使用substring 将文本附加到最后一次分割之后(我们可以直接访问最后一次分割之后第一个字符的偏移量,方法是数组上使用的joinlength,以任何单个字符作为分隔符)

这个方法和split一样使用:

str = 'my/uri/needs/to/be/split';
splitResult = str.split('/', 4);
explodeResult = str.explode('/', 4);
console.log(splitResult);
console.log(explodeResult);

// The following will be written to the console:
// splitResult:   ["my", "uri", "needs", "to"]
// explodeResult: ["my", "uri", "needs", "to/be/split"]

当然,这也可以作为一个函数旋转:

function explode(str, sep, n) {
  var arr = str.split(sep, n)
  if (arr[n-1] != undefined) arr[n-1] += this.substring(arr.join(' ').length);
  return arr;
}

str = 'my/uri/needs/to/be/split';
explodeResult = explode(str, '/', 4);

【讨论】:

  • 你为什么要重新加入设置分隔符 ' ' 而不是传入的分隔符?此外,这假设分隔符是固定长度的。使用 str.split(/\s+/, n); 之类的东西会导致它崩溃。
【解决方案7】:

const theString = "a=b=c=d";
const [first, ...rest] = theString.split("=");
const second = rest.join("=")
console.log(first, second)

如果您使用的是 ECMA 2015,则只需要 2 行代码。

【讨论】:

  • 它不适用于变量n。但对于已知较小的n
  • 这假定分隔符是固定长度的。使用 theString.split(/\s+/); 之类的东西会导致它崩溃。
【解决方案8】:

这是我的实现:

String.prototype.splitRemainder = function(delim, count) {
	if (typeof delim !== 'string') {
		return this.split();    
	}

	if (typeof count !== 'number') {
		return this.split(delim);
	}

	if (count < 2) {
		return this.split(delim);
	}

	count--;
	const parts = this.split(delim, count);
	const remainder = this.slice(parts.join('').length + count);

	if (remainder.length > 0) {
		parts.push(remainder);
	}

	return parts;
}

console.log("dasd asds asds asdasd asdasdas".splitRemainder(" ", 4));
console.log("hello-to-you-too".splitRemainder("-",2));

请注意,这不是实现它的最有效方式。因此,如果您正在寻找最有效的解决方案,这不是它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-12-19
    • 2011-05-28
    • 1970-01-01
    • 2017-09-30
    • 2020-09-29
    • 2021-08-22
    • 1970-01-01
    相关资源
    最近更新 更多