Skip to content

Conversation

@lrhn
Copy link
Member

@lrhn lrhn commented Oct 31, 2025

Each new function separates the elements of an iterable or list with a separator value of the same type.
They allow optionally adding the separator before a first element and/or after a last element.

  • separated: Returns a (lazy) iterable backed by the original elements.
  • separatedList: Creates an (eager) list with the same elements that separated would have returned. (But more efficiently.) (Also has a version specialized for a List input.)
  • separate: Modifies a list in-place to add separators around the existing elements.

Fixes, among others, #628

This is one possible API. Names are fungible. Options are optional.

Attempts to give you the most direct API for what you want, so you don't have to create an intermediate iterable just to call toList() on it, and not call toList() if you could just modify the original list.

WDYT?

Each new function separates the elements of an iterable or list with
a separator value of the same type.
They allow optionally adding the separator before a first element
and/or after a last element.

* `separated`: Returns a (lazy) iterable backed by the original elements.
* `separatedList`: Creates an (eager) list with the same elements
  that separated would have returned. (But more efficiently.)
  (Also has a version specialized for a `List` input.)
* `separate`: Modifies a list in-place to add separators around
  the existing elements.
@lrhn lrhn requested a review from natebosch October 31, 2025 14:43
@lrhn lrhn requested a review from a team as a code owner October 31, 2025 14:43
@github-actions
Copy link

github-actions bot commented Oct 31, 2025

PR Health

License Headers ✔️
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
Files
no missing headers

All source files should start with a license header.

This check can be disabled by tagging the PR with skip-license-check.

API leaks ✔️

The following packages contain symbols visible in the public API, but not exported by the library. Export these symbols or remove them from your publicly visible API.

Package Leaked API symbol Leaking sources

This check can be disabled by tagging the PR with skip-leaking-check.

Breaking changes ⚠️
Package Change Current Version New Version Needed Version Looking good?
collection Breaking 1.19.1 1.20.0-wip 2.0.0
Got "1.20.0-wip" expected >= "2.0.0" (breaking changes)
⚠️

This check can be disabled by tagging the PR with skip-breaking-check.

Coverage ✔️
File Coverage
pkgs/collection/lib/src/iterable_extensions.dart 💚 100 %
pkgs/collection/lib/src/list_extensions.dart 💚 68 % ⬆️ 11 %

This check for test coverage is informational (issues shown here will not fail the PR).

This check can be disabled by tagging the PR with skip-coverage-check.

Changelog Entry ✔️
Package Changed Files

Changes to files need to be accounted for in their respective changelogs.

This check can be disabled by tagging the PR with skip-changelog-check.

/// )); // ()
/// ```
Iterable<T> separated(T separator,
{bool before = false, bool after = false}) sync* {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I can write a non-sync* implementation if we worry about performance, but I expect that most uses will be of separatedList or separate instead.)

@Reprevise
Copy link

Here's an alternate implementation for separatedList (Iterable version):

Don't mind the fact that it's not an extension method. This is what I use in my apps (modified to add before/after).

List<T> separatedList<T>(T separator, Iterable<T> items, {bool before = false, bool after = false}) {
  var iter = items.iterator;
  if (!iter.moveNext()) return [];

  var result = [if (before) separator, iter.current];
  while (iter.moveNext()) {
    result.add(separator);
    result.add(iter.current);
  }

  if (after) {
    result.add(separator);
  }

  return result;
}

@lrhn
Copy link
Member Author

lrhn commented Nov 5, 2025

It's a perfectly good implementation. I should check if for (...in...) is more efficient than directly using an iterator, especially on list literals, which is a common use-case.

@Reprevise
Copy link

I'm not good at benchmarking but from my quick tests with benchmark_harness comparing the two implementations:

baseline = current PR implementation
other = my implementation

iterable = Iterable.generate
complex iterable = .where, .map, etc
list = List.generate

baseline - iterable(RunTime): 2392.475 us.
baseline - complex iterable(RunTime): 1259.8255 us.
baseline - list(RunTime): 2310.4275 us.
other - iterable(RunTime): 1867.0742128935533 us.
other - complex iterable(RunTime): 1013.2355 us.
other - list(RunTime): 1023.0435 us.

@lrhn
Copy link
Member Author

lrhn commented Nov 7, 2025

That does look rather convincing. I had hoped that we could optimize a list for-in more, but if we don't inline and the loop is polymorphic, then that won't happen. And the body is more complicated to account for the difference between the first and following items.

I checked with some thorough benchmarks. The difference was quite significant, so I optimized the code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants