"map.getOrDefault" equivalent in Dart - flutter

I'm working on a friend suggestion algorithm for a flutter social media application. I'm still an amateur when it comes to Dart so I'm stuck on the following line of code:
class FriendSuggestionAlgorithm {
User friendSuggestion(User user) {
int max = -1;
User suggestion;
Map<User, int> map = new HashMap();
for (User friend in user.friends) {
for (User mutualFriend in friend.friends) {
if (mutualFriend.id != user.id && !user.friends.contains(mutualFriend)) {
map.putIfAbsent(mutualFriend, map.getOrDefault(mutual, 0) + 1);
}
}
}
for (MapEntry<User, int> mutualFriend in map.entries) {
if (mutualFriend.value > max) {
max = mutualFriend.value;
suggestion = mutualFriend.key;
}
}
return suggestion;
}
}
map.getOrDefault is underlined (I know the method doesn't exist in Dart). Do you know what the equivalent is in Dart? (PS, I'm just translating Java code into Dart.
Any help is appreciated!

Your code doesn't make sense. map.putIfAbsent will do work only if the key doesn't exist, so the hypothetical map.getOrDefault call with the same key would always return the default value anyway. That is, your logic would be the equivalent of map.putIfAbsent(mutual, () => 1), where nothing happens if the key already exists.
Map.putifAbsent takes a callback as its argument to avoid evaluating it unless it's actually necessary. I personally prefer using ??= when the Map values are non-nullable.
I presume that you actually want to increment the existing value, if one exists. If so, I'd replace the map.putIfAbsent(...) call with:
map[mutual] = (map[mutual] ?? 0) + 1;
Also see: Does Dart have something like defaultdict in Python?

You could do it like this:
map.putIfAbsent(mutual, (map.containsKey(mutual) ? map[mutual] : 0) + 1)
Maybe take a look at this for more info: https://dart.dev/guides/language/language-tour#conditional-expressions
Edit:
This code should work
class FriendSuggestionAlgorithm {
User? friendSuggestion(User user) {
int max = -1;
User? suggestion;
Map<User, int> map = {};
for (User friend in user.friends) {
for (User mutualFriend in friend.friends) {
if (mutualFriend.id != user.id && !user.friends.contains(mutualFriend)) {
map.putIfAbsent(mutualFriend, () => (map[mutualFriend] ?? 0) + 1);
}
}
}
for (MapEntry<User, int> mutualFriend in map.entries) {
if (mutualFriend.value > max) {
max = mutualFriend.value;
suggestion = mutualFriend.key;
}
}
return suggestion;
}
}
Note that suggestion nullable because it could happen that suggestion is never assigned. And therefore friendSuggestion(user) can return null;
To come back to your question
the correct code is
map.putIfAbsent(mutualFriend, () => (map[mutualFriend] ?? 0) + 1);
My mistake on my original answer, the ifAbsent part of this is a function. In the function the value of mutualFriend is retrieved. If that is null use 0.

Related

Break on ForEach() dart

I'm writing a function where I iterate thru a map and look test for valid values on an function. My question is there is a better way to correctly break from a foreach loop when an error is found?
('break' does not work on foreach())
Since I am not able to use the break function here so i had to place a bool marker :/
Any help in making this code nice looking would be appreciated :)
Future<bool> saveToKeychainFunc(Map data) {
bool saved = false;
bool error = false;
data?.keys?.forEach((item) async {
if (data[item] != null) {
await _storage.write(key: item.toString(), value: data[item].toString());
} else {
//TODO
// data error, we got null for a value!
error = true;
}
saved = true;
});
return (error == true) ? false : saved;
}
I've experienced this. My work around is using for loop instead.
ex:
for (int i = 0; i < jsonTransactions.length; i++) {
Transaction transaction = Transaction.fromJson(jsonTransactions[i]);
transactions.add(transaction);
}
You can just add conditions, and add your break inside the loop if conditions are met. Since you're using a map, my code should not be very different since Map also has .length

From RxJava2, How can I compare and filter two observables if the values are equal?

I am new to RxJava2.
I am trying to get a list of Transaction object both from cache and from server.
I want to compare the server value to cache value and if the server value is the same, then ignore it.
I was able to do it easily using .scan() because we can return null and when null is returned from the .scan() the value got ignored(filtered).
RxJava 1
private Observable<List<Transaction>> getTransactionsFromCacheAndServer() {
return Observable.concat(
getTransactionsFromCache(),
getTransactionsFromServer()
)
.scan((p1, p2) -> {
if (p1 == null && p2 != null) {
return p2;
} else if (p1 != null && !isListSame(p1, p2)) {
return p2;
} else {
return null;
}
});
}
With RxJava 2, since I cannot return null anymore, things are not easy.
RxJava 2
private Observable<List<Transaction>> getTransactionsFromCacheAndServer() {
return Observable.concat(
getTransactionsFromCache(),
getTransactionsFromServer()
)
.map(FilterObject::new)
.scan((filterObject1, filterObject2) -> {
List<Transaction> p1 = (List<Transaction>)filterObject1.value;
List<Transaction> p2 = (List<Transaction>)filterObject2.value;
if (p1.size() == 0 && p2.size() > 0) {
return filterObject2;
} else if (!isListSame(p1, p2)) {
return filterObject2;
} else {
filterObject2.filter = true;
return filterObject2;
}
})
.filter(filterObject -> !filterObject.filter)
.map(filterObject -> (List<Transaction>)filterObject.value);
}
Where FilterObject is:
public class FilterObject {
public Object value;
public boolean filter;
public FilterObject(Object value) {
this.value = value;
}
}
Even though I can achieve the same thing using above method, it seems very ugly. Also I had to include two maps which might not be so performance friendly.
Is there a simple/clean way to achieve what I want?
I don't think there is a generic solution to this problem, since an empty list and a list that needs to be filtered (which happens to be empty in all cases) are two different things (the output of the scan) and needs to be handled differently.
However, in your particular case you never emit an empty list, except maybe for the first output.
(I am using String instead Transaction, shouldn't matter)
private Observable<List<String>> getTransactionsFromCacheAndServer() {
return Observable.concat(
getTransactionsFromCache(),
getTransactionsFromServer()
)
.filter(list -> !list.isEmpty())
// If you prefer a consistent empty list over the first
// empty list emission getting filtered
.startWith((List<String>) Collections.EMPTY_LIST)
// Newly emitted value cannot be empty, it only depends only on the comparison
.distinctUntilChanged(this::isListSame);
}
That's the closest I could get with as few operators as possible. Hope it solves your problem.
Based on andras' answer, I modified little bit to achieve what I want.
private Observable<List<String>> getTransactionsFromCacheAndServer() {
return Observable.concat(
getTransactionsFromCache(),
getTransactionsFromServer()
)
.filter(list -> !list.isEmpty())
.distinctUntilChanged(this::isListSame)
.switchIfEmpty(Observable.just(new ArrayList<>()));
}
Andreas' answer will always receive an empty list and then a real data.
My solution above will receive:
1. Data from cache (and then data from server if different)
2. Empty list if both cache and server returns Empty list.

Search data in Observable

I'd like to have a search input, that display the result on keypress.
At the moment, this is what I have :
mylist: Observable<MyData[]>;
term = new FormControl();
ngOnInit() {
this.mylist = this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.searchData(term));
}
searchData(valueToSearch:string){
if(valueToSearch == ''){
this.channels = MyData.find();
}
return MyData.find({'title':new RegExp(valueToSearch)});
}
It works quite well, but I have trouble to initialize "mylist", and I think my method isn't performant at all.
Basically, I want when my component is initialize, that:
this.mylist = MyData.find();
And on keypress, I want my search to be done on this.mylist, to avoid doing too much request.
Is it possible ?
I hope I'm clear.
Thanks by advance guys.
You must subscribe to the mapped data. Modify to the below code
this.term.valueChanges
.debounceTime(400)
.distinctUntilChanged()
.switchMap(term => this.searchData(term))
.subscribe((result) => {
this.mylist = result
});;
#Julia is correct , modify your searchData() function as below
searchData(valueToSearch:string):Observable<any> {
if(valueToSearch == ''){
this.channels = MyData.find();
}
return <Observable<any>>MyData.find({'title':new RegExp(valueToSearch)});
}

How can I get an alert if a specific url substring is present and nothing if null

function getCode() {
if (window.location.href.indexOf("?discount=")) {
var url = (document.URL);
var id = url.substring(url.lastIndexOf('=') + 1);
window.alert(id);
return true;
} else {
return false;
}
}
Purpose: When people go to our "Service Request" page using a QR code that has a substring of ?discount=1234. I have been testing by creating an alert box with the discount code showing. Eventually I want to be able to populate that "1234" automatically into a "Discount Code:" text field on page load.
The above is a mixture of a few suggestions when I researched it.
Result: Going to example.com/serviceRequest.html?discount=1234 gives me the appropriate alert "1234", as I want... Going to example.com/serviceRequest.html gives me the alert http://example.com/serviceRequest.html, but I don't want anything to happen if "?discount=" is null.
Any suggestions?
indexOf returns -1 if the search pattern doesn't exist. In JavaScript, anything not a 0 or false or undefined is considered true.
So your line of:
if(window.location.href.indexOf("?discount=")) {
Would better search as:
if(window.location.href.indexOf("?discount=") > -1) {
Try changing your if-statement to:
if(window.location.href.indexOf("?discount=") != -1)
Look up the documentation for ".indexOf". It returns -1 for not found and >= 0 if it is found.
...indexOf("?discount=") >= 0
substring and indexOf return -1 if the text is not found, so you can test for this. E.g.
function getCode() {
if(window.location.href.indexOf("?discount=") != -1) {
var url = (document.URL);
var id = url.substring(url.lastIndexOf('=') + 1);
window.alert(id);
return true;
}
else {
return false;
}
}
You just need to test the indexOf value:
function getCode() {
if (window.location.href.indexOf("?discount=") !== -1) {
var url = (document.URL);
var id = url.substring(url.lastIndexOf('=') + 1);
window.alert(id);
return true;
}
else {
return false;
}
}
So the quick and dirty answer would be
var discount = window.location.search.split("?discount=")[1];
alert(discount);
But this doesn't take into account the occurence of other query string parameters.
You'll really want to parse all the query parameters into a hash map.
This article does a good job of showing you a native and jQuery version.
http://jquery-howto.blogspot.com/2009/09/get-url-parameters-values-with-jquery.html

tinymce.dom.replace throws an exception concerning parentNode

I'm writing a tinyMce plugin which contains a section of code, replacing one element for another. I'm using the editor's dom instance to create the node I want to insert, and I'm using the same instance to do the replacement.
My code is as follows:
var nodeData =
{
"data-widgetId": data.widget.widgetKey(),
"data-instanceKey": "instance1",
src: "/content/images/icon48/cog.png",
class: "widgetPlaceholder",
title: data.widget.getInfo().name
};
var nodeToInsert = ed.dom.create("img", nodeData);
// Insert this content into the editor window
if (data.mode == 'add') {
tinymce.DOM.add(ed.getBody(), nodeToInsert);
}
else if (data.mode == 'edit' && data.selected != null) {
var instanceKey = $(data.selected).attr("data-instancekey");
var elementToReplace = tinymce.DOM.select("[data-instancekey=" + instanceKey + "]");
if (elementToReplace.length === 1) {
ed.dom.replace(elementToReplace[0], nodeToInsert);
}
else {
throw new "No element to replace with that instance key";
}
}
TinyMCE breaks during the replace, here:
replace : function(n, o, k) {
var t = this;
if (is(o, 'array'))
n = n.cloneNode(true);
return t.run(o, function(o) {
if (k) {
each(tinymce.grep(o.childNodes), function(c) {
n.appendChild(c);
});
}
return o.parentNode.replaceChild(n, o);
});
},
..with the error Cannot call method 'replaceChild' of null.
I've verified that the two argument's being passed into replace() are not null and that their parentNode fields are instantiated. I've also taken care to make sure that the elements are being created and replace using the same document instance (I understand I.E has an issue with this).
I've done all this development in Google Chrome, but I receive the same errors in Firefox 4 and IE8 also. Has anyone else come across this?
Thanks in advance
As it turns out, I was simply passing in the arguments in the wrong order. I should have been passing the node I wanted to insert first, and the node I wanted to replace second.