A port of .NET's LINQ IEnumerable functions to Dart. This package extends the native Iterable
type with all of the LINQ methods that do not exist in native Dart. Starting with version 0.5, this package also contains the extension methods from the MoreLINQ .NET library.
Because this library uses Dart 2.6's new extension methods, any Iterable
has access to these methods as though they were native methods. This includes classes that extend from Iterable
, such as List
and Set
.
In addition, this library adds several new types of Iterable
classes to make some utility functions easier:
// Creates an iterable containing the numbers from 2 to 6: [2, 3, 4, 5, 6]
var rangeI = RangeIterable(2, 6, inclusive: true);
// Creates an iterable that contains 3 copies of the value 'abc': ['abc', 'abc', 'abc']
var repeatI = RepeatIterable('abc', 3);
// Creates an iterable from a string, iterating over its characters
// This is an extension getter property on String that returns an
// iterable via `String.codeUnits.map((u) => String.fromCodeUnit(u))`.
// Results in ['a', 'b', 'c', 'd', 'e', 'f']
var stringI = 'abcdef'.iterable;
// Same as above but using `runes` instead of `codeUnits` to respect
// rune boundaries and maintain surrogate pairs.
var stringIR = 'abcdef'.iterableRunes;
You can call any of 40 new methods on it to modify or analyze it. For example, the native method map
is expanded upon with select
, which combines the element with the index at which the element is found within the iterable:
var list = [10, 20, 30];
var mappedList = list.select((i, index) => '$index-$i'); // ['1-10', '2-20', '3-30']
There are "OrDefault" variants on several common iterator
value getter methods, such as firstOrDefault
, singleOrDefault
, and defaultIfEmpty
. Instead of throwing an error, these methods will return a default value (or null if left unspecified) if the element(s) cannot be found:
var list = <String>[];
var native = list.first; // Throws a StateError
var orDefault = list.firstOrDefault('abc'); // Returns 'abc'
var list2 = [1, 2, 3];
var importantValue = list2.where((i) => i >= 4)
.defaultIfEmpty(-1); // Returns [-1]
You can filter an iterable down to unique instances of elements with the distinct
method:
var list = [1, 1, 1, 2, 2, 3, 4, 5, 5, 5, 5, 5];
var uniqueList = myEnum.distinct(); // [1, 2, 3, 4, 5]
There are also set operations with the except
, intersect
, and union
methods:
var listA = [1, 2, 3, 4];
var listB = [3, 4, 5, 6];
var exclusion = listA.except(listB); // [1, 2]
var intersection = listA.intersect(listB); // [3, 4]
var union = listA.union(listB); // [1, 2, 3, 4, 5, 6]
And you can group elements together by common features using groupBy
:
var list = [1, 2, 3, 4, 5, 6];
var groupedList = list.groupBy((i) => i / 3 == 0); // [[1, 2, 4, 5], [3, 6]]
Or bundle them into groups of a fixed length using segment
:
var list = [1, 2, 3, 4, 5, 6];
var segmented = list.segment(2); // [[1, 2], [3, 4], [5, 6]]
You can even perform complex ordering functions using orderBy
and thenBy
:
var list = ['ab', 'a', 'c', 'aa', ''];
// Sort by string length followed by alphabetical order
var ordered = list.orderBy((c) => c.length)
.thenBy((c) => c);
// Result: ['', 'a', 'c', 'aa', 'ab']
Just like in native dart, every method returns a new Iterable
, so you can chain methods together to make complex mapping, grouping, and filtering behavior:
var list = [3, 1, 6, 2, 3, 2, 4, 1];
var result = list.select((i, idx) => i * 2 + idx) // [6, 3, 14, 8, 10, 10, 14, 9]
.distinct() // [6, 3, 14, 8, 10, 9]
.where((i) => i > 4) // [6, 14, 8, 10, 9]
.orderBy((i) => i) // [6, 8, 9, 10, 14]
.map((i) => i.toRadixString(16)); // [6, 8, 9, A, E]
NEW in 2.0.0
With record support added in Dart 3, A new method has been added called deconstruct
. It's purpose is to easily unwrap iterables of records, resulting in separate iterables containing the corresponding fields of the internal records.
var johns = [
("John Doe", 26, "E145326"),
("John Wick", 58, "E645091"),
("John Teesonter", 15, "E997123"),
];
var (names, ages, ids) = johns.deconstruct();
print(names); // ["John Doe", "John Wick", "John Teesonter"]
print(ages); // [26, 58, 15]
print(ids); // ["E145326", "E645091", "E997123"]
This extension method is implemented on lists containing records with up to nine fields.
(Note: Due to apparent current language restrictions, records containing named fields are not supported.)
As a necessity for some operations, I needed a Tuple
class, and as I was unsatisfied with the current offerings out there right now, I elected to create my own.
For the uninitiated, tuples are similar to lists in concept that they contain multiple values addressable by index. But where every element of a list must resolve to the same type (the type of the list), each element in a tuple can be its own specified type. This results in being able to contain, distribute, and access the items in a tuple in a type-safe way. You could, for example, use a Tuple2<double, String>
to return two values from a function and be able to access both the double
and the String
values without needing to resort to fragile methods such as dynamic
or runtime type casting. Another difference between lists and tuples is that tuples are inherently immutable, so they aren't susceptible to side effects stemming from mutation and can even benefit from being declared as constants.
This package exposes tuple classes from Tuple0
up to Tuple9
, depending on how many items the tuple contains. (Yes, I agree that Tuple0
and Tuple1
seem largely redundant, but I've seen them exist in the tuple libraries of many programming languages so it must serve some purpose or other, so I included them here all the same for completeness if nothing else.) Each tuple class includes the following features:
- Constant constructors allow for efficient use of known tuple values.
- Includes access to the item(s) by getter (
tuple.item2
) or by indexer(tuple[2]
). (Note that access by indexer is not type-safe) - Factory constructor
fromJson
and methodtoJson
means tuples are seralization-ready. - Additional factory constructor
fromList
to generate a tuple from a list (automatically casting when specifying type parameters for the constructor). - An
asType
method allows freely casting the tuple from any assortment of types to any other assortment of types (provided the items are compatible with those types).- Additionally, there is an
asDynamic
convenience method for casting a tuple to dynamic items.
- Additionally, there is an
- Although tuples themselves are immutable, a
copyWith
method allows easy generation of duplicate tuples, optionally specifying new values for specific items.- Additionally, a
copyWithout
method allows selective filtering of items from a tuple resulting in a lower-order tuple.
- Additionally, a
- A
mapActions
method allows you to iterate over each item with an exhaustive list of type-safe callbacks. - Each tuple class extends
Iterable<dynamic>
, so it can be treated as a normal iterable (and thus combined with any darq extension method). - As
==
andhashCode
are both implemented, tuples can be directly compared for equality or used as keys for maps and other hash sets. - (2.0.0) Can be converted to and from Dart 3 records.
As of version 0.5, this package also contains the extension methods from the MoreLINQ .NET library. This more than triples the available number of extension methods over vanilla LINQ.
Some examples of the new methods from MoreLINQ include:
index
:
var list = ['a', 'b', 'c'];
var indexedList = list.index();
// Iterable:
// [ [0, 'a'], [1, 'b'], [2, 'c'] ]
assertAll
:
var list = [2, 4, 5];
list.assertAll((x) => x.isEven);
// Throws an assertion error
awaitAll
:
var list = [
Future.delayed(Duration(seconds: 1)),
Future.delayed(Duration(seconds: 2)),
Future.delayed(Duration(seconds: 3)),
];
await list.awaitAll();
// Waits for 3 seconds before continuing
subsets
:
var list = [1, 2, 3];
var subsets = list.subsets();
// Iterable:
// [ [], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3] ]
interleave
:
var listA = [1, 3, 5];
var listB = [2, 4, 6];
var combined = listA.interleave(listB);
// Iterable:
// [1, 2, 3, 4, 5, 6]
permutations
:
var list = [1, 2, 3];
var perms = list.permutations();
// Iterable:
// [ [1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1] ]
split
:
var list = ['a', ' ', 'b', 'c', ' ', 'd'];
var split = list.split(' ');
// Iterable:
// [ ['a'], ['b', 'c'], ['d'] ]
- aggregate
- aggregateRight
- aggregateRightSelect
- aggregateSelect
- all
- appendAll
- append
- append2
- append3
- append4
- append5
- append6
- append7
- append8
- append9
- assertAll
- assertAny
- assertCount
- atLeast
- atMost
- average
- awaitAll
- awaitAny
- batch
- batchSelect
- between
- cartesian
- cartesian3
- cartesian4
- cartesian5
- cartesian6
- cartesian7
- cartesian8
- cartesian9
- cartesianSelect
- cartesianSelect3
- cartesianSelect4
- cartesianSelect5
- cartesianSelect6
- cartesianSelect7
- cartesianSelect8
- cartesianSelect9
- compareCount
- concatAll
- concat
- concat2
- concat3
- concat4
- concat5
- concat6
- concat7
- concat8
- concat9
- consume
- countBy
- deconstruct (1, 2, 3, 4, 5, 6, 7, 8, 9)
- defaultIfEmpty
- defaultRangeIfEmpty
- distinct
- elementAtOrDefault
- endsWith
- except
- excludeAt
- exclude
- excludeRange
- fillBackward
- fillForward
- fillMissing
- firstOrDefault
- firstWhereOrDefault
- flatten
- groupBy
- groupByValue
- groupJoin
- groupSelect
- groupSelectValue
- index
- insertAll
- insert
- insert2
- insert3
- insert4
- insert5
- insert6
- insert7
- insert8
- insert9
- insertOrAppendAll
- insertOrAppend
- insertOrAppend2
- insertOrAppend3
- insertOrAppend4
- insertOrAppend5
- insertOrAppend6
- insertOrAppend7
- insertOrAppend8
- insertOrAppend9
- interleaveAll
- interleave
- intersect
- joinMap
- lag
- lagSelect
- lastOrDefault
- lastWhereOrDefault
- lead
- leadSelect
- mathConsumers
- max
- memoize
- min
- move
- nonNull
- ofType
- ofType2
- ofType3
- ofType4
- ofType5
- ofType6
- ofType7
- ofType8
- ofType9
- orderByDescending
- orderBy
- padEnd
- pairwise
- partition
- permutations
- prependAll
- prepend
- prepend2
- prepend3
- prepend4
- prepend5
- prepend6
- prepend7
- prepend8
- prepend9
- preScan
- randomSubset
- repeat
- reverse
- scan
- select
- selectMany
- sequenceEquals
- singleOrDefault
- singleWhereOrDefault
- skipLast
- split
- startsWith
- subsets
- sum
- takeEvery
- takeLast
- thenByDescending
- thenBy
- toHashMap
- toLinkedHashMap
- toMap
- toSplayTreeMap
- toStream
- tryAggregate
- tryAggregateRight
- tryInsertAll
- tryInsert
- tryInsert2
- tryInsert3
- tryInsert4
- tryInsert5
- tryInsert6
- tryInsert7
- tryInsert8
- tryInsert9
- trySingleOrDefault
- trySingleWhereOrDefault
- union
- zip