Is this perhaps a Dart bug? - dart

While trying to update a List of List of Strings in a map using the short notation with the fat arrow I get "This expression has type 'void' and can't be used." error. If I don't use the short notation and instead use an intermediate variable and return it it works fine.
I'm trying to update a map of the following type:
Map<String, List<List<String>>> mapWithAListOfListsOfStrings = {};
This is the code using the fat arrow notation.
void main() {
Map<String, List<List<String>>> mapWithAListOfListsOfStrings = {};
String string1 = 'Lorem ipsum dolor sit amet';
String string2 = 'consectetur adipiscing elit';
for (String string in [string1, string2]) {
List<String> splitString = string.split(' ');
mapWithAListOfListsOfStrings.update('1', (list) => list.add(splitString), ifAbsent: () => [splitString]);
}
print("Map contains: ${mapWithAListOfListsOfStrings.toString()}");
}
This is the error I get:
sample.dart:8:56: Error: This expression has type 'void' and can't be used.
mapWithAListOfListsOfStrings.update('1', (list) => list.add(splitString), ifAbsent: () => [splitString]);
This is my workaround that works:
void main() {
Map<String, List<List<String>>> mapWithAListOfListsOfStrings = {};
String string1 = 'Lorem ipsum dolor sit amet';
String string2 = 'consectetur adipiscing elit';
for (String string in [string1, string2]) {
List<String> splitString = string.split(' ');
mapWithAListOfListsOfStrings.update('1', (list) {
List<List<String>> returnList = list;
returnList.add(splitString);
return returnList;
}, ifAbsent: () => [splitString]);
}
print("Map contains: ${mapWithAListOfListsOfStrings.toString()}");
}
And this is the desired result:
Map contains: {1: [[Lorem, ipsum, dolor, sit, amet], [consectetur, adipiscing, elit]]}
I'm happy to leave it like that. I just wonder why the first approach throws an error.
Thanks in advance.

The problem is indeed that the return type of list.add is void, and not the list itself. The update method requires its argument function to return the new value for the provided key, and => list.add(...) doesn't do that, it just returns void.
Your workaround could be reduced to:
(list) {
list.add(splitString);
return list;
}
or to:
(list) => list..add(splitString)
but the real problem is that you shouldn't be using map.update at all. That function is intended to replace the existing value for a key, not to keep the list value after adding something to it. You have to go out of your way to return the value again, instead of just using a function which doesn't change the value of the key (except if it isn't here).
I'd write it as:
mapWithAListOfListOfStrings.putIfAbsent('1', () =>[]).add(splitString);
The putIfAbsent returns the existing value, or inserts a value if there isn't one yet and then returns that value, so this really does:
Ensure that my map['1'] exists and is a list
Do map['1'].add(splitString)
It just does so without needing to look up '1' twice.

Related

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.

Retrieve an element of a list that satisfy a condition

This is my model class:
class Contact {
String name;
String email;
int phoneNo;
Contact(this.name, this.email, this.phoneNo);
}
Suppose I have a list of contacts like below:
List<Contact> contacts = [
new Contact('John', 'john#c.com', 002100),
new Contact('Lily', 'lily#c.com', 083924),
new Contact('Abby', 'abby#c.com', 103385),
];
I want to get John's phone number from contacts List, how can I do that?
singleWhere throws if there are duplicates or no element that matches.
An alternative is firstWhere with orElse https://api.dartlang.org/stable/1.24.3/dart-core/Iterable/firstWhere.html
var urgentCont = contacts.firstWhere((e) => e.name == 'John', orElse: () => null);
print(urgentCont?.phoneNo?.toString()?.padLeft(6, '0') ?? '(not found)');//Output: 002100
This is how I do it using singleWhere:
var urgentCont = contacts.singleWhere((e) => e.name == 'John');
print(urgentCont.phoneNo.toString().padLeft(6, '0'));//Output: 002100
singleWhere(bool test(E element)) → E Returns the single element that
satisfies test.
And there is some other methods in List class. As a example where():
where(bool test(E element)) → Iterable<E> Returns a new lazy Iterable
with all elements that satisfy the predicate test.
Update:
singleWhere() throws an error when there is no matching elements(Bad state: No element). And if there are duplicates, will throw Bad state: Too many elements
So, the best one is firstWhere according to #GunterZochbauer(refer his answer)

search in maps dart2 , same as list.indexOf?

I Use this sample for search in Map but not work :|:
var xmenList = ['4','xmen','4xmen','test'];
var xmenObj = {
'first': '4',
'second': 'xmen',
'fifth': '4xmen',
'author': 'test'
};
print(xmenList.indexOf('4xmen')); // 2
print(xmenObj.indexOf('4xmen')); // ?
but I have error TypeError: xmenObj.indexOf$1 is not a function on last code line.
Pelease help me to search in map object simple way same as indexOf.
I found the answer:
print(xmenObj.values.toList().indexOf('4xmen')); // 2
or this:
var ind = xmenObj.values.toList().indexOf('4xmen') ;
print(xmenObj.keys.toList()[ind]); // fifth
Maps are not indexable by integers, so there is no operation corresponding to indexOf. If you see lists as specialized maps where the keys are always consecutive integers, then the corresponding operation should find the key for a given value.
Maps are not built for that, so iterating through all the keys and values is the only way to get that result.
I'd do that as:
K keyForValue<K, V>(Map<K, V> map, V value) {
for (var entry in map.entries) {
if (entry.value == value) return key;
}
return null;
}
The entries getter is introduced in Dart 2. If you don't have that, then using the map.values.toList().indexOf(value) to get the iteration position, and then map.keys.elementAt(thatIndex) to get the corresponding key.
If you really only want the numerical index, then you can skip that last step.
It's not amazingly efficient (you allocate a new list and copy all the values). Another approach is:
int indexOfValue<V>(Map<Object, V> map, V value) {
int i = 0;
for (var mapValue in map.values) {
if (mapValue == value) return i;
i++;
}
return -1;
}
You can search using .where(...) if you want to find all that match or firstWhere if you assume there can only be one or you only want the first
var found = xmenObj.keys.firstWhere(
(k) => xmenObj[k] == '4xmen', orElse: () => null);
print(xmenObj[found]);

Dart Map syntax: putIfAbsent('FirstKey',() => 1); What is: ()?

In editor hint you get:
scores.putIfAbsent(key, () => numValue);
I am adding single "rows" to my Maps with commands:
myMap.putIfAbsent(9, () => 'Planned');
yourMap.putIfAbsent('foo', () => true);
so: what does that () mean ?
The putIfAbsent function takes two arguments, the key and a function that will return the new value, if needed.
See https://api.dartlang.org/apidocs/channels/stable/dartdoc-viewer/dart-core.Map#id_putIfAbsent
The reason for the second argument to be a function returning the new value, and just not the value itself, is that if the map already contains the key, it's sometimes undesirable to create the value.
Consider the following example:
var map = ...;
var key = ...;
map.putIfAbsent(key, new Value());
If map already contains key, the new value object is not used at all. If the Value object is some heavy or expensive to allocate object, this is an unwanted side-effect.
By taking a function instead
var map = ...;
var key = ...;
map.putIfAbsent(key, () => new Value());
It will only execute the function if the key is not present in map, and the value is needed.
So, to answer the syntax question. A expression of the form () => ... is a short hand of a function expression, returning the result of the first expression. A small example:
var function = () => "some string";
var str = function();
print(str);
will print "some string".
() can't be seen alone here its () => that makes the difference.
// adds the value of numValue
scores.putIfAbsent(key, numValue);
// adds an (anonymous) function that takes 0 arguments and returns the value of numValue.
scores.putIfAbsent(key, () => numValue);
So this two forms are totally different. The first adds a value and the second adds a function.
When we assume numValue has the value 5 when putIfAbsent is called then in the first case
var x = scores[key];
// here var x has the value 5
and in the second case
var x = scores[key];
// here var x references a function
var y = x();
// calling the function executes its body which is `return numValue` (the `return` is implicit in the shorthand function form)
// here var y has the value 5
() indicates that the expression doesn't have any input parameters.

Resources