Boolean statement for multiple filtering Swift - ios

I'm currently creating an app in Swift 4.2 which I'd like a filtering feature in place which allows the user to select multiple filters.
I have an array of the currently selected filters, for example ["Low", "Unread"]. I also have an array of the objects being filtered. But I'm struggling to figure out how to apply multiple filters to this array, especially due to the objects have children which in turn have properties which are filtered against. For example object array holds bulletin.importance.name which is the property that "Low" would be checked against.
The following code is a boolean returning function which will get the filters to be used on the array of bulletin objects:
return (bulletin.bulletinVersion?.issued == true) && (scopes.contains("All") || (scopes.contains((bulletin.bulletinVersion?.bulletin?.importance?.name)!) ||
(!scopes.contains(where: {$0 == "Low" || $0 == "Normal" || $0 == "High"}))) && (scopes.contains(bulletin.read(i: bulletin.firstReadDate)) ||
(!scopes.contains(where: {$0 == "Unread"}))) &&
(scopes.contains(bulletin.signed(i: bulletin.signDate)) && bulletin.bulletinVersion?.bulletin?.requiresSignature == true) && scopes.contains(bulletin.favourited(i: bulletin.favourite)))
This is my current attempt of the boolean check. I wish for it to be hard set so if the user selects "High" and "Unread" it will only show objects which match both of those filters.
The function is called here, getting the filters and filtering an array of ALL bulletins into which ones should be shown based upon the filters:
currentBulletinArray = bulletinArray.filter({bulletin -> Bool in
let doesCategoryMatch = getScopeFilters(issued: true, scopes: scope, bulletin: bulletin, signature: true)
if searchBarIsEmpty(){
return doesCategoryMatch
} else {
return doesCategoryMatch && (bulletin.bulletinVersion?.bulletin?.name?.lowercased().contains(searchText.lowercased()))!
}
})
I want to be able to set any combination of filters, where the returning .filter predicate will show only the bulletins which match ALL the filters.
So, Unread + Unsigned + High will show all High Importance bulletins where they are unread and unsigned.

Instead of trying to shove a huge amount of boolean combinations into one statement, I decided to place each filter into a dictionary:
public var filters:[String: Any] = ["importance":[],
"unread": false,
"unsigned": false,
"favourite": false]
The importance filter will hold an array of strings, for example ["Low", "Medium", "High"].
When the filter buttons are clicked the filters will be toggled if they are a bool or appended/removed from the array:
if(!importanceToAdd.isEmpty){
filters["importance"] = importanceToAdd
}
if(cell.filterTitleLabel.text == "Unread")
{
filters["unread"] = true
}
if(cell.filterTitleLabel.text == "Unsigned")
{
filters["unsigned"] = true
}
if(cell.filterTitleLabel.text == "Favourites")
{
filters["favourite"] = true
}
Then, in a seperate function, I check to see if the filters are set, independently of one another. If so, filter the array fo bulletins by each of these conditions:
if let importance = filters["importance"] as! [String]?{
if(importance.count != 0){
filteredBulletins = filteredBulletins.filter({importance.contains(($0.bulletinVersion?.bulletin?.importance?.name)!)})
}
}
if let unread = filters["unread"] as! Bool?
{
if(unread)
{
filteredBulletins = filteredBulletins.filter({$0.firstReadDate == nil})
}
}
if let unsigned = filters["unsigned"] as! Bool?
{
if(unsigned)
{
filteredBulletins = filteredBulletins.filter({$0.bulletinVersion?.bulletin?.requiresSignature == true && $0.signDate == nil})
}
}
if let favourite = filters["favourite"] as! Bool?
{
if(favourite)
{
filteredBulletins = filteredBulletins.filter({$0.favourite == true})
}
}
The inclusion and removal of filters to a dictionary really made my needs more clear. Trying to create a monster of a boolean statement would have been infinitely difficult to be dynamic enough to match each possible combination of filters (I'm not even sure it would have been possible).
But thank you to all who commented to offer alternative solutions, you really helped me think outside of the box! :)

I am not sure what you are asking, but here goes. First you could format your code to be more readable.... I know multiple return points were considered bad, but the tide seems to be turning a little on that. I would pick out the cases in order of importance and return any definite booleans you can find. For example (untested code as I do not know what Bulletin and Scopes are):
func foo(bulletin: Bulletin, scopes: Scopes) -> Bool {
if bulletin.bulletinVersion?.issued == true && scopes.contains("All") {
return true
}
if scopes.contains(bulletin.bulletinVersion?.bulletin?.importance?.name)! {
return true
}
if scopes.contains(bulletin.bulletinVersion?.bulletin?.importance?.name)! {
return true
}
if !scopes.contains(where: {$0 == "Low" || $0 == "Normal" || $0 == "High"})
&& (scopes.contains(bulletin.read(i: bulletin.firstReadDate) {
return true
}
if !scopes.contains(where: {$0 == "Unread"})
&& scopes.contains(bulletin.signed(i: bulletin.signDate)
&& bulletin.bulletinVersion?.bulletin?.requiresSignature == true
&& scopes.contains(bulletin.favourited(i: bulletin.favourite)) {
return true
}
return false
}
Note that each of these clauses tests for true and returns it if appropriate, so the function returns as soon as a true case is found. The order of the tsts might have an effect on the logic.
I would consider creating a protocol with a method that took a bulletin, scope and returned true or false.
protocol Filter {
func test(bulletin: Bulletin, scopes: Scopes) -> Bool
}
Then creating implementors of this protocol:
class AFilter: Filter {
func test(bulletin: Bulletin, scopes: Scopes) -> Bool {
if bulletin.bulletinVersion?.issued == true && scopes.contains("All") {
return true
}
return false
}
}
Once you have built a list of filter instances you could do (modified to match clarified requirements):
for filter in filters {
if !filter.test(bulletin: bulletin, scopes: scopes) {
return false
}
}
return true
(Bonus: note how easy it was to change the logic with this design pattern)

Related

adding objects to list if it doesn't contain it

I'm trying to add object to a list only if it wasn't added already .. like this:
for (var interest in poll.poll.interests) {
InterestDrop n = new InterestDrop(interest, false);
print(interest);
print(dropInterestsList);
print(dropInterestsList.contains(n));
if (!(dropInterestsList.contains(n))){
dropInterestsList.add(n);
}
}
but this always return false and add the object even when its already there ... how to solve this?
ANSWER:
class InterestDrop {
String name;
bool isClicked;
InterestDrop(this.name, this.isClicked);
bool operator == (o) => o is InterestDrop && name == o.name && isClicked == o.isClicked;
int get hashCode => hash2(name.hashCode, isClicked.hashCode);
}
You need to override the equality operator in your custom class.
From the docs:
The default behavior for all Objects is to return true if and only if
this and other are the same object.
So your contains method will only return true if your array contains the exact object you are comparing against.
You need to do something like this:
class InterestDrop {
operator ==(InterestDrop other) => identifier == other.identifier;
}

breeze query where clause is not executed

I'm quite new to using breeze and at the moment stuck with something which seems very simple.
I have a API call which returns 4 locations. Then using breeze, I'm trying to filter it down using a where clause as follows:
function getLocations(clientId) {
var self = this;
return EntityQuery.from('GetLocations')
.withParameters({ clientId: clientId })
.where("activeStatus", "==", "0")
.expand('LocationType')
.using(self.manager)
.execute()
.then(querySucceeded, this._queryFailed);
function querySucceeded(data) {
if (data.results.length > 1) {
locations = data.results;
}
return locations;
}
}
Ideally, this should give me 0 rows, because in all 4 rows the 'activeStatus' is 1. However, it still shows me all 4 results. I tried with another filter for locationType, and it's the same result. The breeze side where clause does not get executed.
Update to answer the questions:
Following is how the API call in my controller looks like:
public object GetLocations(int clientId) {
}
As you see it only accepts the clientId as a parameter hence I use the with parameter clause. I was thinking that breeze will take care of the activeStatus where clause and I don't have to do the filter on that in the back-end. Is that wrong?
Can someone help with this?
The Breeze documentation indicates that the withParameters is usually used with non-.NET backends or servers which do not recognize oData URIs. Is it possible that the where clause is being ignored because of .withParameters? Can't you rewrite the where clause using the clientID filter?
function getLocations(clientId) {
var self = this;
var p1 = new breeze.Predicate("activeStatus","==","0");
var p2 = new breeze.Predicate("clientId","==",clientId);
var p = p1.and(p2)
return EntityQuery.from('GetLocations')
.where(p)
.expand('LocationType')
.using(self.manager)
.execute()
.then(querySucceeded, this._queryFailed);
function querySucceeded(data) {
if (data.results.length > 1) {
locations = data.results;
}
return locations;
}
}
I'd try this first. Or put the where clause in the withParameters statement, depending on your backend. If that doesn't work, then there might be other options.
Good Luck.
EDIT: An example that I use:
This is the API endpoint that I query against:
// GET: breeze/RST_ClientHistory/SeasonClients
[HttpGet]
[BreezeQueryable(MaxExpansionDepth = 10)]
public IQueryable<SeasonClient> SeasonClients()
{
return _contextProvider.Context.SeasonClients;
}
And here is an example of a query I use:
// qFilters is object. Values are arrays or strings, keys are id fields. SeasonClients might also be Clients
// Setup predicates
var p, p1;
// link up the predicates for passed data
for (var f in qFilters) {
var compareOp = Object.prototype.toString.call(qFilters[f]) === '[object Array]' ? 'in' : '==';
if (!qFilters[f] || (compareOp == 'in' && qFilters[f].length == 0)) continue;
fLC = f.toLowerCase();
if (fLC == "countryid") {
p1 = breeze.Predicate("District.Region.CountryId", compareOp, qFilters[f]);
} else if (fLC == "seasonid") {
p1 = breeze.Predicate("SeasonId", compareOp, qFilters[f]);
} else if (fLC == "districtid") {
p1 = breeze.Predicate("DistrictId", compareOp, qFilters[f]);
} else if (fLC == "siteid") {
p1 = breeze.Predicate("Group.Site.SiteId", compareOp, qFilters[f]);
} else if (fLC == "groupid") {
p1 = breeze.Predicate("GroupId", compareOp, qFilters[f]);
} else if (fLC == "clientid" || fLC == 'seasonclientid') {
p1 = breeze.Predicate("ClientId", compareOp, qFilters[f]);
}
// Setup predicates
if (p1) {
p = p ? p.and(p1) : p1;
}
}
// Requires [BreezeQueryable(MaxExpansionDepth = 10)] in controller
var qry = breeze.EntityQuery
.from("SeasonClients")
.expand("Client,Group.Site,Season,VSeasonClientCredit,District.Region.Country,Repayments.RepaymentType")
.orderBy("DistrictId,SeasonId,GroupId,ClientId");
// Add predicates to query, if any exist
if (p) qry = qry.where(p);
return qry;
That's longer than it needs to be, but I wanted to make sure a full working example is in here. You will notice that there is no reason to use .withParameters. As long as the Context is set up properly on the server, chaining predicates (where clauses) should work fine. In this case, we are creating where clauses with up to 10 ANDs filtering with strict equality or IN a collection, depending on what is passed in the qFilters Object.
I think you should probably get rid of the parameter in your backend controller, make the method parameterless, and include the clientId match as an additional predicate in your query.
This approach also makes your API endpoint much more flexible -- you can use it for a wide variety of queries, even if the ClientId has nothing to do with them.
Does this help? Let me know if you have any more questions?

How to cache aggregate column values on Doctrine_Record instance?

Lets say i have a record class that often gets queried with dyanmic colums that are MySQL aggregate values:
$results = Doctrine_Core::getTable('MyRecord')->creatQuery('m')
->select('m.*, AVG(m.rating) as avg_rating, SUM(m.id) as nb_related')
->innerJoin('m.AnotherRecords a')
->where('m.id = ?')
->fetchOne();
Now lets say i want a method on that record to check if the aggregate columns exist from when the record was queried, and if not then i want to go ahead an issue a separate query to get these values:
// this doesnt actually work because of filterSet and filterGet
// but its general idea
public function getAverageRating($wtihNbRelated = false)
{
if(!isset($this->avg_rating) || ($withNbRelated && !isset($this->nb_related))
{
$rating = $this->getTable()->getAverageRating($this, $withNbRelated);
$this->avg_rating = $rating['avg_rating'];
if($withNbRealted)
{
$this->nb_related = $rating['nb_related'];
}
}
return $withNbRelated
? array('avg_rating' => $this->avg_rating, 'nb_related' => $this->nb_related)
: array('avg_rating' => $this->avg_rating);
}
Is there an easy way (ie. not writing a custom hydrator) to do this?
Simple answer really. I forgot that Doctrine prefixes all its direct protected members with _. So, even though i initially tried manipulating the data member i was forgot the prefix giving me the same result as if i tried $this->avg_rating or its accessor method. The solution was:
public function getAverageRating($wtihNbRelated = false)
{
if(!isset($this->_data['avg_rating']) || ($withNbRelated && !isset($this->_data['nb_related']))
{
$rating = $this->getTable()->getAverageRating($this, $withNbRelated);
$this->_data['avg_rating'] = $rating['avg_rating'];
if($withNbRealted)
{
$this->_data['nb_related'] = $rating['nb_related'];
}
}
return $withNbRelated
? array('avg_rating' => $this->_data['avg_rating'], 'nb_related' => $this->_data['nb_related'])
: array('avg_rating' => $this->_data['avg_rating']);
}

ASP.Net MVC : Get query values with no key

I have URL: http://site.com/page.aspx?update
how do I check if that update value is present?
HttpValueCollection treats that as an entity with null key. I have tried:
var noKeyValues = Request.QueryString.GetValues(null);
if (noKeyValues != null && noKeyValues.Any(v=>v==update)) ...
but it gives me a frowny line, because GetValues' argument is decorated with [NotNull].
so I end up doing:
var queryValuesWithNoKey =
Request.QueryString.AllKeys.Select((key, index) => new { key, value = Request.QueryString.GetValues(index) }).Where(
item => item.key == null).Select(item => item.value).SingleOrDefault();
if (queryValuesWithNoKey != null && queryValuesWithNoKey.Any(v => v.ToLower() == "update")) live = true;
not the most elegant workaround. Is there a better way to get key-less value from query string?
You can use
Request.QueryString[null]
to retrieve a comma separated list of keys with no values. For instance, if your url is:
http://mysite/?first&second
then the above will return
first,second
In your case, you could just do something like:
if(Request.QueryString[null] == "update")
{
// it's an update
}
if that's the only key you would use
Request.QueryString.ToString() to get the "update" value
I know I'm late to the party, but this a function that I use for this kind of task.
internal static bool HasQueryStringKey(HttpRequestBase request, string key)
{
// If there isn't a value, ASP will not recognize variable / key names.
string[] qsParts = request.QueryString.ToString().Split('&');
int qsLen = qsParts.Length;
for (int i = 0; i < qsLen; i++)
{
string[] bits = qsParts[i].Split('=');
if (bits[0].Equals(key, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
You may need to update it so that it is case sensitive, or uses different arguments depending on your purposes, but this has always worked well for me.

Making Grails data-binding interpret empty String values ("") as zero (0)

This question is about altering how the Grails data-binding handles string-to-integer conversion.
Consider the following domain object:
class Foo {
String name
Integer price
}
Furthermore, assume that the domain object is populated from HTTP request parameters:
def foo = new Foo(params).save()
The save() method above will fail if params.price == "" (empty string). I'd like to change this behaviour globally so that an empty string is parsed as zero (0) when converting from a string to an integer in Grails data-binding. How do I achieve that?
added a filter see the setion 5.5.1 Events and Auto Timestamping in the grails documentation (http://grails.org/doc/1.1.x/index.html)
def beforeInsert = {
if (price == '') { price = 0}
}
Instead of changing the data binding why not just write your own setter? In the setter test to see if the string is empty, if it is set price to 0. If it isn't do a normal integer conversion.
try this constraint instead
static constraints = {
price(validator: {val, obj ->
if (val == '' || val == 0) {
obj.price = 0
return true
} else if (val < 1) {
return false;
}
})
}
import org.grails.databinding.BindUsing
class Foo {
String name
#BindUsing({ obj, source ->
source["price"] ?: 0
})
Integer price
}

Resources