C# logic to find offset and fetch values - c#

I get the below resultset from a SQL query and I store it in var.
+-----------+--------+
| Rownumber | Data |
+-----------+--------+
| 0 | 9 |
| 1 | 0 |
| 2 | 4 |
| 3 | 9 |
| 4 | 15 |
| 5 | 2 |
| 6 | 1 |
| 7 | 6 |
| 8 | 0 |
| 9 | 4 |
| 10 | 1 |
| 11 | 1 |
| 12 | 1 |
| 13 | 1 |
| 14 | 1 |
| 15 | 1 |
| 16 | 1 |
| 17 | 1 |
| 18 | 1 |
| 19 | 1 |
| 20 | 1 |
| 21 | 1 |
| 22 | 1 |
+-----------+--------+
I want to write a logic in c# :
I want to add the Data column sequentially.
If the summed Data column value is more than or equal to 15, then I want to store the following value in two variables:
offset = The starting point of rownumber
Fetch = Num of rows taken to achieve the sum 15
E.g:
Iteration 1:
+-----------+--------+
| Rownumber | Data |
+-----------+--------+
| 0 | 9 |
| 1 | 0 |
| 2 | 4 |
| 3 | 9 |
+-----------+--------+
Expected variable values:
offset = 0
Fetch = 4 (num of rows taken to achieve the value of 15. Sum of value should be >= 15)
Iteration 2 :
+-----------+--------+
| Rownumber | Data |
+-----------+--------+
| 4 | 15 |
+-----------+--------+
Expected values:
offset = 4
Fetch = 1 (num of rows taken to achieve the value of 15)
Iteration 3:
+-----------+--------+
| Rownumber | Data |
+-----------+--------+
| 5 | 2 |
| 6 | 1 |
| 7 | 6 |
| 8 | 0 |
| 9 | 4 |
| 10 | 1 |
| 11 | 1 |
+-----------+--------+
Expected values:
offset = 5
Fetch = 7 (num of rows taken to achieve the value of 15)
The iteration will go on until the last value.

I supposed your model look like the following
public class Data
{
public int Rownumber { get; set; }
public int data { get; set; }
}
public class Result
{
public int offset { get; set; }
public int fetsh { get; set; }
}
and you need the following code
public List<Result> GetResults(List<Data> data)
{
var sum = 0;
var start_taking_index = 0;
List<Result> results = new List<Result>();
for (int i = 0; i < data.Count; i++)
{
sum += data[i].data;
if(sum >= 15 || i == data.Count-1)
{
// if the sum exceed 15 create new result
results.Add(new Result
{
offset = start_taking_index,
fetsh = i - start_taking_index +1,
});
// then reset the tracking variables
start_taking_index = i+1;
sum = 0;
}
}
return results;
}
here is xUnit test the scenario in the question
[Fact]
public void GetResults_test()
{
List<Data> datas = new List<Data>()
{
new Data{Rownumber = 0,data= 9},
new Data{Rownumber = 1,data= 0},
new Data{Rownumber = 2,data= 4},
new Data{Rownumber = 3,data= 9},
new Data{Rownumber = 4,data=15},
new Data{Rownumber = 5,data= 2},
new Data{Rownumber = 6,data= 1},
new Data{Rownumber = 7,data= 6},
new Data{Rownumber = 8,data= 0},
new Data{Rownumber = 9,data= 4},
new Data{Rownumber = 10,data= 1},
new Data{Rownumber = 11,data= 1},
new Data{Rownumber = 12,data= 1},
new Data{Rownumber = 13,data= 1},
new Data{Rownumber = 14,data= 1},
new Data{Rownumber = 15,data= 1},
new Data{Rownumber = 16,data= 1},
new Data{Rownumber = 17,data= 1},
new Data{Rownumber = 18,data= 1},
new Data{Rownumber = 19,data= 1},
new Data{Rownumber = 20,data= 1},
new Data{Rownumber = 21,data= 1},
new Data{Rownumber = 22,data= 1},
};
var result = GetResults(datas);
Assert.NotEmpty(result);
// first
Assert.Equal(0,result[0].offset);
Assert.Equal(4,result[0].fetsh);
// second
Assert.Equal(4, result[1].offset);
Assert.Equal(1, result[1].fetsh);
//
Assert.Equal(5, result[2].offset);
Assert.Equal(7, result[2].fetsh);
//
Assert.Equal(12, result[3].offset);
Assert.Equal(11, result[3].fetsh);
// total count count
Assert.Equal(4, result.Count);
}

I would go with:
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
List<Data> datas = new List<Data>()
{
new Data{Rownumber = 0,data= 9},
new Data{Rownumber = 1,data= 0},
new Data{Rownumber = 2,data= 4},
new Data{Rownumber = 3,data= 9},
new Data{Rownumber = 4,data=15},
new Data{Rownumber = 5,data= 2},
new Data{Rownumber = 6,data= 1},
new Data{Rownumber = 7,data= 6},
new Data{Rownumber = 8,data= 0},
new Data{Rownumber = 9,data= 4},
new Data{Rownumber = 10,data= 1},
new Data{Rownumber = 11,data= 1},
new Data{Rownumber = 12,data= 1},
new Data{Rownumber = 13,data= 1},
new Data{Rownumber = 14,data= 1},
new Data{Rownumber = 15,data= 1},
new Data{Rownumber = 16,data= 1},
new Data{Rownumber = 17,data= 1},
new Data{Rownumber = 18,data= 1},
new Data{Rownumber = 19,data= 1},
new Data{Rownumber = 20,data= 1},
new Data{Rownumber = 21,data= 1},
new Data{Rownumber = 22,data= 1},
};
foreach(var entry in Calculate(datas))
{
Console.WriteLine("Offset: " + entry.Key + " | Fetch: " + entry.Value);
}
}
public static List<KeyValuePair<int, int>> Calculate(List<Data> data)
{
var result = new List<KeyValuePair<int, int>>();
int offset = 0, lastOffset = 0;
int sum = 0;
foreach(var entry in data)
{
sum += entry.data;
if(sum >= 15)
{
result.Add(new KeyValuePair<int, int>(lastOffset, offset - lastOffset + 1));
sum = 0;
lastOffset = offset + 1;
}
offset++;
}
return result;
}
public class Data
{
public int Rownumber { get; set; }
public int data { get; set; }
}
}
Mind that it does not have any guards - it's up to you.

System.Linq may do trick for you with Skip and TakeWhile extension methods:
static void Main()
{
// Fill source info
var source = new List<(int, int)>();
for (int i = 0; i <= 22; i++)
source.Add((i, i switch
{
0 => 9,
1 or 8 => 0,
2 or 9 => 4,
3 => 9,
4 => 15,
5 => 2,
7 => 6,
_ => 1,
}));
// Fetching result
foreach (var (offset, fetch) in GetResult(source))
Console.WriteLine($"Offset: {offset} | Fetch: {fetch}");
// Output:
// Offset: 0 | Fetch: 4
// Offset: 4 | Fetch: 1
// Offset: 5 | Fetch: 7
// Offset: 12 | Fetch: 11
Console.ReadKey();
}
static List<(int, int)> GetResult(List<(int, int)> source)
{
var result = new List<(int, int)>();
var proceededRecords = 0;
while (proceededRecords < source.Count)
{
var offset = proceededRecords;
var dataSum = 0;
var fetch = source.Skip(proceededRecords).TakeWhile((data, _) =>
{
if (dataSum >= 15)
return false;
dataSum += data.Item2;
return true;
}).Count();
proceededRecords += fetch;
result.Add((offset, fetch));
}
return result;
}
Remarks.
I used tuples to simplify example and avoid creating some Model class with RowNumber and Data properties or Result class with Offset and Fetch properties.
The idea was to loop over source collection with taking some unknown amount of tuples until sum of 2nd value in tuple is less than 15. TakeWhile help me with that. Skip was used to... skip amount of already fetched records.

While your example shows that your data indexes are sequential and start at zero, it was not stated explicitely that it will be always the case.
This solution works even if the index of the data from your database is not starting from 0, or is not sequencial (or both). Or if instead of an index you had any other kind of identifier, such as a timestamp for instance.
i.e. if your data is like
+-----------+--------+
| Rownumber | Data |
+-----------+--------
| 6 | 1 |
| 7 | 6 |
| 8 | 0 |
| 9 | 4 |
etc...
+-----------+--------+
or
+-----------+--------+
| Rownumber | Data |
+-----------+--------
| 16 | 1 |
| 7 | 6 |
| 108 | 0 |
| 9 | 4 |
| 1910 | 1 |
| 121 | 1 |
etc..
+-----------+--------+
or even
+-----------------------+--------+
| Timestamp | Data |
+-----------------------+--------+
| 2021/03/02 - 10:06:24 | 1 |
| 2021/03/02 - 12:13:03 | 6 |
| 2021/03/04 - 02:48:57 | 0 |
| 2021/05/23 - 23:38:17 | 4 |
etc...
+-----------------------+--------+
Here is the part of the code actually doing the work. It does not really add complexity compared to a solution that would work only on zero starting sequencial indexes:
var Results = new List<Result>();
var Group = new List<Data>();
var Sum = 0;
var CurrentIndex = 0;
while (Source.Any())
{
CurrentIndex = 0;
Sum = 0;
while (Sum < 15 && CurrentIndex < Source.Count)
{
Sum += Source[CurrentIndex].Value;
CurrentIndex++;
}
Group = Source.Take(CurrentIndex).ToList();
Source = Source.Skip(CurrentIndex).ToList();
Results.Add(new Result
{
Root = Group.First().Index,
Fetch = Group.Count
});
}
What it does is rather simple:
It enumerates the first elements of your collection (source) while their sum is inferior to 15 (and there is still some elements to enumerate).
It counts the number of elements just enumerated (the fetch) and get the index of the first element (the root).
It then constructs a new collection by removing the elements that were just enumerated, and starts again, using that new collection until there is no more elements to enumerate.
That is all.
The Group variable could be avoided alltogether. It would give the following code. I prefered keeping it in my example as it shows that the group itself could be used to perform any kind of operation on its content if needed.
while (Source.Any())
{
CurrentIndex = 0;
Sum = 0;
while (Sum < 15 && CurrentIndex < Source.Count)
{
Sum += Source[CurrentIndex].Value;
CurrentIndex++;
}
Results.Add(new Result
{
Root = Source.First().Index,
Fetch = CurrentIndex
});
Source = Source.Skip(CurrentIndex).ToList();
}
By the way, the second nested while loop could be avoided by using Linq, see below. However this Linq query is particular and should be used with caution.
The TakeWhile method is using an unpure lambda, i.e. the lambda relies on external data: the captured variable Sum.
While this works perfectly fine, be aware that generally this kind of Linq query could lead to problems further down the road. For instance adding .AsParallel() to such kind of query would not work at all.
while (Source.Any())
{
Sum = 0;
Group = Source.TakeWhile(e =>
{
if (Sum < 15)
{
Sum += e.Value;
return true;
}
return false;
}).ToList();
Results.Add(new Result
{
Root = Group.First().Index,
Fetch = Group.Count
});
Source = Source.Skip(Group.Count).ToList();
}
Here is the complete code, as a full runnable Linqpad query, with randomly generated data:
void Main()
{
// Random data set preparation.
var Rnd = new Random();
var NonSequencialIndexes = Enumerable
.Range(100, 300)
.Where(i => Rnd.Next(2) == 1)
.OrderBy(i => Guid.NewGuid())
.Take(30)
.ToArray();
var Source = Enumerable
.Range(0, 30)
.Select(i => new Data
{
Index = NonSequencialIndexes[i],
Value = Rnd.Next(16)
})
.ToList()
.Dump("Random data set");
// Actual code
var Results = new List<Result>();
var Group = new List<Data>();
var Sum = 0;
var CurrentIndex = 0;
while (Source.Any())
{
CurrentIndex = 0;
Sum = 0;
while (Sum < 15 && CurrentIndex < Source.Count)
{
Sum += Source[CurrentIndex].Value;
CurrentIndex++;
}
Group = Source.Take(CurrentIndex).ToList();
Source = Source.Skip(CurrentIndex).ToList();
Results.Add(new Result
{
Root = Group.First().Index,
Fetch = Group.Count
});
}
// Display results
Results.Dump("Results");
}
// You can define other methods, fields, classes and namespaces here
public class Data
{
public int Index { get; set; }
public int Value { get; set; }
}
public class Result
{
public int Root { get; set; }
public int Fetch { get; set; }
}
An example of result:
+------+-------+
| Root | Fetch |
+------+-------+
| 346 | 3 |
+------+-------+
| 121 | 3 |
+------+-------+
| 381 | 2 |
+------+-------+
| 110 | 2 |
+------+-------+
| 334 | 2 |
+------+-------+
| 226 | 2 |
+------+-------+
| 148 | 2 |
+------+-------+
| 114 | 3 |
+------+-------+
| 397 | 3 |
+------+-------+
| 274 | 3 |
+------+-------+
| 135 | 3 |
+------+-------+
| 386 | 2 |
+------+-------+
for this data collection
+-------+------+
| Index | Value|
+-------+------+
| 346 | 0|
+-------+------+
| 294 | 14|
+-------+------+
| 152 | 11|
+-------+------+
| 121 | 3|
+-------+------+
| 234 | 6|
+-------+------+
| 393 | 13|
+-------+------+
| 381 | 8|
+-------+------+
| 305 | 15|
+-------+------+
| 110 | 13|
+-------+------+
| 357 | 9|
+-------+------+
| 334 | 8|
+-------+------+
| 214 | 13|
+-------+------+
| 226 | 6|
+-------+------+
| 248 | 15|
+-------+------+
| 148 | 12|
+-------+------+
| 131 | 9|
+-------+------+
| 114 | 3|
+-------+------+
| 250 | 4|
+-------+------+
| 217 | 11|
+-------+------+
| 397 | 3|
+-------+------+
| 312 | 7|
+-------+------+
| 191 | 7|
+-------+------+
| 274 | 7|
+-------+------+
| 292 | 6|
+-------+------+
| 277 | 14|
+-------+------+
| 135 | 2|
+-------+------+
| 240 | 12|
+-------+------+
| 163 | 12|
+-------+------+
| 386 | 12|
+-------+------+
| 330 | 5|
+-------+------+

Related

Replace a Number with a Random Number

I want to make my own little encryption (it not must be secure).
I thought it would be a good idea, if the entered password would be converted to binary and then I want to change all the 1 to (2 | 3 | 9 | 7) so if we had
1101 = 2907 | 9703 ... and so on
So if we would enter 9703 we could decrypt it to 1101 again.
But I can’t find a Method to Replace these.
The Replace() Method would only do this:
1101 -> 2202 | 1101 -> 9909
And yes, i know that is not a good Method to encrypt something but I just want to code a very simple encryption on my own.
int[] replace_Ones = { 2, 3, 9, 7 };
int ServerEncryption = 1101010000111001;
ServerEncryption.Replace(1, 2);
Starting from this:
int[] replace_Ones = { 2, 3, 9, 7 }; // I removed the 0 ;)
long ServerEncryption = 1101010000111001;
You can make a method that does the following:
long FunnyEncrypt( long pseudobinary )
{
long result = 0;
long scope = 1;
while( pseudobinary > 0 )
{
// place 2 or 3 or 7 or 9 0 or 1
result += scope * GetRandomReplacement() * ( pseudobinary % 10 );
scope *= 10;
pseudobinary = pseudobinary / 10; // decimal right shift 110 / 10 -> 11
}
return result;
}
Disclaimer: untested! Corrections welcome.
GetRandomReplacement is left for practice :D - but it basically is "pick a random int out of [0..3] and use it as index into the array of [2,3,7,9]".
Example:
example input: 1101
| iteration | input | result | scope | -> | input | result | scope | GetRandomRepl |
| 1 | 1101 | 0 | 1 | -> | 110 | 7 | 10 | 7 |
| 2 | 110 | 7 | 10 | -> | 11 | 7 | 100 | - |
| 3 | 11 | 7 | 100 | -> | 1 | 307 | 1000 | 3 |
| 4 | 1 | 307 | 1000 | -> | 0 | 2307 | 10000 | 2 |
=> Result = 2307
EDIT: changed to long after testing, did not see the number is too big for int: https://dotnetfiddle.net/5X4lZu
If the problem is to replace '1' value of a binary string to a random value in { 2, 3, 9, 7 } you could do like this:
char[] replace_Ones = { '2', '3', '9', '7' };
StringBuilder ServerEncryption =new StringBuilder("1101010000111001");
Random r = new Random();
for (int i=0 ; i < ServerEncryption.Length ; i++)
{
if (ServerEncryption[i] != '0')
ServerEncryption[i] = replace_Ones[r.Next(replace_Ones.Length)];
}
Console.WriteLine(ServerEncryption);
dotnetFiddle

How to validate Date Start and Finish Overlap from a list of items

What I have
A list of objects with Id, DateStart and DateFinish.
[
{
Id: 1234567890,
DateStart: new DateTime(),
DateFinish: new DateTime(),
},
...
]
What I need to do
I need to validate if none of the dates overlap each other.
I'm not sure if overlap is passing the right meaning here, so here is some examples:
Invalid Entry
[
{
Id: 1,
DateStart: new DateTime().AddHours(1),
DateFinish: new DateTime().AddHours(3),
},
{
Id: 2,
DateStart: new DateTime().AddHours(2),
DateFinish: new DateTime().AddHours(4),
}
]
This list have an overlap because the time of id 2 is in the middle of id 1
A table to show better:
-------------------------------------------------------------
| 1 | 2 | 3 | 4 |
| DateStart1 | | DateFinish1 | |
| | DateStart2 | | DateFinish2 |
-------------------------------------------------------------
*overlap* *overlap*
Other Invalid Examples
-------------------------------------------------------------
| 1 | 2 | 3 | 4 |
| DateStart1 | | | DateFinish1 |
| | DateStart2 | DateFinish2 | |
-------------------------------------------------------------
*overlap* *overlap*
-------------------------------------------------------------
| 1 | 2 | 3 | 4 |
| DateStart1 | | | DateFinish1 | // This would be a full overlap
| DateStart2 | | | DateFinish2 | // And it's also Invalid
-------------------------------------------------------------
*overlap* *overlap*
-------------------------------------------------------------
| 1 | 2 | 3 | 4 |
| | DateStart1 | | DateFinish1 | // Same as first example
| DateStart2 | | DateFinish2 | | // But "inverted"
-------------------------------------------------------------
*overlap* *overlap*
Valid Entry
[
{
Id: 1,
DateStart: new DateTime().AddHours(1),
DateFinish: new DateTime().AddHours(2),
},
{
Id: 2,
DateStart: new DateTime().AddHours(2),
DateFinish: new DateTime().AddHours(4),
}
]
A table to show better:
-------------------------------------------------------------
| 1 | 2 | 3 | 4 |
| DateStart1 | DateFinish1 | | |
| | DateStart2 | | DateFinish2 |
-------------------------------------------------------------
*not overlap*
And you can also have DateStart and DateFinish that are the same value, which means it can start and end at the same time.
-------------------------------------------------------------
| 1 | 2 | 3 | 4 |
| DateStart1 | | | |
| DateFinish1 | | | |
| DateStart2 | | | DateFinish2 |
-------------------------------------------------------------
*not overlap*
What I have done so far:
I'm making a foreach loop, where item is each element, and using a where with the following expression:
myList.Any(
x => x.Id == item.Id
&&
(
(
item.DateStart <= x.DateStart
&&
item.DateFinish > x.DateStart
&&
item.DateFinish <= x.DateFinish
)
||
(
item.DateStart >= x.DateStart
&&
item.DateStart < x.DateFinish
&&
item.DateFinish > x.DateFinish
)
||
(
item.DateStart <= x.DateStart
&&
item.DateFinish >= x.DateFinish
)
)
)
My Question
Is this expression correct? I have tried it with a lot of data and it seems to be wrong sometimes.
I need to be certain that it will cover all edge cases.
If there is a better way of writing all this logic, it would help to, because this code looks to ugly and hard to understand for other people.
I would use the following code:
static bool IsOverlapping(IEnumerable<Range> list)
{
Range previousRange = null;
foreach (var currentRange in list.OrderBy(x => x.DateStart).ThenBy(x => x.DateFinish))
{
if (currentRange.DateStart > currentRange.DateFinish)
return true;
if (previousRange?.DateFinish > currentRange.DateStart)
return true;
previousRange = currentRange;
}
return false;
}
A quick and dirty version. Not very performant as is on large sets. But can be improved on.
https://dotnetfiddle.net/Widget/PEn2Lm
static void DetectOverlap(List<Range> l)
{
foreach(var r in l)
{
var overlap = l.Any(x => x.Id != r.Id
&& ((r.Start == x.Start && r.End == x.End)
|| (r.Start >= x.Start && r.Start < x.End)
|| (r.End > x.Start && r.End <= x.End)));
if(overlap)
{
Console.WriteLine("Overlap detected");
throw new Exception("Overlapping range detected");
}
}
Console.WriteLine("Clean ranges");
}
I tried to mirror your cases, but I'd suggest writing unit tests to full test all of your scenarios.
List<FooBar> bars = new List<FooBar>()
{
new FooBar() //end date is inside 3
{
Start = new DateTime(2001,12,1),
End = new DateTime(2002,5,15),
Id = 1
},
new FooBar() //fine
{
Start = new DateTime(2005,12,1),
End = new DateTime(2006,5,15),
Id = 2
},
new FooBar() //start date is inside 1
{
Start = new DateTime(2002,4,1),
End = new DateTime(2003,5,15),
Id = 3
},
new FooBar() //this one is fine
{
Start = new DateTime(2006,5,15),
End = new DateTime(2007,5,15),
Id = 4
},
new FooBar() //also fine
{
Start = new DateTime(2001,12,1),
End = new DateTime(2001,12,1),
Id = 5
},
};
And then a, to me at least, slightly easier to read / skim code snippet which seems to work perfectly:
var inside = bars.Where(w =>
bars.Where(outer => ((outer.Start < w.Start && outer.End > w.Start)
|| (outer.Start < w.End && outer.End > w.End)
|| (outer.Start == w.Start && outer.End == w.End)) && outer.Id != w.Id).Any()).ToList();
inside.ForEach(e => {
Console.WriteLine($"{e.Id}");
});
For actual use, I'd also test for Any or First, and not ToList, but this gives me the Ids for the console to check.
As for why I used this logic, it might prove faulty but my assumptions:
An overlapped start date is between another input's start and end,
or an item's end date is between another input's start and end dates,
or a start and end date matches another input's values exactly.
Additional tests (as with your provided code) I expect false positives due to the use of <= and >=.
For example, changing the method to include
bars.Where(outer => ((outer.Start <= w.Start && outer.End >= w.Start)
gives me false positives on 4 and 5

Finding multiple unique matches from List<object> where two criteria have to be different

I am having trouble selecting the first item in a list that is unique based on two fields, JOB_ID and EMPLOYEE_ID.
Each job should only be assigned to one employee (the one with the lowest OVERALL_SCORE), then move on and assign the next employee.
The List Objects are as follows:
JobMatch.cs
public int JOB_ID { get; set; }
public int JOB_MATCHES_COUNT { get; set; }
EmployeeMatch.cs
public int EMPLOYEE_ID { get; set; }
public int EMPLOYEE_MATCHES_COUNT { get; set; }
Rankings.cs
public int JOB_ID { get; set; }
public int EMPLOYEE_ID { get; set; }
public int TRAVEL_TIME_MINUTES { get; set; }
public int PRIORITY { get; set; }
public int OVERALL_SCORE { get; set; }
Rankings.cs gets an overall score based on the travel time field and
number of matches an Employee/Job has.
EmployeeMatch.cs
+-------------+-------------------+
| EMPLOYEE_ID | EMP_MATCHES_COUNT |
+-------------+-------------------+
| 3 | 1 |
| 4 | 1 |
| 2 | 3 |
| 1 | 4 |
+-------------+-------------------+
JobMatch.cs
+--------+-------------------+
| JOB_ID | JOB_MATCHES_COUNT |
+--------+-------------------+
| 1 | 1 |
| 2 | 2 |
| 3 | 2 |
| 4 | 4 |
+--------+-------------------+
Ranking.cs (shortened as to not fill the screen)
+--------+-------------+---------------+
| JOB_ID | EMPLOYEE_ID | OVERALL_SCORE |
+--------+-------------+---------------+
| 4 | 3 | 800 |
| 4 | 4 | 800 |
| 3 | 1 | 800 |
| 3 | 2 | 1200 |
| 2 | 1 | 1600 |
| 2 | 2 | 1800 |
| 4 | 1 | 2000 |
| 4 | 2 | 2100 |
| 1 | 1 | 6400 |
+--------+-------------+---------------+
Basically, the idea is to select the first unique Employee and Job in this list and then the best matches will be put into a separate list, something like the following for the above scenario:
+--------+-------------+---------------+
| JOB_ID | EMPLOYEE_ID | OVERALL_SCORE |
+--------+-------------+---------------+
| 4 | 3 | 800 |
| 3 | 1 | 800 |
| 2 | 2 | 1800 |
+--------+-------------+---------------+
I tried the following but it didn't work as intended:
var FirstOrder = (rankings.GroupBy(u => u.JOB_ID)
.Select(g => g.First())).ToList();
var SecondOrder = (FirstOrder.GroupBy(u => u.EMPLOYEE_ID)
.Select(g => g.First())).ToList();
The idea is choosing first element and then removing corresponding elements from list to make sure next choice is unique, as below:
var rankings = new List<Rankings> {
new Rankings{ JOB_ID= 4,EMPLOYEE_ID= 3, OVERALL_SCORE= 800 },
new Rankings{ JOB_ID= 4,EMPLOYEE_ID= 4, OVERALL_SCORE= 800 },
new Rankings{ JOB_ID= 3,EMPLOYEE_ID= 1, OVERALL_SCORE= 800 },
new Rankings{ JOB_ID= 3,EMPLOYEE_ID= 2, OVERALL_SCORE= 1200 },
new Rankings{ JOB_ID= 2,EMPLOYEE_ID= 1, OVERALL_SCORE= 1600 },
new Rankings{ JOB_ID= 2,EMPLOYEE_ID= 2, OVERALL_SCORE= 1800 },
new Rankings{ JOB_ID= 4,EMPLOYEE_ID= 1, OVERALL_SCORE= 2000 },
new Rankings{ JOB_ID= 4,EMPLOYEE_ID= 2, OVERALL_SCORE= 2100 },
new Rankings{ JOB_ID= 1,EMPLOYEE_ID= 1, OVERALL_SCORE= 6400 },
};
var cpy = new List<Rankings>(rankings);
var result = new List<Rankings>();
while (cpy.Count() > 0)
{
var first = cpy.First();
result.Add(first);
cpy.RemoveAll(r => r.EMPLOYEE_ID == first.EMPLOYEE_ID || r.JOB_ID == first.JOB_ID);
}
result:
+--------+-------------+---------------+
| JOB_ID | EMPLOYEE_ID | OVERALL_SCORE |
+--------+-------------+---------------+
| 4 | 3 | 800 |
| 3 | 1 | 800 |
| 2 | 2 | 1800 |
+--------+-------------+---------------+
Really, if you're trying to get the best score for the job, you don't need to select by unique JOB_ID/EMPLOYEE_ID, you need to sort by JOB_ID/OVERALL_SCORE, and pick out the first matching employee per JOB_ID (that's not already in the "assigned list").
You could get the items in order using LINQ:
var sorted = new List<Ranking>
(
rankings
.OrderBy( r => r.JOB_ID )
.ThenBy( r => r.OVERALL_SCORE )
);
...and then peel off the employees you want...
var best = new List<Ranking>( );
sorted.ForEach( r1 =>
{
if ( !best.Any
(
r2 =>
r1.JOB_ID == r2.JOB_ID
||
r1.EMPLOYEE_ID == r2.EMPLOYEE_ID
) )
{
best.Add( r1 );
}
} );
Instead of using Linq to produce a sorted list, you could implement IComparable<Ranking> on Ranking and then just sort your rankings:
public class Ranking : IComparable<Ranking>
{
int IComparable<Ranking>.CompareTo( Ranking other )
{
var jobFirst = this.JOB_ID.CompareTo( other.JOB_ID );
return
jobFirst == 0?
this.OVERALL_SCORE.CompareTo( other.OVERALL_SCORE ):
jobFirst;
}
//--> other stuff...
}
Then, when you Sort() the Rankings, they'll be in JOB_ID/OVERALL_SCORE order. Implementing IComparable<Ranking> is probably faster and uses less memory.
Note that you have issues...maybe an unstated objective. Is it more important to fill the most jobs...or is more important to find work for the most employees? The route I took does what you suggest, and just take the best employee for the job as you go...but, maybe, the only employee for job 2 may be the same as the best employee for job 1...and if you put him/her on job 1, you might not have anybody left for job 2. It could get complicated :-)
Basically you could use System.Linq.Distinct method reinforced with the custom equality comparer IEqualityComparer<Ranking>. The System.Linq provide this method out of the box.
public class Comparer : IEqualityComparer<Ranking>
{
public bool Equals(Ranking l, Ranking r)
{
return l.JOB_ID == r.JOB_ID || l.EMPLOYEE_ID == r.EMPLOYEE_ID;
}
public int GetHashCode(Ranking obj)
{
return 1;
}
}
The trick here is with the GetHashCode method, and then as simple as this
rankings.Distinct(new Comparer())

How to efficiently get unique combinations of a list of objects

I have a single list of objects that I want to make unique combinations from.
The objects that I have are (CityObj);
public string City_Name;
public int Population;
double xcord;
double ycord;
double zcord;
The result would be a list that contains a new object (CityComboObj)
public string City_NameA;
public int PopulationA;
double xcordA;
double ycordA;
double zcordA;
public string City_NameB;
public int PopulationB;
double xcordB;
double ycordB;
double zcordB;
A sample of the dataset is as follows;
City1 | 3840 | 42 | -12 | 5
City2 | 39402 | 1 | 59 | -5
City3 | 5934 | 99 | -55 | 3
City4 | 12394 | -56 | 9 | 16
The resultant list of objects would look like;
City1 | 3840 | 42 | -12 | 5 City2 | 39402 | 1 | 59 | -5
City1 | 3840 | 42 | -12 | 5 City3 | 5934 | 99 | -55 | 3
City1 | 3840 | 42 | -12 | 5 City4 | 12394 | -56 | 9 | 16
City2 | 39402 | 1 | 59 | -5 City3 | 5934 | 99 | -55 | 3
City2 | 39402 | 1 | 59 | -5 City4 | 12394 | -56 | 9 | 16
City3 | 5934 | 99 | -55 | 3 City4 | 12394 | -56 | 9 | 16
As you can see its only the unique results returned.
Currently I am using a horribly inefficient apporach to do going this;
foreach (var element in CityListA)
{
if (!CityListB.Any(o => o.City_NameA == element.City_NameA && o.City_NameB == element.City_NameB))
{
if (!CityListB.Any(o => o.City_NameA == element.City_NameB && o.City_NameB == element.City_NameA))
{
CityListB.add(element)
}
}
}
In a nutshell the approach is to take two lists one full and one empty, compare each element of the full list with the empty list to see if it exists or the transpose exists and if it doesn't add it.
It works but it is slow, is there a better way of doing this?
Thanks
Your code needs some reengineering, for example:
internal class CityObjs : List<CityObj>
{
}
internal class CityObj
{
public string City_Name;
public int Population;
double xcord;
double ycord;
double zcord;
}
internal class CityComboObj
{
internal CityObj CityA ;
internal CityObj CityB ;
internal CityComboObj(CityObj A,CityObj B) { CityA=A ; CityB=B;}
}
internal class CityComboObjs: List<CityComboObj>
{
}
Assuming that the list CityObjs is initialized:
CityComboObjs = new CityComboObjs() ;
for (int i=0;i<CityObjs.Count-1;i++) for (int j=i+1;j<CityObjs.Count;j++)
CityComboObjs.Add(new CityComboObj(CityObjs[i],CityObjs[j]) ;
This actually works. Just replace simple integers with real objects. The idea of this code is to do it more efficiently, as you asked, without checking if the pair already exists. In your case myList[i] will return a CITY object
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var myList = (new []{1 , 2, 3, 4}).ToList();
var newList = new List<ListObject>();
var count = myList.Count;
for (int i = 0; i < count - 1; i++)
{
for(int j = i + 1; j < count; j++)
{
newList.Add(new ListObject(){ I = myList[i], J = myList[j]});
}
}
newList.ForEach(x => Console.WriteLine(x));
}
class ListObject
{
public int I {get; set;}
public int J {get; set;}
public override string ToString()
{
return I + " - " + J;
}
}
}
Output
1 - 2
1 - 3
1 - 4
2 - 3
2 - 4
3 - 4

How to add List<T> containing item of type List<string> ,int,string into another List

I have a list with complex data
public class CAR
{
public int ID {get ; set ; }
public string Name { get ; set ; }
public string EngineType { get ; set ; }
public List<string> Months { get; set; }
}
Note that Months data is List<string> its max count is 150
List<CAR> A = new List<CAR>();
List<CAR> B = new List<CAR>();
A has follwoing data
ID | Name | EngineType | Months[0] | Months[1] | Months[2] | Months[3] .. | Months[149] |
1 | Zen | 1001 | 1 | 1 | 4 | 5 .. | 6 |
2 | Benz | 2002 | 6 | 4 | 5 | 6 .. | 2 |
3 | Zen | 1001 | 3 | 1 | 7 | 5 .. | 0 |
4 | Zen | 1001 | 2 | 2 | 4 | 5 .. | 6 |
5 | Zen | 2002 | 2 | 2 | 4 | 5 .. | 6 |
6 | Benz | 2002 | 1 | 1 | 1 | 1 .. | 1 |
IF EngineType and Name are same we add those rows and store the result in a single row
Eg : adding rows
row 1 in B = 1 + 3 + 4
row 2 in B = 2 + 6
row 3 in B = 5
B should contain the following op
ID | Name | EngineType | Months[0] | Months[1] | Months[2] | Months[3] ... | Months[149] |
- | Zen | Petrol | 6 | 4 | 15 | 15 .. | 12 |
- | Benz | Diesel | 7 | 5 | 6 | 7 | 3 |
- | Zen | Diesel | 2 | 2 | 4 | 5 .. | 6 |
had months data been separate entity of type integer something else i could have done this
B = from val in A
group val by new val.EngineType into g
select new CAR{
EngineType = g.Key,
Name = g.Name,
Month0 = g.Sum(p => p.Month0),
Month1 = g.Sum(p => p.Month1),
Month2 = g.Sum(p => p.Month2),
.
.
.
.
.
.
Month148 = g.Sum(p => p.Month148),
Month149 = g.Sum(p => p.Month149)
}.ToList<CAR>();
But since its of type List<string> is there a way to get this done?
Thanks a lot!
Use the power of LINQ:
var B = A.GroupBy(x => new { x.Name, x.EngineType })
.Select(g => new Car
{
Name = g.Key.Name,
EngineType = g.Key.EngineType,
Months = g.SelectMany(x => x.Months.Select((y,i) => new { i, y = int.Parse(y) }))
.GroupBy(x => x.i)
.OrderBy(g2 => g2.Key)
.Select(g2 => g2.Sum(x => x.y).ToString()).ToList()
}).ToList();
foreach (CAR c in A)
{
bool blnadded = false;
if (B.Count == 0)
{
B.Add(c);
blnadded = true;
}
else
foreach (CAR d in B)
{
if (d.Name == c.Name && d.EngineType == c.EngineType)
{
for (int i = 0; i < d.Months.Count; i++)
d.Months[i] = (Convert.ToInt32(d.Months[i]) + Convert.ToInt32(c.Months[i])).ToString();
blnadded = true;
}
}
if (blnadded==false)
B.Add(c);
}

Categories

Resources