I have a non-primitive list which I would like to sort.
When I sort it, the UI thread is blocked and the app freezes for a few seconds.
I tried to avoid this by using dart's Isloate compute function but since the parameter sent to the compute function must be a primitive or a list/map of primitives (send method) it didn't work.
To conclude, is there any way to perform a list sort(non-primitive) without blocking the UI thread?
Edit: Clarification - I was trying to call a function through compute and I was passing a list of objects (which I got from a third party plugin) as an argument, those objects had a property of type Iterable and that caused everything to fail - make sure all the types are primitive or List/Map of primitives. With the answers I received and changing the type from Iterable to List it worked.
I'm not sure if I understood your question, but you can sort a list of non primitive elements like this:
final List<Element> elements = [
Element(id: 1),
Element(id: 7),
Element(id: 2),
Element(id: 0)
];
elements.sort((a, b) => a.compareTo(b));
// or
elements.sort((a, b) => a.id > b.id ? 1 : -1);
This would be a print(elements); output:
I/flutter ( 7351): [id: 0, id: 1, id: 2, id: 7]
And this would be the class Element
class Element {
final int id;
Element({this.id});
#override
String toString() => "id: $id";
int compareTo(Element other) => this.id > other.id ? 1 : -1;
}
Edit: To make this asynchronously, you could do this:
Future<List<Element>> asyncSort() async {
print("before sort: $elements");
elements = await compute(_sort, elements);
print("after sort: $elements");
return elements;
}
static List<Element> _sort(List<Element> list) {
list.sort((a, b) => a.compareTo(b));
return list;
}
print("before calling asyncSort(): $elements");
asyncSort();
print("after calling asyncSort(): $elements");
And this would be the output:
I/flutter ( 7351): before calling asyncSort(): [id: 1, id: 7, id: 2, id: 0]
I/flutter ( 7351): before sort: [id: 1, id: 7, id: 2, id: 0]
I/flutter ( 7351): after calling asyncSort(): [id: 1, id: 7, id: 2, id: 0]
I/flutter ( 7351): after sort: [id: 0, id: 1, id: 2, id: 7]
Edit2: If you want to send a compare function to compute, you could use a Map or a List of arguments with the list and the compare function and pass that instead of the list, because compute just takes one argument. Here is an example:
Future<List<Element>> asyncSort() async {
print("before sort: $elements");
Map args = {"list": elements, "compare": compare};
elements = await compute(_sortWith, args);
print("after sort: $elements");
return elements;
}
static List<Element> _sortWith(Map args) {
List<Element> list = args["list"];
Function(Element a, Element b) compare = args["compare"];
list.sort((a, b) => compare(a, b));
return list;
}
static int compare(Element a, Element b) {
return a.id > b.id ? 1 : -1;
}
This is how i use compute, just put all parameters to a list, then call it in a List of dynamic object:
image = await compute(getCropImage, [copyFaces, streamImg]);
imglib.Image getCropImage(List<dynamic> values) {
var face = values[0]; // copyFaces
var image = values[1]; // streamImg
}
Related
I have a map that initially has empty lists as values. And a list with various data. What I want to do is to search the list for items similar to the map keys and add them as values.
This is my code:
void main() {
List list = [];
var maps= {
'A' : [],
'B' : [],
'C' : [],
};
List y = ['A','B','C','A','B','C'];
maps.forEach((key,values){
List temp =[];
for (var i in y){
if(key == i) {
list.add(i);
}
temp = list;
}
maps[key] = temp;
list.clear();
print(maps);
});
}
This is the result I get
{A: [], B: [], C: []}
And I need this result
{A: [A,A], B: [B,B], C: [C,C]}
Thank you very much for your help.
Your issue is that at no point are you directly manipulating the existing List with the matching key in your Map.
And you don't need the list or the temp variables.
var maps = {
'A': [],
'B': [],
'C': [],
};
List y = ['A', 'B', 'C', 'A', 'B', 'C'];
maps.forEach((key, value) {
for (String char in y) {
if(char == key) {
maps[key]!.add(char); // this is what adds the matching String from the list to the corresponding list in the map
}
}
});
log(maps.toString()); // {A: [A, A], B: [B, B], C: [C, C]}
I’ve got an Observable that has an array of objects and I would like to convert them to a different object using a second observable. This is part of a larger project so to simplify this for my question I am going to have an observable that has an array of number and I would like to convert them to a string. I started with the following.
const response$ = of({results: [1, 2, 3]});
response$.pipe(
map((response) => {
return response.results.map((id) => {
return id.toString();
})
})
)
.subscribe((response: string[]) => {
console.log(response);
})
The response in the subscribe will be an array of string as expected. Now I need to use a second observable to convert the number to string (again just to make the question simpler). So I replaced return id.toString() with return of(id.toString()) to simulate making a second call to an observable.
const response$ = of({results: [1, 2, 3]});
response$.pipe(
map((response) => {
return response.results.map((id) => {
return of(id.toString());
})
}),
)
.subscribe((response: Observable<string>[]) => {
})
Now the signature of the response is Observable<string>[] but I need the response to be string[] so I started reading about other RxJS operators and I ended up with the following.
const response$ = of({results: [1, 2, 3]});
response$.pipe(
concatMap((response) => {
return response.results.map((id) => {
return of(id.toString());
})
}),
concatAll()
)
.subscribe((response: string) => {
console.log('bar', response);
})
I used concatMap() and concatAll() because I need the call to the second observable to happen in sequence. The problem now is that my response is a string and I get three calls to the subscriber “1” “2” “3”. I need one response of string[]. Can someone explain how to take an Observable<string>[] and convert it to Observable<string[]> in my example?
I think what you're looking for is this:
const response$ = of({results: [1, 2, 3]});
response$.pipe(
switchMap((response) => {
// map array to observables and execute all with forkJoin
return forkJoin(...response.results.map((id) => {
return of(id.toString());
}))
})
)
.subscribe((response: string) => {
console.log('bar', response);
})
however, this is going to execute in parallel. if you need sequential execution on the inner, you can use concat and reduce
const response$ = of({results: [1, 2, 3]});
response$.pipe(
switchMap((response) => {
// map array to observables and execute all with concat and collect results with reduce
return concat(...response.results.map((id) => {
return of(id.toString());
})).pipe(reduce((acc, v) => acc.concat([v]), []))
})
)
.subscribe((response: string) => {
console.log('bar', response);
})
only thing to be careful of is to make sure response.results has items in it. may need a length check like:
if (!response.results.length)
return of([])
Prior to null-safe dart, the following was valid syntax:
final list = [1, 2, 3];
final x = list.firstWhere((element) => element > 3, orElse: () => null);
if (x == null) {
// do stuff...
}
Now, firstWhere requires orElse to return an int, opposed to an int?, therefore I cannot return null.
How can I return null from orElse?
A handy function, firstWhereOrNull, solves this exact problem.
Import package:collection which includes extension methods on Iterable.
import 'package:collection/collection.dart';
final list = [1, 2, 3];
final x = list.firstWhereOrNull((element) => element > 3);
if (x == null) {
// do stuff...
}
You don't need external package for this instead you can use try/catch
int? x;
try {
x = list.firstWhere((element) => element > 3);
} catch(e) {
x = null;
}
A little bit late but i came up with this:
typedef FirstWhereClosure = bool Function(dynamic);
extension FirstWhere on List {
dynamic frstWhere(FirstWhereClosure closure) {
int index = this.indexWhere(closure);
if (index != -1) {
return this[index];
}
return null;
}
}
Example use:
class Test{
String name;
int code;
Test(code, this.name);
}
Test? test = list.frstWhere(t)=> t.code==123);
An alternative is that you set a nullable type to the list.
Instead of just [1, 2, 3], you write <int?>[1, 2, 3], allowing it to be nullable.
void main() {
final list = <int?>[1, 2, 3];
final x = list.firstWhere(
(element) => element != null ? (element > 3) : false,
orElse: () => null);
print(x);
}
This should work, and it's a better solution:
extension IterableExtensions<T> on Iterable<T> {
T? firstWhereOrNull(bool Function(T element) comparator) {
try {
return firstWhere(comparator);
} on StateError catch (_) {
return null;
}
}
}
To add to #Alex Hartfords answer, and for anyone who doesn't want to import a full package just for this functionality, this is the actual implementation for firstWhereOrNull from the collection package that you can add to your app.
extension FirstWhereExt<T> on List<T> {
/// The first element satisfying [test], or `null` if there are none.
T? firstWhereOrNull(bool Function(T element) test) {
for (final element in this) {
if (test(element)) return element;
}
return null;
}
}
Can I sort List<dynamic> in dart?
List<dynamic> list= [9,10,'Plus One'];
list.sort();
print(list);
I expect the result like 9,10,'Plus One' Or 'Plus One', 9, 10
You just need to provide a callback to List.sort that orders heterogeneous types the wya you want. For example, assuming that your heterogeneous List contains only ints and Strings, you could do:
List<dynamic> list = [9, 10, 'Plus One'];
list.sort((a, b) {
if ((a is int && b is int) || (a is String && b is String)) {
return a.compareTo(b);
}
if (a is int && b is String) {
return -1;
} else {
assert(a is String && b is int);
return 1;
}
});
print(list);
If you need to potentially handle other types, you will need to adjust the callback appropriately.
If you want to sort dynamic list and want string before number(int or double) try
this code:
List<dynamic> list = [
'mahmoud',
14,
'zika',
9,
10,
'plus One',
5,
'banana',
1,
2.5,
'apple',
2,
1.2,
'ball'
];
list.sort(
(a, b) {
if ((a is num && b is num) || (a is String && b is String)) {
return a.compareTo(b);
}
// a Greater than b return 1
if (a is num && b is String) {
return 1;
}
// b Greater than a return -1
else if (a is String && b is num) {
return -1;
}
// a equal b return 0
return 0;
},
);
print(list);// [apple, ball, banana, mahmoud, plus One, zika, 1, 1.2, 2, 2.5, 5, 9, 10, 14]
How I can get the most popular number from a list in dart without using any third party libraries?
var list = [0, 1, 1, 2, 2, 2, 3, 3, 4]; // most popular number is 2
If there are two or more popular numbers then the output should be a List with both values. Example:
One popular number:
var list = [0, 1, 1, 2, 2, 2, 3, 3, 4];
// Output should be [2]
Two or more popular numbers:
var list = [0, 1, 1, 2, 2, 2, 3, 3, 3];
// Output should be [2, 3]
Thank you in advance for your help!
This works...you can optimize it
var list = [1, 1, 2, 2, 3, 4, 5];
list.sort();
var popularNumbers = [];
List<Map<dynamic, dynamic>> data = [];
var maxOccurrence = 0;
var i = 0;
while (i < list.length) {
var number = list[i];
var occurrence = 1;
for (int j = 0; j < list.length; j++) {
if (j == i) {
continue;
}
else if (number == list[j]) {
occurrence++;
}
}
list.removeWhere((it) => it == number);
data.add({number: occurrence});
if (maxOccurrence < occurrence) {
maxOccurrence = occurrence;
}
}
data.forEach((map) {
if (map[map.keys.toList()[0]] == maxOccurrence) {
popularNumbers.add(map.keys.toList()[0]);
}
});
print(popularNumbers);
try this to count each element in list:
var list = [0, 1, 1, 2, 2, 2, 3, 3, 4];
var popular = Map();
list.forEach((l) {
if(!popular.containsKey(l)) {
popular[l] = 1;
} else {
popular[l] +=1;
}
});
I guess I found the solution.
Let me explain it to you:
I had queried through your list and checked whether the keys of the map contains the element or not. If the map does not contain the element as the key then, it will create a key from the element and pass 1 as the value. If the map does contain the element as a key then it will simply increment the value.
Once the map is ready, I had sorted the map values and stored them in a List. From the sorted map values I had taken the last element from the list of sorted values because we had sorted it in ascending order so the most popular value will be at last.
At last, I had queried through the map and check whether the value of the particular key is equal to the popularValue or not. If it is then we are adding the current key and value to the mostPopularValues list.
If I got something wrong please let me know.
void main() {
List list = [0, 1, 1, 1, 2, 2, 2, 3, 3, 4];
List mostPopularValues = [];
var map = Map();
list.forEach((element) {
if (!map.containsKey(element)) {
map[element] = 1;
} else {
map[element] += 1;
}
});
print(map);
// o/p : {0: 1, 1: 3, 2: 3, 3: 2, 4: 1}
List sortedValues = map.values.toList()..sort();
print(sortedValues);
// o/p : [1, 1, 2, 3, 3]
int popularValue = sortedValues.last;
print(popularValue);
// o/p : 3
map.forEach((k, v) {
if (v == popularValue) {
mostPopularValues.add("$k occurs $v time in the list");
}
});
print(mostPopularValues);
// o/p : [1 occurs 3 time in the list, 2 occurs 3 time in the list]
}
Not sure if that's the best solution, but it works pretty well. Let me know if there are any doubts.
final list = [0, 1, 1, 2, 2, 2, 3, 3, 4];
// Count occurrences of each item
final folded = list.fold({}, (acc, curr) {
acc[curr] = (acc[curr] ?? 0) + 1;
return acc;
}) as Map<dynamic, dynamic>;
// Sort the keys (your values) by its occurrences
final sortedKeys = folded.keys
.toList()
..sort((a, b) => folded[b].compareTo(folded[a]));
print('Most popular value: ${sortedKeys.first}'); // 1
print('Second most popular value: ${sortedKeys[1]}'); // 2
I have solved this problem by defining an extension on Iterable:
extension MostPopularItemsExtension<E> on Iterable<E> {
/// Returns the most popular items, where all items in the returned
/// list have the same number of occurances. If [this] is empty, returns an
/// empty list
///
/// Examples:
/// `[1,2,3,2].mostPopularItems() == [2]`
/// `[1,1,2,2].mostPopularItems() == [1,2]`
Iterable<E> mostPopularItems() {
if (isEmpty) return [];
final itemsCounted = <E, int>{};
for (final e in this) {
if (itemsCounted.containsKey(e)) {
itemsCounted[e] = itemsCounted[e]! + 1;
} else {
itemsCounted[e] = 1;
}
}
final highestCount = (itemsCounted.values.toList()..sort()).last;
return itemsCounted.entries
.where((e) => e.value == highestCount)
.map((e) => e.key);
}
}
The basic idea is to count all occurrences of each item in a Map object, get the highest count from this map and then return all items that have that specific number of occurrences.