Dart list - remove nulls [duplicate] - dart

This question already has answers here:
How to convert a List<T?> to List<T> in null safe Dart?
(2 answers)
Closed 3 months ago.
Is there a more elegant way of removing nulls from a Dart list than this:
List<T> nullFilter<T>(List<T?> list) =>
list.where((T? e) => e != null)
// This should not be necessary
.map((e) => e!)
.toList();

Something like this makes it a bit more clean:
List<T> nullFilter<T>(List<T?> list) => [...list.whereType<T>()];
void main() {
final list1 = [1, 2, 3, null];
print('${list1.runtimeType}: $list1');
// List<int?>: [1, 2, 3, null]
final list2 = nullFilter(list1);
print('${list2.runtimeType}: $list2');
// List<int>: [1, 2, 3]
}

You could use the very popular collection package (which is an official core package published by the Dart Team) as such:
final list2 = list1.whereNotNull();
Or as pointed out by the comment, if you don't want it as an iterable:
final list2 = list1.whereNotNull().toList();
For reference, the implementation for that is as follows (if you for some reason don't want to include the package but create the extension yourself in your own file):
/// Extensions that apply to iterables with a nullable element type.
extension IterableNullableExtension<T extends Object> on Iterable<T?> {
/// The non-`null` elements of this `Iterable`.
///
/// Returns an iterable which emits all the non-`null` elements
/// of this iterable, in their original iteration order.
///
/// For an `Iterable<X?>`, this method is equivalent to `.whereType<X>()`.
Iterable<T> whereNotNull() sync* {
for (var element in this) {
if (element != null) yield element;
}
}
}

Related

SplayTreeSet contains duplicate values

I have a SplayTreeSet of the objects ChatRoomListModel. I'd like to order my set based on the objects DateTime createTime value.
I'm not sure where I'm going wrong because there's duplicate items being added item.
I later down the line perform a _latestMessages.add(newMessage) and it's not actually calling the overloads and there's duplicates being added.
I tested by using _latestMessage.contains(newMessageButSameChatRoomId), it returns false.
When I perform _latestMessage.toSet() every duplicate goes away.
How can I get SplayTreeSet to use my overloading equals?
Thanks!
ObservableSet<ChatRoomListModel> _latestMessages = ObservableSet.splayTreeSetFrom(
ObservableSet(),
compare: (a, b) => b.compareTo(a),
);
The ChatRoomListModel model has the following methods and overloads:
int compareTo(ChatRoomListModel other){
return messagesModel.createTime.compareTo(other.messagesModel.createTime);
}
ChatRoomListModel copyWith({
String? firstName,
String? otherUserId,
MessagesModel? messagesModel,
}) {
return ChatRoomListModel(
firstName: firstName ?? this.firstName,
otherUserId: otherUserId ?? this.otherUserId,
messagesModel: messagesModel ?? this.messagesModel,
);
}
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ChatRoomListModel &&
runtimeType == other.runtimeType &&
messagesModel.chatRoomId == other.messagesModel.chatRoomId;
#override
int get hashCode => messagesModel.chatRoomId.hashCode;
Your issue is that you have two completely different notions of what it means for two ChatRoomListModel objects to be "equal". You provide both compareTo and operator == implementations, but they consider different sets of properties, so compareTo can return 0 when operator == returns false, which is confusing at best. SplayTreeMap considers only compareTo, not operator ==. From the SplayTreeSet documentation:
Elements of the set are compared using the compare function passed in the constructor, both for ordering and for equality. If the set contains only an object a, then set.contains(b) will return true if and only if compare(a, b) == 0, and the value of a == b is not even checked.
I'm presuming what you call "duplicates" are elements that have equal chatRoomIds but that have different creation times, and creation times are the only things that your SplayTreeSet cares about.
If your goal is to maintain only the latest message per chatRoomId, you need to maintain a data structure that uses the chatRoomId (a String) as a key. The natural collection for that would be a Map<String, ChatRoomListModel>. (Since the ChatRoomListModel knows its own chatRoomId, it also could just be a HashSet with explicit equals and hashCode callbacks.)
If you additionally want to keep messages in chronological order, you either will need to explicitly sort them afterward or maintain a separate data structure that keeps them in chronological order. You could continue using a SplayTreeSet for that. Basically before you add any entry to the SplayTreeSet, check the Map first to see if an existing entry for that chatRoomId.
I don't fully understand your data structures, but here's an example that you presumably can adapt:
import 'dart:collection';
class Message {
Message(this.creationTime, {required this.chatRoomId, required this.text});
final DateTime creationTime;
final String chatRoomId;
final String text;
#override
String toString() => '$creationTime: [$chatRoomId] $text';
}
class Messages {
final _latestMessages = <String, Message>{};
final _orderedMessages = SplayTreeSet<Message>((message1, message2) {
var result = message1.creationTime.compareTo(message2.creationTime);
if (result != 0) {
return result;
}
result = message1.chatRoomId.compareTo(message2.chatRoomId);
if (result != 0) {
return result;
}
return message1.text.compareTo(message2.text);
});
void add(Message message) {
var existingMessage = _latestMessages[message.chatRoomId];
if (existingMessage != null &&
message.creationTime.compareTo(existingMessage.creationTime) < 0) {
// An existing message exists with a later creation date. Ignore the
// incoming message.
return;
}
_latestMessages[message.chatRoomId] = message;
_orderedMessages.remove(existingMessage);
_orderedMessages.add(message);
}
void printAll() => _orderedMessages.forEach(print);
}
void main() {
var messages = Messages();
messages.add(Message(
DateTime(2023, 1, 1),
chatRoomId: 'foo',
text: 'Hello foo!',
));
messages.add(Message(
DateTime(2023, 1, 2),
chatRoomId: 'bar',
text: 'Goodbye bar!',
));
messages.add(Message(
DateTime(2023, 1, 2),
chatRoomId: 'foo',
text: 'Goodbye foo!',
));
messages.add(Message(
DateTime(2023, 1, 1),
chatRoomId: 'bar',
text: 'Hello bar!',
));
messages.printAll();
}

Generic method that returns List<T> instead returns List<dynamic>

I tried writing a simple generic method that would iteratively copy a nested List, for example a List<List<int>>. But unfortunately, the recursive call seems to always return List<dynamic>, so I get the following error
The argument type List<dynamic> can't be assigned to the parameter type T
List<T> listDeepCopy<T>(List<T> list){
List<T> newList = List<T>();
list.forEach((value) {
if( value is List ){
newList.add(listDeepCopy(value)); // <-- listDeepCopy() always returns List<dynamic>
}
else{
newList.add(value);
}
});
return newList;
}
So if I call
List<List<int>> list = [[1,2],[3,4]];
List<List<int>> copy = listDeepCopy(list);
T is List<int>
value is T - i.e. List<int>
listDeepCopy(value) should equal listDeepCopy<List<int>>, which would return a List<int>, which should be possible to add to newList, which is a List<List<int>>
Where am I going wrong here, and how can I make something like this work?
I probably would implement it as:
List<T> listDeepCopy<T>(List<T> list) {
var copy = list.toList();
for (var i = 0; i < copy.length; i += 1) {
var element = copy[i];
if (element is List) {
copy[i] = listDeepCopy(element) as T;
}
}
return copy;
}
void main() {
List<List<int>> list = [
[1, 2],
[3, 4]
];
List<List<int>> copy = listDeepCopy(list);
list[0][0] = 99;
print(copy); // Prints: [[1, 2], [3, 4]]
}
A problem with your approach is that Dart cannot properly infer the generic type parameter for that recursive listDeepCopy(value) call. value is of type T that is known to be a List (which is shorthand for List<dynamic>), and I am not aware of a way to extract the static element type. (Maybe #lrn will see this and provide a better, more complete explanation.)
In such a case, it's better to rely on polymorphism by calling a method on the List that returns a copy of itself: .toList().
(As an example where this matters, consider a shallow copy scenario:
List<T> shallowCopy1<T>(List<T> list) => <T>[...list];
List<T> shallowCopy2<T>(List<T> list) => list.toList();
extension StaticType<T> on T {
Type get staticType => T;
}
void main() {
List<num> list = <int>[1, 2, 3];
var copy1 = shallowCopy1(list);
var copy2 = shallowCopy2(list);
print('original: staticType: ${list.staticType}, runtimeType: ${list.runtimeType}');
print('copy1: staticType: ${copy1.staticType}, runtimeType: ${copy1.runtimeType}');
print('copy2: staticType: ${copy2.staticType}, runtimeType: ${copy2.runtimeType}');
}
Although both copies preserve the static type of the original List, only copy2 preserves the object's actual (runtime) type. A proper copy depends on the runtime type of the object being copied, and the only robust way to do that is for the object to create a copy of itself.)
You can't do it the way you are trying to do it.
The problem is that deepClone<T> converts a List<dynamic> to a List<T> (which is fine) and then tries to convert elements that are themselves lists into typed list ... but you don't know the type.
In effect, when you check that value is List, you don't know what kind of list to convert it to.
There are two cases:
Either T is List<X> or Iterable<X> for some type X, but you have no way to get your hands on that X. Dart doesn't allow you to destructure types at runtime.
Or T is Object or another general supertype with no "list element" type inside it, and then you simply do not have any information about what List type to convert the nested list to. (That's actually the simplest case, because then you should simply not deepClone the list at all).
There is a way to figure out which case you are in (<T>[] is List<Iterable<Object?>>), but it won't help you in the former case, unless you want to do an exhaustive search of all the possible types that X might be.
What I'd do instead is to build a converter, instead of using a single function.
abstract class Cloner<T> {
const factory Cloner() = _ValueCloner<T>;
T clone(dynamic source);
Cloner<List<T>> get list => _ListCloner(this);
}
abstract class _BaseCloner<T> implements Cloner<T> {
const _BaseCloner();
Cloner<List<T>> get list => _ListCloner<T>(this);
}
class _ValueCloner<T> extends _BaseCloner<T> {
const _ValueCloner();
T clone(dynamic source) => source as T;
}
class _ListCloner<T> extends _BaseCloner<List<T>> {
final Cloner<T> _base;
_ListCloner(this._base);
List<T> clone(dynamic source) =>
<T>[for (var o in source as List<dynamic>) _base.clone(o)];
}
Then, if you actually know the type of the data, you can build your cloner as:
var typedList =
Cloner<int>().list.list.clone(
<dynamic>[<dynamic>[1, 2], <dynamic>[3, 4]]);
which yields a List<List<int>> with the value <List<int>>[<int>[1, 2], <int>[3, 4]].

Is there a way to pass an argument to the `test` function of the `firstWhere` method of an iterable

I am learning Dart and I'm following the Codelabs tutorial on iterable collections.
I have just read about the firstWhere method of iterables for finding the first element that satisfies some criterion.
The tutorial gives an example similar to the following:
bool predicate(String item, {int minLength = 6}) => item.length > minLength;
void main() {
const items = ['Salad', 'Popcorn', 'Toast', 'Lasagne'];
var foundItem = items.firstWhere(predicate);
print(foundItem);
}
Which would print Popcorn as it is the first string with 6 or more characters.
I'm wondering whether it is possible to pass the minLength argument to predicate when calling items.firstWhere(predicate).
sure, but like this:
final minLength = 6;
final foundItem = items.firstWhere((String item) => item.length > minLength));
what you example is doing is just extracting the method (String item) => item.length > minLength; to a separate global variable. which isn't necessary and I wouldn't recommend.

How to convert an Iterable of type X to type Y Re: Iterable<WordPair> to Iterable<String>

In pubspec.yaml, I'm using english_words library to generate wordpairs:
dependencies:
flutter:
sdk: flutter
# Contains a few thousand of the most used English words
# plus some utility functions.
english_words: ^3.1.0
Now the WordPair Class is not a subtype of String and so I can't use the Iterable's lambdas or functions like cast or retype to 'cast' the 'WordPairs' to Strings.
So, I had to write the function called getWords().
See below the Dart file, Model.dart, that contains this implementation.
You'll see the old line commented out where it was returning in the getter the type Iterable.
Would there be a more efficient way to do this?
For example, I didn't want to involve a List Class in the conversion, but I can't find any other way to successfully do this.
Thanks.
---------------- Model.dart
import 'package:english_words/english_words.dart' show WordPair, generateWordPairs;
import 'dart:collection';
/// Model Class
///
class Model {
String get randomWordPair => new WordPair.random().asPascalCase;
// Iterable<WordPair> get wordPairs => generateWordPairs().take(10);
Iterable<String> get wordPairs => getWords();
Iterable<String> getWords(){
Iterable<WordPair> pairs = generateWordPairs().take(10);
ListWords<String> words = new ListWords();
for (var pair in pairs) {
words.add(pair.asString);
}
return words;
}
}
class ListWords<E> extends ListBase<E> {
final List<E> l = [];
set length(int newLength) { l.length = newLength; }
int get length => l.length;
E operator [](int index) => l[index];
void operator []=(int index, E value) { l[index] = value; }
}
In Dart 2 you can use
iterable.cast<NewType>()
but it is prone to lead to inefficiency if the resulting list is accessed often, because it wraps the original iterable into a new one and has to forward every access.
Usually more efficient are
new List<NewType>.of(oldList)
or
new List.from<NewType.from(oldList)
I was not able to derive the difference between .of() and from() from the docs though (https://api.dartlang.org/dev/2.0.0-dev.50.0/dart-core/List/List.from.html, https://api.dartlang.org/dev/2.0.0-dev.50.0/dart-core/List/List.of.html)
At first glance, a loop that is collecting the result of an expression can generally be replaced with an appropriate .map method invocation on an Iterable. See if that will help.

How to sort map value?

I have this map:
var temp= {
'A' : 3,
'B' : 1,
'C' : 2
};
How to sort the values of the map (descending). I know, I can use temp.values.toList()..sort().
But I want to sort in context of the keys like this:
var temp= {
'B' : 1,
'C' : 2
'A' : 3,
};
This example uses a custom compare function which makes sort() sort the keys by value. Then the keys and values are inserted into a LinkedHashMap because this kind of map guarantees to preserve the order.
Basically the same as https://stackoverflow.com/a/29629447/217408 but customized to your use case.
import 'dart:collection';
void main() {
var temp= {
'A' : 3,
'B' : 1,
'C' : 2
};
var sortedKeys = temp.keys.toList(growable:false)
..sort((k1, k2) => temp[k1].compareTo(temp[k2]));
LinkedHashMap sortedMap = new LinkedHashMap
.fromIterable(sortedKeys, key: (k) => k, value: (k) => temp[k]);
print(sortedMap);
}
Try it on DartPad
The SplayTreeMap has a named constructor which accepts map and a comparator which is used to sort given map while building new map. Since SplayTreeMap is a descendant of Map you can easily substitute it.
import 'dart:collection';
void main() {
var unsorted = {'A': 3, 'B': 1, 'C': 2};
final sorted = SplayTreeMap.from(
unsorted, (key1, key2) => unsorted[key1].compareTo(unsorted[key2]));
print(sorted);
}
final Map<String, ClassCategory> category;
...
Map<String, ClassCategory> sorted = SplayTreeMap.from(category,
(key1, key2) => category[key1]!.title.compareTo(category[key2]!.title));
for (var item in sorted.entries) {
...
}

Resources