Sort by multiple criteria with Kotlin - sorting

We want to sort objects by three different criteria, the criteria with higher priority overrides the next one):
status field
time field
sort field
status are enums and have a custom sorting, which we implemented by a comparator. (unknown, open, closed,)
sortBy Field is an Integer and can be sorted accordingly
time field can be null, we only want to sort the ones with null to the top and leave the remaining sorting as it is
example:
Object1(status: open, time: 1, sort: 4)
Object2(status: closed, time: null, sort: 1)
Object3(status: unknown, time: 2, sort: 3)
Object4(status: unknown, time: null, sort: 2)
Object5(status: open, time: 1, sort: 5)
sorted:
Object 3 (status: unknown)
Object 4 (status: unknown, next item in the original list)
Object 2 (status != unknown, time: null)
Object 1 (status != unknown, time != null, sort: 4)
Object 5 (status != unknown, time != null, sort: 5)
we return the list the following way:
list.sortedWith(statusComparator.thenBy { it.sort }
What is missing is the sorting of items with time = null.
How do I sort the items with time = null to the top and leave the remaining sorting untouched?

To sort depending if the value is null we can... do exactly this, sort by checking if it is null:
list.sortedWith(
statusComparator
.thenBy { it.time != null }
.thenBy { it.sort }
)
It works, because false is considered smaller than true. And it returns true for any non-null value, so all non-null values are considered the same.
We can join time and sort steps into a single one, by returning null if time is null and sort otherwise:
.thenBy { item -> item.time?.let { item.sort } }
// or:
.thenBy { if (it.time == null) null else it.sort }
It could be a little more performant, but I consider this harder to understand and less flexible.

You can combine Comparator objects with thenComparing so you probably want to write a comparator for the time something like
val timeComparator = Comparator<YourObject> { first, second ->
when {
first.time == null && second.time != null -> -1
first.time == null && second.time == null -> 0
first.time != null && second.time == null -> 1
else -> 0
}
}
And then change
list.sortedWith(statusComparator.thenBy { it.sort }
to
list.sortedWith(statusComparator.thenComparing(timeComparator).thenBy { it.sort })

Related

Filter a map by two conditions in Terraform

Having this input variable
variable "input_var"{
type = map(object({
str_attribute = string
num_attribute = number
sub_obj = optional(object({
id = number
other_property = string
}))
}))
}
How can I loop over all map elements and filter them while taking only the ones that complies to both sub_obj != null and id != 0? And how to make it a map where I keep both the original key and value?
I have tried several things but nothing usable. Latest iteration is:
for_each = [for k,v in var.input_var: tomap({k = v}) if v.sub_obj.id !=0]
This does not work first because if sub_obj is null an error occurs and I was unable to find any information about logical operators, like and in this case, such that I could use it as sub_obj != null and sub_obj.id !=0.
And second because it does not create a map, which makes sense as tomap would create a map from all objects added as arguments.
To construct a map instead of a list you should use the constructor {}.
For a safe check on the value of var.input_var.sub_obj.id inequality with 0, you can use the coalesce (similar to null coalescing operators) or try functions.
Combining the two we arrive at:
# option one
for_each = { for k,v in var.input_var: k => v if coalesce(sub_obj.id, 0) != 0 }
# option two
for_each = { for k,v in var.input_var: k => v if try(sub_obj.id != 0, false) }

What is the most efficient way or best practice for null check when you use Split and FirstOrDefault methods together?

I use Split and LastOrDefault methods together, I use this code block for null check. But it does not seems the most efficient way to me. Because I use lots of check and && operators. It seem a little ugly. Is there any way to accomplish this null-check in a better way? (I searched on web but couldn' t find related answers.)
Note : C# Language version 4.0
Here is my code :
if (HttpContext.Current.Request.Url.AbsolutePath.Split('/') != null &&
HttpContext.Current.Request.Url.AbsolutePath.Split('/').Length > 0 &&
HttpContext.Current.Request.Url.AbsolutePath.Split('/').Last() != null &&
HttpContext.Current.Request.Url.AbsolutePath.Split('/').Last().Split('.') != null &&
HttpContext.Current.Request.Url.AbsolutePath.Split('/').Last().Split('.').Length > 0 &&
HttpContext.Current.Request.Url.AbsolutePath.Split('/').Last().Split('.').First() != null)
{
pageName = HttpContext.Current.Request.Url.AbsolutePath.Split('/').LastOrDefault().Split('.').FirstOrDefault();
}
Thanks for all answers.
The tests are all not needed:
First, don't run Split multiple times on the same data:
var splitSlashAbsPath = HttpContext.Current.Request.Url.AbsolutePath.Split('/');
The return array from Split can never be null
// if (splitSlashAbsPath != null &&
the return array from Split can never be zero length
// splitSlashAbsPath.Length > 0 &&
so the return from Last() can never be null
// splitSlashAbsPath.Last() != null &&
Don't run split multiple times on the same data (and calling Last on an array doesn't make sense)
var splitDotAbsPath = splitSlashAbsPath[splitSlashAbsPath.Length-1].Split('.');
the return array from Split can never be null
// splitDotAbsPath != null &&
the return array from Split can never be zero length
// splitDotAbsPath.Length > 0 &&
so, the First() from Split can never be null
// splitDotAbsPath.First() != null)
// {
since you can call Last, calling LastOrDefault makes no sense
same for FirstOrDefault
// pageName = splitDotAbsPath.FirstOrDefault();
Calling First on an array also doesn't make sense
pageName = splitDotAbsPath[0];
// }
So, in summary you have:
var splitSlashAbsPath = HttpContext.Current.Request.Url.AbsolutePath.Split('/');
var splitDotAbsPath = splitSlashAbsPath[splitSlashAbsPath.Length-1].Split('.');
pageName = splitDotAbsPath[0];
However, in general, using Split for just getting one element is very inefficient, so this would be better:
var path = HttpContext.Current.Request.Url.AbsolutePath;
var pastSlashPos = path.LastIndexOf('/') + 1;
var countUntilDot = path.IndexOf('.', pastSlashPos);
countUntilDot = (countUntilDot >= 0 ? countUntilDot : path.Length) - pastSlashPos;
pageName = path.Substring(pastSlashPos, countUntilDot);

Using IEqualityComparer to check specific values

So this is the first time I've tried using IEqualityComparer and I'm running into an issue.
It's likely I just don't understand exactly what the code is doing behind the scenes.
The list I'm providing it looks like this:
Test Run | SN | Retest
1 185 0
2 185 1
3 185 1
4 185 1
I'm trying to use Distinct() to find the number of items which have a unique SN, and 'retest==1'.
var result = testRunList.Distinct(new UniqueRetests());
And the derived IEqualityCompare class looks like this:
public class UniqueRetests : IEqualityComparer<TestRunRecord>
{
// Records are equal if their SNs and retest are equal.
public bool Equals(TestRunRecord x, TestRunRecord y)
{
//Check whether any of the compared objects is null.
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
//Check whether it's a retest AND if the specified records' properties are equal.
return x.retest == 1 && y.retest == 1 && x.boardSN == y.boardSN;
}
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
public int GetHashCode(TestRunRecord record)
{
//Check whether the object is null
if (Object.ReferenceEquals(record, null)) return 0;
//Get hash code for the board SN field.
int hashRecordSN = record.boardSN.GetHashCode();
//Get hash code for the retest field.
int hashRecordRetest = record.retest.GetHashCode();
//Calculate the hash code for the product.
return hashRecordSN ^ hashRecordRetest;
}
}
Problem is that this seems to produce a which includes the first two items, whereas what I'm looking for is a list that includes only a single item where 'retest==1'.
Any idea what I'm doing wrong here? How is it that a record with 'retest == 0' is being returned?
Answer
If the condition is false, the objects are treated as if they are not equal. Distinct returns not-equal rows. Btw, you are violating the contract of IEqualityComparer with this type of code. The results are actually undefined. – usr
With "violating the contract" I mean (for example) than an object with retest==0 will compare unequal to itself. – usr
You need to filter out items with retest = 0. Put a .Where(x => x.retest != 0) in front of the Distinct.

Linq with Logic

I have simple Linq statement (using EF4)
var efCars = (from d in myentity.Cars
where d.CarName == inputCar.CarName
&& d.CarIdNumber == inputCar.IdNumber
&& d.Make == inputCar.Make
select d.Car);
I want it to be smarter so that it will only query across one or more of the 3 fields IF they have values.
I can do a test before, and then have a separate linq statement for each permutation of valyes for inputcar
(i.e. one for all 3, one for if only carname has a value, one for if carname AND CarIdNumber has a value etc etc)
but there must be a smarter way
Thanks!
If "has no value" means null then you can use the null coalescing operator ?? to say take the first value if populated, otherwise take the second:
var efCars = (from d in myentity.Cars
where d.CarName == (inputCar.CarName ?? d.CarName
&& d.CarIdNumber == (inputCar.IdNumber && d.CarIdNumber)
&& d.Make == (inputCar.Make && d.Make)
select d.Car);
This basically says if a value exists it must match, otherwise treat it as matching
However if instead you're saying "when a special value (empty string) ignore it, otherwise match" then you can do one of two approaches (or possibly more!):
where (inputCar.CarName == "" || d.CarName == inputCar.CarName)
where (string.IsNullOrEmpty(inputCar.CarName) || d.CarName == inputCar.CarName)
For performance (when dealing with database queries) it can sometimes be beneficial to let EF generate queries based on the filters, instead of using one generic query. Of course you will need to profile whether it helps you in this case (never optimize prematurely), but this is how it would look if you dynamically build your query:
var efCars =
from car in myentity.Cars
select car;
if (inputCar.CarName != null)
{
efCars =
from car in efCars
where care.CarName == inputCar.CarName
select car;
}
if (inputCar.IdNumber != null)
{
efCars =
from car in efCars
where care.CarIdNumber == inputCar.IdNumber
select car;
}
if (inputCar.Make != null)
{
efCars =
from car in efCars
where care.Make == inputCar.Make
select car;
}
where (inputCar.CarName != null || d.CarName == inputCar.CarName) &&...

Unpassable Where Clauses LINQ-to-SQL

As I'm struggling to learn LINQ I’ve managed to generate a SQL statement with "AND (0 = 1)" as part of the where clause. I'm just wondering if this result is common in poorly written queries and is a known issues to try and avoid or if I am doing something totally backwards to end up with this.
Update
public static IEnumerable<ticket> GetTickets(stDataContext db,string subgroup, bool? active)
{
var results = from p in db.tickets
where
( active == null || p.active == active )
/*(active == null ? true :
((bool)active ? p.active : !p.active))*/ &&
p.sub_unit == db.sub_units.Where(c=>subgroup.Contains(c.sub_unit_name))
select p;
return results;
}
If I ignore the active part and just run
public static IEnumerable<ticket> GetTickets1(stDataContext db,string subgroup, bool? active)
{
return db.tickets.Where(c => c.sub_unit.sub_unit_name == subgroup);
}
It returns the groups of tickets I want ignoring the active part.
I'd pull the processing out of the ternary operators.
where ( active == null || p.active == active )
EDIT
The rest of the where clause looks funky too... why is it not just doing
&& p.sub_unit.sub_unit_name == subgroup
or
&& subgroup.Contains(p.sub_unit.sub_unit_name)
?
That is some pretty heavy abuse of the ternary operator.
This expression:
(active == null ? true :
((bool)active ? p.active : !p.active))
Is equivalent to the following logic:
bool result;
if (active == null)
{
result = true;
}
else
{
if ((bool)active)
{
result = p.active;
}
else
{
result = !p.active;
}
}
result &= ...
Think carefully about what this is doing:
If active is null, you're fine, it skips to the next condition.
If active is true, result is true at the end of the conditional.
If active is false, result is false at the end of the conditional.
In the last case, the query can never return any rows!
#Tanzelax has already supplied a simple rewrite. The main idea is that you want to compare p.active to active, not actually evaluate the condition as p.active.
This is probably caused by a null value in one you the columns you have declared as non-nullable. LINQ2SQL makes all columns non-nullable by default. Go back to the designer and change the fields to allow values to be null. Unfortunately this feature is By Design (see connect.microsoft.com link below.)
(linq) incorrect sql generated for row.column == localVar when localVar is null (should be "is null" check)

Resources