I want to sort a list based on a boolean using Comparable in dart. I tried the following but was not able to do it.
In the list, all the elements which are true should come first rest of the list should remain as it is.
class Item implements Comparable<Item> {
int id;
String name;
int price;
bool isAvailable;
Item({this.id, this.name, this.price, this.isAvailable = false});
#override
int compareTo(Item other) {
if (isAvailable) {
return -1;
}
return 0;
}
}
void main() {
Item item = new Item(id: 1, name: "Item one", price: 1000);
Item item2 = new Item(id: 2, name: "Item two", price: 2000);
Item item3 =
new Item(id: 3, name: "Item three", price: 500, isAvailable: true);
List<Item> items = [item, item2, item3];
items.sort();
items.forEach((Item item) {
print('${item.id} - ${item.name} - ${item.price}');
});
}
This should print
3 - Item three - 500
1 - Item one - 1000
2 - Item two - 2000
3 - Item three - 500 should come first because it is true but it is printing
1 - Item one - 1000
2 - Item two - 2000
3 - Item three - 500
What am I doing wrong?
This code can be run as it is on Dartpad here
A compareTo implementation should be reflexive, anti-symmetric, and transitive. Violating these properties can give sort results that are not self-consistent.
As written, your compareTo claims that two elements are always considered "equal" in sort order if this.isAvailable is false. But what about if other.isAvailable is true?
Your sort should work if you implement compareTo properly without trying to take shortcuts:
int compareTo(Item other) {
if (isAvailable == other.isAvailable) {
return 0;
} else if (isAvailable) {
return -1;
}
return 1;
}
Related
I want to sort a personArray with age and name:
final personArray = [
_Person(age: 10, name: 'Dean'),
_Person(age: 20, name: 'Jack'),
_Person(age: 30, name: 'Ben'),
_Person(age: 30, name: 'Alice'),
];
personArray.sort((p1, p2) {
return Comparable.compare(p1.age, p2.age);
});
for (final element in personArray) {
print(element.name);
}
Console print: Dean Jack Ben Alice.
But what I want is: Dean Jack Alice Ben.
The pseudocode looks like:
personArray.sort((p1, p2) {
return Comparable.compare(p1.age, p2.age) && Comparable.compare(p1.name, p2.name);
});
Anyway can do it?
Change your compare function
personArray.sort((p1, p2) {
final compare = Comparable.compare(p1.age, p2.age);
return compare == 0 ? Comparable.compare(p1.name, p2.name) : compare;
});
you can try this code
personArray.sort((a, b) {
return a.name.compareTo(b.name);
});
it will sort the objects with respect to name.
personArray.sort((a, b) {
if (a.age != b.age) {
return a.age - b.age;
} else {
return a.name.compareTo(b.name);
}
});
and this will sort it first with age and if both the age are equal then it will sort the objects with respect to name.
I was writing my question but had found the solution before posting it. There are many examples about how to sort a list in Dart by comparing two fields. However, I still found it wasn't straight forward, at least for me, to figure out the sorting by more than two fields. I thought it would be worth sharing it under a separate topic.
Here's how I am sorting lists in Dart by three or more fields:
class Student {
String name;
String course;
int age;
Student(this.name, this.course, this.age);
#override
String toString() {
return '{$name, $course, $age}';
}
}
main() {
List<Student> students = [];
students.add(Student('Katherin', 'Dart Potions', 21));
students.add(Student('Adam Sr', 'Dart Magic', 40));
students.add(Student('Adam Jr', 'Dart Magic', 15));
students.sort(
(a, b) {
final int sortByCourse = -a.course.compareTo(b.course); // the minus '-' for descending
if (sortByCourse == 0) {
final int sortByName = a.name.compareTo(b.name);
if (sortByName == 0) {
return a.age.compareTo(b.age);
}
return sortByName;
}
return sortByCourse;
},
);
print('Sort DESC by Course, then ASC by Name and then ASC by Age:\n ${students.toString()}');
}
How I can manipulate the existing state in Riverpod. 'm a beginner about Flutter and Riverpod. When I try add one to Order error pops up and says:
Error: A value of type 'int' can't be assigned to a variable of type
'List'.
final OrderPaperProvider = StateNotifierProvider<OrderPaper, List<Order>>((ref) {
return OrderPaper();
});
#immutable
class Order {
final String product_id;
final String product_name;
final int product_price;
final int product_count;
Order({required this.product_id, required this.product_name, required this.product_price, required this.product_count});
Order copyWith({product_id, product_name, product_price, product_count}) {
return Order(
product_id: product_id,
product_name: product_name,
product_price: product_price,
product_count: product_count,
);
}
}
class OrderPaper extends StateNotifier<List<Order>> {
OrderPaper() : super([]);
void addOrder(Order order) {
for (var x = 0; x < state.length; x++) {
if (state[x].product_id == order.product_id) {
addOneToExistingOrder(x, order);
return;
}
}
state = [...state, order];
}
void removeOrder(String product_id) {
state = [
for (final order in state)
if (order.product_id != order) order,
];
}
void addOneToExistingOrder(index, Order order) {
state = state[index].product_count + 1; // <--------- Error in this line
}
void clearOrderPaper() {
state = [];
}
}
What is happening in the code you posted is basically this:
You are telling it to update the state which is a List<Order> with an integer.
This is because state[index].product_count + 1 actually equals a number, for example 1+2 = 3.
So you are telling it, state = 3, and this causes your error.
What you need to do, is create a new state with the list of items and the edited item, like this (you don't need to pass the index, you can get it in the function):
void addOrder(Order order) {
//find if the product is in the list. The index will not be -1.
final index = state.indexWhere((entry) => entry.product_count == order. product_count);
if (index != -1) {
//if the item is in the list, we just edit the count
state = [
...state.sublist(0, index),
order.copyWith(product_count: state[index].product_count + 1),
...state.sublist(index + 1),
];
}else {
//otherwise, just add it as a new entry.
state = [...state, order],
}
}
I have a list of Product objects which have a property called productName. For example:
Product(
....
productName: "Red Shirt",
)
Product(
....
productName: "Yellow Scarf",
)
Product(
....
productName: "Trousers",
)
What I want:
When some one searches for Red Scarf, I need to show both red pants
and yellow scarf.
My current algorithm:
class Search {
/*
A hashmap is used to store the products.
Key: productName
Value: Product
*/
final Map<String, Product> _map = Map.fromIterable(
products,
key: (product) => product.productName,
);
List<Product> search(String search) {
List<String> searchWords = search.trim().toLowerCase().split(" ");
List<Product> result = [];
for (int i = 0; i < searchWords.length; i++) {
for (int j = 0; j < products.length; j++) {
if (products[j].productName!.toLowerCase().contains(searchWords[i])) {
result.add(products[j]);
}
}
}
return result.toSet().toList();
}
}
The issue with this algorithm is when I type in Red S, it shows trousers as well since it has S in it.
I need to solve this issue.
And what better algorithms can be used to reduce the time complexity?
N.B: The _map doesn't do anything. I just thought I might need it.
The most optimised way would be to use RegExp, the following code will consider that one of your words must start with one of "search word", for example with Red S it will search for any words starting with red or s:
class Search {
Set<Product> search(String search) {
final searchWords = search.trim().split(" ");
final searchWordsRegExp = searchWords.formatToWordsRegExp();
return products.where((product) {
return product.productName.contains(searchWordsRegExp);
}).toSet();
}
}
extension FormatToWordsRegExpExtension on Iterable<String> {
RegExp formatToWordsRegExp({bool caseSensitive = false}) {
final buffer = StringBuffer();
for (final word in this) {
buffer.write(r'^' + word + r'|\b' + word);
}
return RegExp(buffer.toString(), caseSensitive: caseSensitive);
}
}
Use case
void main() {
final search = Search();
final words = ['Red S', 'red s', 'red', 'yellow', 'tro'];
for (final w in words) {
print('Search for "$w"');
search.search(w).showData();
print('--------------------');
}
}
Output
Search for "Red S"
Red Shirt
Yellow Scarf
--------------------
Search for "red s"
Red Shirt
Yellow Scarf
--------------------
Search for "red"
Red Shirt
--------------------
Search for "yellow"
Yellow Scarf
--------------------
Search for "tro"
Trousers
--------------------
Try the example on DartPad
I want to add a list to my main List and remove duplicate, like this:
class item {
int id;
String title;
item({this.id, this.title});
}
void main() {
// this is working for List<int>
List<int> c = [1, 2, 3];
List<int> d = [3, 4, 5];
c.addAll(d..removeWhere((e) => c.contains(e)));
print(c);
// but this is not working for List<item>
List<item> a = new List<item>();
a.add(new item(id: 1, title: 'item1'));
a.add(new item(id: 2, title: 'item2'));
List<item> b = new List<item>();
b.add(new item(id: 2, title: 'item2'));
b.add(new item(id: 3, title: 'item3'));
a.addAll(b..removeWhere((e) => a.contains(e)));
a.forEach((f) => print('${f.id} ${f.title}'));
}
and output is like this:
[1, 2, 3, 4, 5]
1 item1
2 item2
2 item2
3 item3
As you test this code on https://dartpad.dev/ output is ok for List<int> but there is duplicate in output for List<item>.
The first list have integer values and when you call contains it will check the values and will work correctly.
In second case you have item objects. Both lists have objects that may have same property values but both are two different object. For example, the below code will work correctly in your case, because the item2 object is same in both lists.
Item item2 = Item(id: 2, title: 'item2');
List<Item> a = new List<Item>();
a.add(new Item(id: 1, title: 'item1'));
a.add(item2);
List<Item> b = new List<Item>();
b.add(item2);
b.add(new Item(id: 3, title: 'item3'));
When you call contains it will use the Object.== method, so to handle this issue you have to override that method and specify your own equality logic.
class Item {
int id;
String title;
Item({this.id, this.title});
#override
bool operator == (Object other) {
return
identical(this, other) ||
other is Item &&
runtimeType == other.runtimeType &&
id == other.id;
}
}
Or you can use the equatable package to handle it better.
References:
contains method
operator == method
I think you need to iterate on your list a if you want to compare a property (e.g title)
a.addAll(
b
..removeWhere((e) {
bool flag = false;
a.forEach((x) {
if (x.title.contains(e.title)) {
flag = true;
}
});
return flag;
}),
);
As suggested below, those two list items are different
Thats because these 2 item's:
a.add(new item(id: 2, title: 'item2'));
b.add(new item(id: 2, title: 'item2'));
are different. They are 2 instances of 2 different objects that just have the same values for id and title. As the documentation tells:
The default behavior for all Objects is to return true if and only if this and other are the same object.
If you want to compare if the 2 fields are the same you can override the equality operator inside your item class:
operator ==(item other) {
return (this.id == other.id && this.title == other.title);
}
which gives the expected output:
[1, 2, 3, 4, 5]
1 item1
2 item2
3 item3