Eray Erdin
Eray Erdin

Reputation: 3149

How to `map` an `Iterable` partially in Dart?

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?


Environment

Upvotes: 1

Views: 117

Answers (4)

lrn
lrn

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

jamesdlin
jamesdlin

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:

  • Use 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.
  • Use collection-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

Eray Erdin
Eray Erdin

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:

  • Performance is definitely worse than Ariel's answer. My solution uses 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.
  • hashCode must be overriden for non-primitives. It is easy to compare 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.
  • Using another collection type for 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

Tomer Ariel
Tomer Ariel

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

Related Questions