Dart: alternatives for classic way to loop through arrays where index is needed - dart

I need to loop through list items and I need index, not value.
I know three options:
Classic:
for (final i = 0; i < list.length; i++) {
With asMap:
for (final i in list.asMap().keys)
With Iterable:
for (final i in Iterable.generate(list.length)) {
Non-classic options look to be easier to read, write and less error prone.
How about performance? It seems both options produce iterable, so they should not create performance overhead. Is it correct assessment or I am missing something?

Micro benchmark here (usual caveats apply) https://gist.github.com/jakemac53/16c782ed92f6bbceb98ad83cd257c760.
If your code is perf sensitive, use the "classic" for loop.

While existing alternatives for the classic way are not great from performance perspective, there is work in progress to introduce a better way to loop lists: https://github.com/dart-lang/collection/pull/259#discussion_r1090563595
For now you the extension will enable nice looping:
extension Indexes<T> on List<T> {
Iterable<int> get indexes sync* {
for (var i = 0; i < length; i++) yield i;
}
}
Then we can write:
for (final i in list.indexes)

Using package:collection, you can get a .mapIndexed on an Iterable to give you both the item and the index of the item.
final result = someList.mapIndexed((n, e) => "item $n is $e");

Related

How to fill a list with null values so it is a specific length?

I have a situation where I have a list that can be at most 4 elements.
However, if I have only 1-3 elements to put in that list, how can I fill the remainder with null values?
For example, a List<int?> of length 4 with 2 given elements, should result in:
[1,3] -> [1,3,null,null]
Here's what I'm doing, but maybe there is a better way
List.generate(4, (index) {
try {
final id = given.elementAt(index);
return id;
} catch (error) {
return null;
}
});
The simplest version would probably be:
[for (var i = 0; i < 4; i++) i < given.length ? given[i] : null]
You can use List.generate, but in this case, the function is simple enough that a list literal can do the same thing more efficiently.
(Or, as #jamesdlin says, if you want a fixed-length list, use List.generate).
A more indirect variant could be:
List<GivenType?>.filled(4, null)..setAll(0, given)
where you create a list of four nulls first, then write the given list into it. It's probably just more complicated and less efficient.

Combine multiple Uint8List.view

Assume I want to concatenate N Uint8Lists into a single one.
The naive approach is to simple copy all elements into a new list. However, that seems rather memory in efficient. Instead, I want to create a single Uint8List "view" which simply indexes into the appropriate underlying list instead of copying all its content.
In C++ I'd usually just overwrite operator[] but I am not quite certain how to do this with Uint8Lists in Dart.
In C++, you can make a View class that overrides operator[]. In Dart, you could do the same thing:
class View<T> {
View(this._lists);
List<List<T>> _lists;
T operator [](int index) {
for (var list in _lists) {
if (index < list.length) {
return list[index];
}
index -= list.length;
}
throw RangeError('...');
}
}
You could stop there, but doing just that usually wouldn't be enough in either language. In C++, you'd also want to provide begin() and end() methods for range-based for loops to work. Similarly, in Dart, you'd want to provide the Iterable interface so that for-in would work.
Luckily package:collection (note that this is separate from dart:collection) provides a CombinedListView class that does that work for you. For example:
import 'dart:typed_data';
import 'package:collection/collection.dart';
void main() {
var list1 = Uint8List.fromList([1, 2, 3]);
var list2 = Uint8List.fromList([4, 5, 6]);
var list3 = Uint8List.fromList([7, 8, 9]);
var view = CombinedListView<int>([list1, list2, list3]);
for (var i in view) {
print(i);
}
}

How to enumerate Stream results

I have a Stream, that yields Strings like this:
'apple'
'orange'
'banana'
When I listen to the Stream, I want to assign numbers to the results from 1 to n (Stream lenght) in the order they were yielded like this:
1: 'apple'
2: 'orange'
3: 'banana'
What is the best way to do that?
As there is no enumerate() function like in Python, my attempts so far are pretty bad:
inside Stream definition
int n = 0
await for (var nextString in originStream) {
++n;
yield [n, nextString];
}
when stream is called
myStream.transform(enumerateFunc)
List enumerateFunc(String myStr){
return [n, myStr];
}
I got a suggestion to create a class for this. But I don't want to create a class for simply assing numbers to Stream events in the order they happenned.
Is there a better way to do this?
As #julemand101 suggested, a pretty viable option is to create a class for this purpose and yield the class instead of List:
Stream<String> fruits() async* {
int n = 1;
await for (final String fruit in fruitChannel) {
yield Enumerated(n, fruit);
n++;
}
}

efficient way to do bulk updates where values are assigned serially from a list?

Lets say there is a domain A with property p.
class A{
Integer p
}
I have a list of A i.e
def lis = A.list()
and then i have a list of numbers
def num = [4, 1, 22, ......]
what is the most efficient way to do bulk update in grails where each object of A is assigned a number from num serially.
One way could be
for(int i=0; i<lis.size(); i++){
lis[i].p = num[i]
lis[i].save(flush: true)
}
But this solution i assume is not efficient. Can this be achieved using HQL or other efficient methods? I appreciate any help! Thanks!
if your list of A and numbers is a very great amount of data to treat (if lis.size is for example equal to 10 000), then you should do this :
A.withNewTransaction { status -> // begin a new hibernate session
int stepForFlush = 100
int totalLisSize = A.count()
def lis
for(int k=0; k < totalLisSize; k+=stepForFlush) {
lis = A.list(max: stepForFlush, offset: k) // load only 100 elements in the current hibernate session
...
for(int i=0; i<lis.size(); i++) {
lis[i].p = num[k+i]
lis[i].save()
}
A.withSession { session ->
session.flush() // flush changes to database
session.clear() // clear the hibernate session, the 100 elements are no more attached to the hibernate session
// Then they are now eligible to garbage collection
// you ensure not maintaining in memory all the elements you are treating
}
} // next iteration, k+=100
} // Transaction is closed then transaction is commited = a commit is executed to database,
// and then all changes that has been flush previously are committed.
Note :
In this solution, you do not load all your A elements in memory and it helps when your A.list().size() is very great.
While I agree that your solution is likely to be inefficient, it's mostly due to the fact that you're flushing on every save. So you can get a performance boost by using a transaction; which automatically causes a flush when committed:
A.withTransaction { status ->
...
for(int i=0; i<lis.size(); i++) {
lis[i].p = num[i]
lis[i].save()
}
}
Of course, if you can use the #Transactional annotation, that would be better.
Yes, you can use HQL, but the fundamental problem here is that your list of numbers is arbitrary, so you'd need multiple HQL queries; one for each update.
Try the transactional approach first, since that's the easiest to set up.

How can I use a HashMap of List of String in Vala?

I am trying to use a HashMap of Lists of strings in Vala, but it seems the object lifecycle is biting me. Here is my current code:
public class MyClass : CodeVisitor {
GLib.HashTable<string, GLib.List<string>> generic_classes = new GLib.HashTable<string, GLib.List<string>> (str_hash, str_equal);
public override void visit_data_type(DataType d) {
string c = ...
string s = ...
if (! this.generic_classes.contains(c)) {
this.generic_classes.insert(c, new GLib.List<string>());
}
unowned GLib.List<string> l = this.generic_classes.lookup(c);
bool is_dup = false;
foreach(unowned string ss in l) {
if (s == ss) {
is_dup = true;
}
}
if ( ! is_dup) {
l.append(s);
}
}
Note that I am adding a string value into the list associated with a string key. If the list doesn't exist, I create it.
Lets say I run the code with the same values of c and s three times. Based some printf debugging, it seems that only one list is created, yet each time it is empty. I'd expect the list of have size 0, then 1, and then 1. Am I doing something wrong when it comes to the Vala memory management/reference counting?
GLib.List is the problem here. Most operations on GLib.List and GLib.SList return a modified pointer, but the value in the hash table isn't modified. It's a bit hard to explain why that is a problem, and why GLib works that way, without diving down into the C. You have a few choices here.
Use one of the containers in libgee which support multiple values with the same key, like Gee.MultiMap. If you're working on something in the Vala compiler (which I'm guessing you are, as you're subclassing CodeVisitor), this isn't an option because the internal copy of gee Vala ships with doesn't include MultiMap.
Replace the GLib.List instances in the hash table. Unfortunately this is likely going to mean copying the whole list every time, and even then getting the memory management right would be a bit tricky, so I would avoid it if I were you.
Use something other than GLib.List. This is the way I would go if I were you.
Edit: I recently added GLib.GenericSet to Vala as an alternative binding for GHashTable, so the best solution now would be to use GLib.HashTable<string, GLib.GenericSet<string>>, assuming you're okay with depending on vala >= 0.26.
If I were you, I would use GLib.HashTable<string, GLib.HashTable<unowned string, string>>:
private static int main (string[] args) {
GLib.HashTable<string, GLib.HashTable<unowned string, string>> generic_classes =
new GLib.HashTable<string, GLib.HashTable<unowned string, string>> (GLib.str_hash, GLib.str_equal);
for (int i = 0 ; i < 3 ; i++) {
string c = "foo";
string s = i.to_string ();
unowned GLib.HashTable<unowned string, string>? inner_set = generic_classes[c];
stdout.printf ("Inserting <%s, %s>, ", c, s);
if (inner_set == null) {
var v = new GLib.HashTable<unowned string, string> (GLib.str_hash, GLib.str_equal);
inner_set = v;
generic_classes.insert ((owned) c, (owned) v);
}
inner_set.insert (s, (owned) s);
stdout.printf ("container now holds:\n");
generic_classes.foreach ((k, v) => {
stdout.printf ("\t%s:\n", k);
v.foreach ((ik, iv) => {
stdout.printf ("\t\t%s\n", iv);
});
});
}
return 0;
}
It may seem hackish to have a hash table with the key and value having the same value, but this is actually a common pattern in C as well, and specifically supported by GLib's hash table implementation.
Moral of the story: don't use GLib.List or GLib.SList unless you really know what you're doing, and even then it's generally best to avoid them. TBH we probably would have marked them as deprecated in Vala long ago if it weren't for the fact that they're very common when working with C APIs.
Vala's new can be a little weird when used as a parameter. I would recommend assigning the new list to a temporary, adding it to the list, then letting it go out of scope.
I would also recommend using libgee. It has better handling of generics than GLib.List and GLib.HashTable.

Resources