A sets receipt is a set of sets:
Set dish1 = {'a', 'b'};
Set dish2 = {'c', 'd'};
Set dish3 = {'a', 'b', 'c'};
Set dish4 = {'a', 'b', 'c', 'd'};
Set receipt = {dish1, dish2, dish3, dish4};
I would like to find the logic that can test whether a material belongs to a certain dish of above receipt.
Set material1 = {'a', 'b'}; // return dish1
Set material2 = {'a', 'b', 'c'}; // dish3
Set material3 = {'a', 'b', 'd'}; // null
Set material4 = {'b', 'c', 'a'}; // dish3
material3 {'a', 'b', 'd'} returns null not the dish1 because I'm looking for an exact element match but the order doesn't matter so material4 {'b', 'c', 'a'} can return dish3.
Thank you.
To be able to have dish1, dish2, etc. available for your result, they actually have to be in your program. You can do this with a Map.
void main() {
var receipt = {
'dish1': {'a', 'b'},
'dish2': {'c', 'd'},
'dish3': {'a', 'b', 'c'},
'dish4': {'a', 'b', 'c', 'd'},
};
var availableIngredients = {'b', 'c', 'a'};
var dishesWithAvailableIngredients = receipt
.entries
.where(
(entry) => entry.value.containsAll(availableIngredients)
)
.map((entry) => entry.key);
print(dishesWithAvailableIngredients);
}
Of course, it might be nice to have some more structured data model in place:
void main() {
var receipt = new Receipt([
new Dish(name: 'dish1', ingredients: {'a', 'b'}),
new Dish(name: 'dish2', ingredients: {'c', 'd'}),
new Dish(name: 'dish3', ingredients: {'a', 'b', 'c'}),
new Dish(name: 'dish4', ingredients: {'a', 'b', 'c', 'd'}),
]);
print(receipt.dishesWithIngredients({'b', 'c', 'a'}));
}
class Dish {
// Might also want to track quantity, but I'm leaving that out
String name;
Set<String> ingredients;
Dish({String name, Set<String> ingredients}) {
this.name = name;
this.ingredients = ingredients;
}
bool containsAll(Set<String> ingredients) {
return this.ingredients.containsAll(ingredients);
}
String toString() {
return "Dish(name: ${name}, ingredients: ${ingredients})";
}
}
class Receipt {
List<Dish> dishes;
Receipt(List<Dish> dishes) {
this.dishes = dishes;
}
Iterable<Dish> dishesWithIngredients(Set<String> ingredients) {
return dishes.where((d) => d.containsAll(ingredients));
}
String toString() {
return "Receipt(dishes: ${dishes})";
}
}
You need a way to compare sets for equality, in order to see whether one set is equal to another.
You can use package:collection's SetEquality for that.
That would make the code to look up an set in set of sets:
Set? findDish(Set<Set> receipt, Set dish) {
for (var receiptDish in receipt) {
if (const SetEquality().equals(receiptDish, dish)) {
return receiptDish;
}
}
return null;
}
That's not particularly efficient. Instead you could use a custom hash map to store the receipts, and allow direct lookup by a set:
const setEquality = SetEquality();
Set<Set> receipt = HashSet<Set>(
equals: setEquality.equals, hashCode: setEquality.hash)
..addAll([dish1, dish2, dish3, dish4]);
Then you can directly look up a set in the other set:
Set? material1 = receipt.lookup({'a', 'b'}); // return dish1
Set? material2 = receipt.lookup({'a', 'b', 'c'}); // dish3
Set? material3 = receipt.lookup({'a', 'b', 'd'}); // null
Set? material4 = receipt.lookup({'b', 'c', 'a'}); // dish3
Related
Using MySQL.
I have the following entity:
import { Column, Entity, ValueTransformer } from 'typeorm';
const ormIdTransformer: ValueTransformer = {
to: (value: any) => (value !== undefined && value !== null ? Number(value) : value),
from: (value: any) => (value !== undefined && value !== null ? String(value) : undefined),
};
#Entity()
export class User {
#Column({ type: 'int', transform: ormIdTransformer, primary: true })
id: string;
#Column({ type: 'int', transform: ormIdTransformer })
profileId: string;
// ...
}
Please note that profileId is not mapped as a relation here.
In my app, I have a custom UserRepository, and it has access to an entityManager (both internally, and externally, in case the operation is running inside a transaction).
I'm trying to search for all users that have specific IDs and a specific profile (also by ID):
// Just an example
const profileId = '1';
const userIds = ['1', '2', '3', /* ... */];
const ids = await entityManager.find(User, { where: { profileId, id: In(userIds) }, select: ['id'] });
I'd expect TypeORM to generate a query similar to the following:
SELECT `User`.`id` from `User` `User` WHERE `User`.`profileId` = ? AND `User`.`id` IN ?`
-- Parameters: [1, [1, 2, 3, ...]]
Instead, TypeORM seems to completely ignore the FindOperator (the In) I provided, and just passes In(userIds) through as if it was a normal value. This FindOperator goes directly to my ValueTransformer, which I think is not correct.
But that's not the worse of it. By activating query logging, I see that TypeORM generates the following query:
SELECT `User`.`id` AS `User_id` FROM `User` `User` WHERE `User`.`profileId` = ? AND `User`.`id` = ?
-- Parameters: [2, NaN]
The operation obviously fails with a database error: QueryFailedError: ER_BAD_FIELD_ERROR: Unknown column 'NaN' in 'where clause'.
Notice how the condition for the id column is not being applied correctly: it should be IN, not =.
Am I doing something wrong?
After some debugging, I found out that the problem was related to the custom transformer I was implementing for my IDs.
QueryBuilder has special logic to take care of FindOperator, but that only works if ColumnMetadata.getEntityValue (which is called in QueryBuilder) actually returns an operator. But ColumnMetadata.getEntityValue lacks the logic to deal with FindOperator; instead of applying the transformation to the operator's wrapped value, it applies the transformation to the operator itself.
With that knowledge, I looked up for problems related to FindOperator and custom transformations, and found an answer to a similar question. That helped me implement a transformation process that actually works with FindOperators.
Long story short, turns out I had to reconstruct the entire operator, and transform the values wrapped in it myself. This is what I came up with:
import { FindOperator, FindOperatorType, ValueTransformer } from 'typeorm';
const idFromDb = (value: any) => {
if (value === undefined || value === null) {
return undefined;
}
return String(value);
};
const idToDb = (value: any) => {
if (value === undefined || value === null) {
return undefined;
}
if (Array.isArray(value)) {
return value.map(idToDb);
}
if (value instanceof FindOperator) {
return new FindOperator(
value.type as FindOperatorType,
idToDb(value.value),
value.useParameter,
value.multipleParameters,
);
}
return Number(value);
};
export const ormIdTransformer: ValueTransformer = {
to: idToDb,
from: idFromDb,
};
I have a dart list:
List<String?> vals;
I want to remove any null values and convert it to a List<String>.
I've tried:
List<String> removeNulls(List<String?> list) {
return list.where((c) => c != null).toList() as List<String>;
}
At run time I'm getting the following error:
List<String?>' is not a subtype of type 'List<String>?'
What is the correct way to resolve this?
Ideally you'd start with a List<String> in the first place. If you're building your list like:
String? s = maybeNullString();
var list = <String?>[
'foo',
'bar',
someCondition ? 'baz' : null,
s,
];
then you instead can use collection-if to avoid inserting null elements:
String? s = maybeNullString();
var list = <String?>[
'foo',
'bar',
if (someCondition) 'baz',
if (s != null) s,
];
An easy way to filter out null values from an Iterable<T?> and get an Iterable<T> result is to use .whereType<T>(). For example:
var list = <String?>['foo', 'bar', null, 'baz', null];
var withoutNulls = list.whereType<String>().toList();
Another approach is to use collection-for with collection-if:
var list = <String?>['foo', 'bar', null, 'baz', null];
var withoutNulls = <String>[
for (var s in list)
if (s != null) s
];
Finally, if you already know that your List doesn't contain any null elements but just need to cast the elements to a non-nullable type, other options are to use List.from:
var list = <String?>['foo', 'bar', 'baz'];
var withoutNulls = List<String>.from(list);
or if you don't want to create a new List, Iterable.cast:
var list = <String?>['foo', 'bar', 'baz'];
var withoutNulls = list.cast<String>();
Without creating a new List
void main() {
List<String?> list = ['a', null, 'b', 'c', null];
list.removeWhere((e) => e == null); // <-- This is all you need.
print(list); // [a, b, c]
}
Creating a new List
First create a method, filter for example:
List<String> filter(List<String?> input) {
input.removeWhere((e) => e == null);
return List<String>.from(input);
}
You can now use it:
void main() {
List<String?> list = ['a', null, 'b', 'c', null];
List<String> filteredList = filter(list); // New list
print(filteredList); // [a, b, c]
}
To use retainWhere, replace the predicate in removeWhere with (e) => e != null
As in Dart you can combine several list items into one according to the following condition:
Given List<String> arr = ['2','3','b','*','4','5','6','-','3','4'];
Get arr = ['23','b','*','456','-','34'];
The list is unknown in advance. It can be of any length and with any sequence of characters. And need to combine only the lines that have numbers in them.
I would be grateful for any suggestions.
You are not describing what should happen if there are multiple special characters or letters. So I have made my example so it will only combine numbers:
void main() {
final arr = ['2', '3', 'b', '*', '4', '5', '6', '-', '3', '4'];
print(combine(arr)); // [23, b, *, 456, -, 34]
}
List<String> combine(List<String> input) {
final output = <String>[];
final buffer = StringBuffer();
for (final string in input) {
if (int.tryParse(string) == null) {
if (buffer.isNotEmpty) {
output.add(buffer.toString());
buffer.clear();
}
output.add(string);
} else {
buffer.write(string);
}
}
if (buffer.isNotEmpty) {
output.add(buffer.toString());
}
return output;
}
You can use ''.join() here:
arr = [''.join(arr[0:2]), arr[2], arr[3], ''.join(arr[4:7]), arr[7], ''.join(arr[8:10])]
If you only want to have a condition where you only join numerical values then you can add a for loop beforehand.
So, I have a list of lists, like this
var a = [['a', 'b', 'c'], ['1', '2', 'b'], [3, 'd', true]];
I want to output a flattened list with duplicate values removed -
var b = ['a', 'b', 'c', '1', '2', 3, 'd', true];
While, I know I can use map/reduce, just trying to understand what is the most dart like way of doing this.
More info about input - I'm just trying to understand iterables and sets better in Dart (coming from Python world). Dart Trickeristry welcome!
You want to flatten the list of lists and remove duplicates.
The way to remove duplicates is to use a Set with an efficient lookup (or something equivalent, but Set is the simplest de-duplicating functionality).
Any other approach is likely going to be quadratic because it checks each element against each other element.
For that, I'd do:
var result = [...{for (var list in lists) ...list}];
This flattens the lists into a set, then converts that set to a list again.
The one other alternative would be flattening into a list, sorting the list, and then removing adjacent duplicates. That requires the elements to be Comparable, which the example given here isn't, or that you provide a comparison function. Let's assume the latter:
List<T> flatUnique(Iterable<Iterable<T>> elements, int compare(T a, T b)) {
var flat = [for (var element in elements) ...element];
if (flat.isEmpty) return flat;
flat.sort(compare);
var current = flat[0];
var j = 1;
for (var i = 1; i < flat.length; i++) {
var next = flat[i];
if (current != next) {
current = flat[j++] = next;
}
}
flat.length = j;
return flat;
}
That's more complicated because there is not a general "remove duplicates from sorted list" operation in the libraries ... because people just use a set.
It also changes the order of elements other than by removing duplicates.
in dart 2.3,
you can actually use spread operator like this:
var a = [['a', 'b', 'c'], ['1', '2', 'b'], [3, 'd', true]];
var b = [...a[0], ...a[1], ...a[2]];
// print ['a', 'b', 'c', '1', '2', 3, 'd', true];
and do your code afterwards
You can combine a Set with the expand method.
var a = [['a', 'b', 'c'], ['1', '2', 'b'], [3, 'd', true]];
var b = Set.from(a.expand((x) => x)).toList();
// [a, b, c, 1, 2, 3, d, true]
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) {
...
}