Reputation: 3149
Assuming I have a List
as below:
final numbers = [1, 0, -1, 1, -3, 0];
I'd like to generate another list where 1
results in true
and 0
results in false
and skip the others. So, in other words, I'd like a method like map
where I can also skip some elements. In terms of test, it would assert to:
expect(newList, equals([true, false, true, false]));
In this result, I'd like to skip -1
and -3
in the list.
How can I achieve this?
Upvotes: 1
Views: 117
Reputation: 71643
The most direct version would be:
var result = numbers.expand<bool>((n) =>
n == 0
? const <bool>[false]
: n == 1
? const <bool>[true]
: const <bool>[]);
The expand
method can do everything map
and where
can, plus more,
Not particularly efficient, but not all bad either.
Another approach is to use a sync*
function:
var result = () sync* {
for (var number in numbers) {
if (number == 0) {
yield false;
} else if (number == 1) {
yield true;
}
}();
If you don't care about creating a list eagerly, that's a also the approach for a list literal:
var result = [for (var number in numbers)
if (number == 0) false else if (number == 1) true
];
Or:
const _map = {0: [false], 1: [true]};
var result = [for (var number in numbers} ...?_map[number]];
The options are endless. In practice, doing where
and map
is probably more readable.
Upvotes: 1
Reputation: 89965
Iterable.map
is a 1:1 mapping; if your input has n elements, then the output must have n elements too.
Using numbers.where(...).map(...)
works, but alternatively:
Iterable.map
first to map either to desired values or to an invalid sentinel value. If your sentinel value is null
, then you can use Iterable.whereType
to filter out them out:
var transformed = numbers.map((n) {
switch (n) {
case 0:
return false;
case 1:
return true;
default:
return null;
}
}).whereType<bool>();
You might prefer this if you want all of your logic in a single callback.for
instead of Iterable.map
and collection-if
instead of Iterable.where
:
var transformed = [
for (var n in numbers)
if (isValidValue(n))
transform(n),
];
where you define isValidValue
and transform
functions with your filtering and transformation logic respectively.Upvotes: 0
Reputation: 3149
Ariel's answer gave me a better idea. In the example of the question, what we'd like to filter out are two values, which are 1
and 0
. The real problem I'm dealing with right now is more complicated than this one, it has a wide range of lookup list. I haven't specifically asked "What is a more programmatic way to do this?" or "What if the lookup list has more values?".
So, if the values I'm looking for are more than these two values, a List
of lookup values and contains method would be better for my case.
final numbers = [1, 0, -1, 1, -3, 0];
final lookup = {1, 0};
final result = numbers.where((n) => lookup.contains(n)).map((n) => n == 1 ? true : false).toList();
print(result);
This has some drawbacks though, some of which that come to my mind are:
contains
, which will perform look-ups in lookup
rather than a simple byte-comparison with n == 1
. So, it isn't great with large lists.int
with an int
in this simple example, but if you have an instance, you need to override hashCode
, otherwise Dart will compare the memory addresses rather than values.lookup
other than Set
might also have performance impact because Set
is inherently O(1) while the others might depend on their standard library implementation, but it's safe to assume they're going to be worse than Set
. (See Ariel's comment).BTW, lookup
does not have to be a Set
. I've done it Set
because lookup
contains distinct values and the order is not important.
Upvotes: 0
Reputation: 1537
Use the .where()
method to get a new iterable with only the elements satisfying a predicate, then use .map()
to apply the transformation on the result.
final numbers = [1, 0, -1, 1, -3, 0];
final result = numbers.where((n) => n == 1 || n == 0).map((n) => n == 1);
print(result);
\\ (true, false, true, false)
Upvotes: 1