Related
I have a test where I have two lists created. One represents data that I have gone and collected from a source (of which I have no control over) and the other representing a known data already in my repository.
They look like this:
var newAccounts = new[]
{
new Account
{
Id = 1,
SA_ID = 1,
SA_Name = "Sa_Name1",
RelationshipType = "new",
LE_ID = 1,
LE_GroupID = 1,
SiteID = 1,
MinDate = DateTime.Now.AddDays(-1),
MaxDate = DateTime.Now.AddDays(1),
DaysOn = 1,
Child1 = new List<Child1>{ new Child1
{
SiteID = 1,
MaxDate = DateTime.Today.AddDays(7),
MinDate = DateTime.Today.AddDays(1),
}
},
Child2 = new List<Child2>
{
new Child2
{
SA_ID = 1,
LastUpdate = DateTime.Now.AddDays(-1),
CommentText = "Account added",
Status = AccountStatus.AccountAdded.ToString(),
}
}
},
new Account
{
Id = 2,
SA_ID = 2,
SA_Name = "Sa_Name2",
RelationshipType = "new",
LE_ID = 2,
LE_GroupID = 2,
SiteID = 2,
MinDate = DateTime.Now.AddDays(-2),
MaxDate = DateTime.Now.AddDays(2),
DaysOn = 2,
},
new Account
{
Id = 3,
SA_ID = 3,
SA_Name = "Sa_Name3",
RelationshipType = "new",
LE_ID = 3,
LE_GroupID = 3,
SiteID = 3,
MinDate = DateTime.Now.AddDays(-3),
MaxDate = DateTime.Now.AddDays(3),
DaysOn = 3,
}
};
var knownAccounts = new[]
{
new Account
{
Id = 1,
SA_ID = 1,
SA_Name = "Sa_Name1",
RelationshipType = "new",
LE_ID = 1,
LE_GroupID = 1,
SiteID = 1,
MinDate = DateTime.Now.AddDays(-1),
MaxDate = DateTime.Now.AddDays(1),
DaysOn = 1,
Child1 = new List<Child1>{ new Child1
{
SiteID = 1,
MaxDate = DateTime.Today.AddDays(7),
MinDate = DateTime.Today.AddDays(1),
}
},
Child2 = new List<Child2>
{
new Child2
{
SA_ID = 1,
LastUpdate = DateTime.Now.AddDays(-1),
CommentText = "Account added",
Status = AccountStatus.AccountAdded.ToString(),
}
}
}
};
In my unit tests I am wanting to strip out Account ID 1 from newAccounts so I'm only left with 2 entries in my collection. These are my attempts thus far:
public List<T> ReturnUniqueEntriesList<T>(List<T> newAccounts, List<T> knownAccounts)
{
var a = knownAccounts.Intersect(newAccounts).ToList();
var listA = newAccounts.Except(knownAccounts).ToList();
var listB = knownAccounts.Except(newAccounts).ToList();
var result = listB.Intersect(listA).ToList();
return result;
}
When I run this the final result is 0. a also returns 0 and listA & listB simply return their respective objects.
What is it that I'm doing wrong / missing here? Any help would be appreciated
Override Equals and GetHashcode for Account so that they don't rely on the default implementations (memory address of the object). This means the C# will be able to equate them properly when executing an Except.
For example:
public class Account{
public override bool Equals(object other){
return other is Account a && a.Id == this.Id; //nb; is returns false if other is a null, even if it is an Account
}
public override int GetHashCode(){
return Id.GetHashCode();
}
}
As it is, the following two accounts are very different:
var a = new Account { Id = 1 };
var b = new Account { Id = 1 };
..because they live at different memory addresses.
By overriding Equals so that it compares the Id of the other, regardless the other properties, you can then essentially realize the situation you seem to describe of "two account objects with the same ID are equivalent"
If other properties factor into the decision, add those too. Hashcode.Combine is a useful method to combine several hashcodes for the puzzle of getting multiple properties' hashcodes to produce a suitable new signle hashcode - https://learn.microsoft.com/en-us/dotnet/api/system.hashcode.combine?view=net-5.0
I'm trying to merge two arrays and sum the values having the same keys. Is it possible to do so?
public struct BassoValues
{
public int BassoId { get; set; }
public decimal Amount { get; set; }
public BassoValues(int bassoId, decimal amount)
{
BassoId = bassoId;
Amount = amount;
}
}
var arrayOne = new BassoValues[4]
arrayOne[0] = new BassoValues() { BassoId = 1, Amount = 1};
arrayOne[1] = new BassoValues() { BassoId = 2, Amount = 10};
arrayOne[2] = new BassoValues() { BassoId = 3, Amount = 20};
arrayOne[3] = new BassoValues() { BassoId = 4, Amount = 30};
var arrayTwo = new BassoValues[4]
arrayTwo[0] = new BassoValues() { BassoId = 1, Amount = 1};
arrayTwo[1] = new BassoValues() { BassoId = 2, Amount = 10};
arrayTwo[2] = new BassoValues() { BassoId = 3, Amount = 20};
arrayTwo[3] = new BassoValues() { BassoId = 4, Amount = 30};
I want to achieve the following result.
var arrayFinal = new BassoValues[4]
arrayFinal[0] = new BassoValues() { BassoId = 1, Amount = 2};
arrayFinal[1] = new BassoValues() { BassoId = 2, Amount = 20};
arrayFinal[2] = new BassoValues() { BassoId = 3, Amount = 40};
arrayFinal[3] = new BassoValues() { BassoId = 4, Amount = 60};
This is how I am trying to achieve the result:
for (int i = 0; i < arrayOne.Length; i++)
{
for (int j = 0; j < arrayTwo.Length; j++)
{
if (arrayOne[0].BassoId == arrayTwo[0].BassoId)
{
var bassoId = arrayOne[0].BassoId;
var sum = arrayOne[0].Amount + arrayTwo[0].Amount;
arrayFinal[0] = new BassoValues() { bassoId, sum};
}
}
}
It'll work in cases when some ids aren't contained in both arrays and if ids can repeat inside one array as well.
var result = arrayOne.Concat(arrayTwo).GroupBy(x => x.BassoId)
.Select(x => new BassoValues(x.Key, x.Sum(y => y.Amount)))
.ToArray();
One solution would be to join the arrays using the Id:
var sumarray = (from a1 in arrayOne
join a2 in arrayTwo on a1.BassoId equals a2.BassoId
select new BassoValues {BassoId = a1.BassoId, Amount = a1.Amount + a2.Amount}).ToArray();
EDIT: In case that each array can contain multiple entries with the same ID and you want to sum them up then the linq-join solution will not suffice anymore. You could group by the id and calculate the sums per id in a loop:
List<BassoValues> Result = new List<BassoValues>();
foreach (var element in arrayOne.GroupBy(x => x.BassoId))
{
BassoValues temp = new BassoValues {BassoId = element.Key};
temp.Amount = arrayTwo.Where(x => x.BassoId == temp.BassoId).Sum(x => x.Amount) + element.Sum(x => x.Amount);
Result.Add(temp);
}
You say your arrays' sizes are fixed to 4.
var arrayFinal = new BassoValues[4]; // create final array
// loop each array
for (int i = 0; i < 4; i++)
{
int amount = arrayOne[i].Amount + arrayTwo[i].Amount;
arrayFinal[i] = new BassoValues() { BassoId = (i+1), Amount = amount };
}
Found this Post and it has good solution when shuffling items in List<T>.
But in my case i have a class Person which is defined as this:
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string Position { get; set; }
}
This is my implementation and usage:
List<Person> workers = new List<Person>()
{
new Person { Id = 1, Name = "Emp 1", Position = "Cashier"},
new Person { Id = 2, Name = "Emp 2", Position = "Sales Clerk"},
new Person { Id = 3, Name = "Emp 3", Position = "Cashier"},
new Person { Id = 4, Name = "Emp 4", Position = "Sales Clerk"},
new Person { Id = 5, Name = "Emp 5", Position = "Sales Clerk"},
new Person { Id = 6, Name = "Emp 6", Position = "Cashier"},
new Person { Id = 7, Name = "Emp 7", Position = "Sales Clerk"}
};
Now i want to shuffle all records and get 1 Sales Clerk. Here is my code and is working:
var worker = workers.OrderBy(x => Guid.NewGuid()).Where(x => x.Position == "Sales Clerk").First();
// This can yield 1 of this random result (Emp 2, Emp 4, Emp 5 and Emp 7).
Console.WriteLine(worker.Name);
But according to the given Post GUID is not good for randomizing record. And the worst is i cant use Shuffle() and call the Where and First() extensions to get the desired result.
How can i do that with Shuffle() extension?
If the question is how to get it so you can chain Shuffle() with the rest of your Linq operators, the answer is to modify the Shuffle method to return reference to the list shuffled:
public static IEnumerable<T> Shuffle<T>(this IList<T> list)
{
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
byte[] box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
int k = (box[0] % n);
n--;
T value = list[k];
list[k] = list[n];
list[n] = value;
}
return list;
}
Your code then becomes:
var worker = workers.Shuffle().Where(x => x.Position == "Sales Clerk").First();
Random oRandom = new Random();
var worker = workers[oRandom.Next(0,workers.Count)];
I am trying to fill a tree with more than one parameter (points and types) and at the end, show which "branch" has the max of points and in every branch, show how many equal tupe I have.
The tree will be something like this:
FATHER (Points:200|Type:2)
|_CHILD01 (P:120|Type:3)
| |_CHILD4 (P:300|T:3)
| | |_CHILD8 (P:220|T:3)
| | |_CHILD9 (P:65|T:1)
| |_CHILD5 (P:15|T:9)
|_CHILD2 (P:10|T:1)
|_CHILD3 (P:80|T:2)
|_CHILD6 (P:25|T:2)
| |_CHILD10 (P:110|T:7)
| |_CHILD11 (P:195|T:3)
|_CHILD7 (P:50|T:7)
and what I am trying to get is:
NUMBER OF POINTS PER BRANCH:
Branch01 -> FATHER (200), CHILD01 (120), CHILD04 (300), CHILD08 (220) -> TotalPoints: 840
Branch02 -> FATHER (200), CHILD01 (120), CHILD04 (300), CHILD09 (65) -> TotalPoints: 685
Branch03 -> FATHER (200), CHILD01 (120), CHILD05 (15) -> TotalPoints: 335
Branch04 -> FATHER (200), CHILD02 (10) -> TotalPoints: 210
Branch05 -> FATHER (200), CHILD03 (80), CHILD06 (25), CHILD10 (110) -> TotalPoints: 415
Branch06 -> FATHER (200), CHILD03 (80), CHILD06 (25), CHILD11 (195) -> TotalPoints: 500
Branch07 -> FATHER (200), CHILD03 (80), CHILD07 (50) -> TotalPoints: 330
and
COUNT THE NUMBER OF TYPES in WHICH BRANCH:
TypePerBranch01:
- Type1:0
- Type2:1
- Type3:2
- Type4:1
- Type5:0
- Type6:0
- Type7:0
- Type8:0
- Type9:0
TypePerBranch02:
- Type1:1
- Type2:1
- Type3:1
- Type4:1
- Type5:0
- Type6:0
- Type7:0
- Type8:0
- Type9:0
TypePerBranch03:
- Type1:0
- Type2:1
- Type3:1
- Type4:0
- Type5:0
- Type6:0
- Type7:0
- Type8:0
- Type9:1
TypePerBranch04:
- Type1:1
- Type2:1
- Type3:0
- Type4:0
- Type5:0
- Type6:0
- Type7:0
- Type8:0
- Type9:0
TypePerBranch05:
- Type1:0
- Type2:3
- Type3:0
- Type4:0
- Type5:0
- Type6:0
- Type7:1
- Type8:0
- Type9:0
TypePerBranch06:
- Type1:0
- Type2:3
- Type3:1
- Type4:0
- Type5:0
- Type6:0
- Type7:0
- Type8:0
- Type9:0
TypePerBranch07:
- Type1:0
- Type2:2
- Type3:0
- Type4:0
- Type5:0
- Type6:0
- Type7:1
- Type8:0
- Type9:0
I have done some code but its not working.
Here´s the function:
//
// FUNÇÃO ResizeArray
public T[,] ResizeArray<T>(T[,] original, int xSize, int ySize)
{
var newArray = new T[xSize, ySize];
var xMin = Math.Min(xSize, original.GetLength(0));
var yMin = Math.Min(ySize, original.GetLength(1));
for (var x = 0; x < xMin; x++)
for (var y = 0; y < yMin; y++)
newArray[x, y] = original[x, y];
return newArray;
}
//
// FUNÇÃO TreeBranchPath
int[] TotalPontosRamo = new int[1];
int[,] FolhaInfoPontos = new int[1, 1];
int[,] FolhaInfoPatamar = new int[1, 1];
int CountRamos = 0;
private void TreeBranchPath(int idusr, int usrpnts, int usrpata, int nivelnum, int ramonum)
{
FolhaInfoPontos[nivelnum, ramonum] = usrpnts;
FolhaInfoPatamar[nivelnum, ramonum] = usrpata;
var AfilhadosList = (from af in db.NRV_USERS
where af.idpatrocinador == idusr
select af).ToList();
/*Se for NULL não tem descendentes */
if (AfilhadosList != null)
{
int CountNumFilhos = AfilhadosList.Count();
int CountFilhos = 0;
nivelnum = nivelnum + 1;
FolhaInfoPontos = ResizeArray(FolhaInfoPontos, nivelnum, ramonum + CountNumFilhos);
FolhaInfoPatamar = ResizeArray(FolhaInfoPatamar, nivelnum, ramonum + CountNumFilhos);
foreach (var descid in AfilhadosList)
{
CountFilhos = CountFilhos + 1;
/* Inicio - Quantos Pontos o User tem */
var UserPoints = (from pnt in db.NRV_USERPONTOS
where pnt.iduser == descid.id_user && pnt.usrpntact == true
select pnt).FirstOrDefault();
int TotalUserPoints = UserPoints.pontosgrupo + UserPoints.pontosproprios;
/* Fim - Quantos Pontos o User tem */
TotalPontosRamo[CountRamos] = TotalPontosRamo[CountRamos] + TotalUserPoints;
/* Inicio - Em que Patamar o User está */
var AuxUserPatamar = (from cp in db.NRV_USERPATAMAR
where cp.iduser == idusr
select cp.idpatamax).FirstOrDefault();
/* Fim - Em que Patamar o User está */
Array.Resize(ref TotalPontosRamo, CountRamos + 1);
TreeBranchPath(descid.id_user, TotalUserPoints, AuxUserPatamar, nivelnum, CountFilhos);
}
}
else
{
return;
}
}
Can someone help me?
Here I indented the results based on the level of the Node.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Node father = new Node()
{
name = "FATHER", points = 200, type = 2, children = new List<Node>(){
new Node() {
name = "CHILD01", points = 120, type = 3, children = new List<Node>(){
new Node(){
name = "CHILD04", points = 300, type = 3, children = new List<Node>(){
new Node(){ name = "CHILD08", points = 220, type = 3, children = null},
new Node(){ name = "CHILD09", points = 65, type = 1, children = null}
}
},
new Node(){ name = "CHILD05", points = 15, type = 9, children = null}
}
},
new Node() { name = "CHILD02", points = 10, type = 1, children = null},
new Node(){
name = "CHILD03", points = 80, type = 2, children = new List<Node>(){
new Node(){
name = "CHILD06", points = 25, type = 2, children = new List<Node>(){
new Node(){ name = "CHILD10", points = 110, type = 7, children = null},
new Node(){ name = "CHILD11", points = 195, type = 3, children = null}
}
},
new Node(){ name = "CHILD07", points = 50, type = 7, children = null}
}
}
}
};
Count results = father.GetTotals(0);
foreach (Count result in Node.results)
{
Console.WriteLine("{0} Name = {1}, Points = {2}, Types = {3}",
string.Join("", Enumerable.Repeat(" ", result.level)),
result.name,
result.points,
string.Join(",", result.types.OrderBy(x => x.Key).Select(x => x.Key.ToString() + ":" + x.Value.ToString()).ToArray())
);
}
}
}
public class Count
{
public string name;
public int level { get; set; }
public int points { get; set; }
public Dictionary<int, int> types { get; set; }
}
public class Node
{
public string name { get; set; }
public int points { get; set; }
public int type { get; set; }
public List<Node> children { get; set; }
public static List<Count> results = new List<Count>();
public Count GetTotals(int level)
{
Count result = new Count();
result.name = name;
result.level = level;
result.points = points;
result.types = new Dictionary<int, int>();
result.types.Add(type, 1);
if (children != null)
{
for (int childCount = children.Count - 1; childCount >= 0; childCount--)
{
Node child = children[childCount];
Count childResutls = child.GetTotals(level + 1);
result.points += childResutls.points;
foreach (int key in childResutls.types.Keys)
{
if (result.types.ContainsKey(key))
{
result.types[key] += childResutls.types[key];
}
else
{
result.types.Add(key, childResutls.types[key]);
}
}
}
}
Node.results.Insert(0, result);
return result;
}
}
}
Here is a slightly different answer with the nodes in the same order as the input
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Node father = new Node()
{
name = "FATHER", points = 200, type = 2, children = new List<Node>(){
new Node() {
name = "CHILD01", points = 120, type = 3, children = new List<Node>(){
new Node(){
name = "CHILD04", points = 300, type = 3, children = new List<Node>(){
new Node(){ name = "CHILD08", points = 220, type = 3, children = null},
new Node(){ name = "CHILD09", points = 65, type = 1, children = null}
}
},
new Node(){ name = "CHILD05", points = 15, type = 9, children = null}
}
},
new Node() { name = "CHILD02", points = 10, type = 1, children = null},
new Node(){
name = "CHILD03", points = 80, type = 2, children = new List<Node>(){
new Node(){
name = "CHILD06", points = 25, type = 2, children = new List<Node>(){
new Node(){ name = "CHILD10", points = 110, type = 7, children = null},
new Node(){ name = "CHILD11", points = 195, type = 3, children = null}
}
},
new Node(){ name = "CHILD07", points = 50, type = 7, children = null}
}
}
}
};
Count results = father.GetTotals();
foreach (Count result in Node.results)
{
Console.WriteLine("Name = {0}, Points = {1}, Types = {2}",
result.name,
result.points,
string.Join(",", result.types.OrderBy(x => x.Key).Select(x => x.Key.ToString() + ":" + x.Value.ToString()).ToArray())
);
}
}
}
public class Count
{
public string name;
public int points { get; set; }
public Dictionary<int, int> types { get; set; }
}
public class Node
{
public string name { get; set; }
public int points { get; set; }
public int type { get; set; }
public List<Node> children { get; set; }
public static List<Count> results = new List<Count>();
public Count GetTotals()
{
Count result = new Count();
result.name = name;
result.points = points;
result.types = new Dictionary<int, int>();
result.types.Add(type, 1);
if (children != null)
{
for (int childCount = children.Count - 1; childCount >= 0; childCount--)
{
Node child = children[childCount];
Count childResutls = child.GetTotals();
result.points += childResutls.points;
foreach (int key in childResutls.types.Keys)
{
if (result.types.ContainsKey(key))
{
result.types[key] += childResutls.types[key];
}
else
{
result.types.Add(key, childResutls.types[key]);
}
}
}
}
Node.results.Insert(0, result);
return result;
}
}
}
I am trying to re-write a method using TDD.
Here is the original method without the database logic and below it is my attempt to re-write it. Its not complete as I just hit a wall and do not understand how to finish it.
I'm trying to learn and can't work it out so need some help. Thanks for reading.
//Original Method
public class StringCalculator()
{
public List<ShipmentInformation> GetBoxRange(int BoxSize, int Quantity, ref string DVDType, ref string ErrorMessage)
{
//Get Connection String
//Do Database Queries and return shipmentInfo variable as List<ShipmentInformation>
var DVDTotals = shipmentInfo.GroupBy(x => x.DVDType).Select(x => new { DVDType = x.Key, Total = x.Sum(y => (y.DVDEndID - y.DVDStartID) + 1) });
if (DVDTotals.Count() == 0 || DVDTotals.All(x => x.Total < Quantity))
{
ErrorMessage = "There is not enough data to create a shipment based on the quantity";
return new List<ShipmentInformation>();
}
//Select the one with the largest amount of stock
var LargestDVDType = DVDTotals.Aggregate((l, r) => l.Total > r.Total ? l : r).DVDType;
var LargestDVDTypeSelection = shipmentInfo.Where(x => x.DVDType == LargestDVDType);
long previousDVDStartID = 0;
//Make sure ranges are sequential
List<ShipmentInformation> SequentialBoxResults = new List<ShipmentInformation>();
foreach (var item in LargestDVDTypeSelection)
{
if (item.DVDStartID - previousDVDStartID == BoxSize || previousDVDStartID == 0)
{
SequentialBoxResults.Add(item);
}
else
{
SequentialBoxResults.Clear();
SequentialBoxResults.Add(item);
}
previousDVDStartID = item.DVDStartID;
if (BoxSize * SequentialBoxResults.Count == Quantity)
{
break;
}
}
if (SequentialBoxResults.Count == 0 || BoxSize * SequentialBoxResults.Count != Quantity)
{
ErrorMessage = "There are no available ranges to create a shipment based on the quantity";
return new List<ShipmentInformation>(); ;
}
List<ShipmentInformation> Results = new List<ShipmentInformation>();
var Genres = SequentialBoxResults.GroupBy(x => x.Genre);
foreach (var Genre in Genres)
{
var orderedGenres = Genre.OrderBy(x => x.DVDStartID);
ShipmentInformation shipment = new ShipmentInformation();
shipment.Genre = Genre.Key;
shipment.DVDStartID = orderedGenres.First().DVDStartID;
var lastItem = orderedGenres.Last();
shipment.DVDEndID = lastItem.DVDEndID;
shipment.DVDType = lastItem.DVDType;
Results.Add(shipment);
}
//We have our shipment recordsnow split them up if any are over 75000
for (int i = 0; i < Results.Count; i++)
{
long rangeSize = Results[i].DVDEndID - Results[i].DVDStartID + 1;
double noOfLoops = Math.Ceiling(rangeSize / 75000D);
long remainder = rangeSize % 75000;
if (noOfLoops > 1)
{
bool AddedRecord = false;
for (int j = 0; j < noOfLoops; j++)
{
long shipmentSize = 0;
if (j == (noOfLoops - 1))
{
shipmentSize = remainder;
if (AddedRecord)
Results.Add(new ShipmentInformation() { DVDStartID = Results.Last().DVDEndID + 1, DVDEndID = Results.Last().DVDEndID + 1 + (shipmentSize - 1), Genre = Results.Last().Genre });
else
Results.Add(new ShipmentInformation() { DVDStartID = Results[i].DVDEndID + 1, DVDEndID = Results[i].DVDEndID + 1 + (shipmentSize - 1), Genre = Results[i].Genre });
}
else
{
shipmentSize = 75000;
if (j == 0)
{
Results[i].DVDEndID = Results[i].DVDStartID + (shipmentSize - 1);
}
else if (j == 1)
{
Results.Add(new ShipmentInformation() { DVDStartID = Results[i].DVDEndID + 1, DVDEndID = Results[i].DVDEndID + 1 + (shipmentSize - 1), Genre = Results[i].Genre });
AddedRecord = true;
}
else
{
Results.Add(new ShipmentInformation() { DVDStartID = Results.Last().DVDEndID + 1, DVDEndID = Results.Last().DVDEndID + 1 + (shipmentSize - 1), Genre = Results.Last().Genre });
AddedRecord = true;
}
}
}
}
}
return Results;
}
}
//New Method with Tests
public List<ShipmentInformation> GetBoxRange(int BoxSize, int Quantity, DateTime CutOffDate, IDataProvider Provider, ref string DVDType, ref string ErrorMessage)
{
if (BoxSize == 0)
{
ErrorMessage = "Please enter a BoxSize";
}
if (Quantity == 0)
{
ErrorMessage = "Please enter a Quantity";
}
if (!String.IsNullOrWhiteSpace(ErrorMessage))
{
return new List<ShipmentInformation>();
}
List<ShipmentInformation> Data = Provider.GetData();
if (Data.Count == 0)
{
ErrorMessage = "Database failed to return data";
return new List<ShipmentInformation>();
}
List<ShipmentInformation> OrderedData = GetSequentialBoxes(Data, BoxSize, Quantity);
if (OrderedData.Count == 0)
{
ErrorMessage = "No sequential data found";
return new List<ShipmentInformation>();
}
//I'm not sure how to continue from here - I started writing GetRecordsPerGenre but got lost in what I'm trying to do. I still need to check if size is over 75000
return OrderedData;
}
public virtual List<ShipmentInformation> GetSequentialBoxes(List<ShipmentInformation> Data, int BoxSize, int Quantity)
{
if (Data.Count == 0)
return new List<ShipmentInformation>();
var orderedData = Data.OrderBy(x => x.DVDStartID);
if (!orderedData.SequenceEqual(Data))
return new List<ShipmentInformation>();
var DVDTotals = Data.GroupBy(x => x.DVDType).Select(x => new { DVDType = x.Key, Total = x.Sum(y => (y.DVDEndID - y.DVDStartID) + 1) });
if (DVDTotals.Count() == 0 || DVDTotals.All(x => x.Total < Quantity))
{
return new List<ShipmentInformation>();
}
var LargestDVDType = DVDTotals.Aggregate((l, r) => l.Total > r.Total ? l : r).DVDType;
Data = Data.Where(x => x.DVDType == LargestDVDType).ToList();
List<ShipmentInformation> returnData = new List<ShipmentInformation>();
long previousDVDStartID = 0;
foreach (var item in Data)
{
if (previousDVDStartID == 0 || item.DVDStartID - previousDVDStartID == BoxSize)
{
returnData.Add(item);
}
else
{
returnData.Clear();
returnData.Add(item);
}
previousDVDStartID = item.DVDStartID;
if (returnData.Count * BoxSize == Quantity)
break;
}
return returnData.OrderBy(x => x.DVDStartID).ToList();
}
public List<ShipmentInformation> GetRecordsPerGenre(List<ShipmentInformation> Data)
{
List<ShipmentInformation> Results = new List<ShipmentInformation>();
var Genres = Data.GroupBy(x => x.Genre);
foreach (var Genre in Genres)
{
var orderedGenres = Genre.OrderBy(x => x.DVDStartID);
ShipmentInformation shipment = new ShipmentInformation();
shipment.Genre = Genre.Key;
shipment.DVDStartID = orderedGenres.First().DVDStartID;
var lastItem = orderedGenres.Last();
shipment.DVDEndID = lastItem.DVDEndID;
shipment.DVDType = lastItem.DVDType;
Results.Add(shipment);
}
return Results;
}
//Tests
[TestFixture]
public class GetBoxRangeMethod
{
private StringCalculator CreateNewCalculator()
{
return new StringCalculator();
}
[TestCase(0, 1)]
[TestCase(1, 0)]
public void ZeroValuesForBoxSizeOrQuantity_ReturnsErrorAndEmptyList(int BoxSize, int Quantity)
{
StringCalculator sc = CreateNewCalculator();
string ErrorMessage = "";
string ChipType = "";
FakeDataProvier provider = new FakeDataProvier(new List<ShipmentInformation>());
List<ShipmentInformation> result = sc.GetBoxRange(BoxSize, Quantity, new DateTime(2012, 01, 01), "A", provider, ref ChipType, ref ErrorMessage);
Assert.AreNotEqual("", ErrorMessage);
Assert.AreEqual(0, result.Count);
}
[Test]
public void EmptyBookTypeString_ReturnsErrorAndEmptyList()
{
StringCalculator sc = CreateNewCalculator();
string ErrorMessage = "";
string ChipType = "";
FakeDataProvier provider = new FakeDataProvier(new List<ShipmentInformation>());
List<ShipmentInformation> result = sc.GetBoxRange(1, 1, new DateTime(2012, 01, 01), "", provider, ref ChipType, ref ErrorMessage);
Assert.AreNotEqual("", ErrorMessage);
Assert.AreEqual(0, result.Count);
}
[Test]
public void EmptyDBData_ReturnsErrorAndEmptyList()
{
StringCalculator sc = CreateNewCalculator();
string ErrorMessage = "";
string ChipType = "";
FakeDataProvier provider = new FakeDataProvier(new List<ShipmentInformation>());
List<ShipmentInformation> result = sc.GetBoxRange(1, 1, new DateTime(2012, 01, 01), "A", provider, ref ChipType, ref ErrorMessage);
Assert.AreNotEqual("", ErrorMessage);
Assert.AreEqual(0, result.Count);
}
[Test]
public void EmptyOrderedData_ReturnsErrorAndEmptyList()
{
FakeShipmentCalculator sc = new FakeShipmentCalculator();
string ErrorMessage = "";
string ChipType = "";
FakeDataProvier provider = new FakeDataProvier(new List<ShipmentInformation>() { new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" } });
List<ShipmentInformation> result = sc.GetBoxRange(1, 1, new DateTime(2012, 01, 01), "A", provider, ref ChipType, ref ErrorMessage);
Assert.AreNotEqual("", ErrorMessage);
Assert.AreEqual(0, result.Count);
}
}
//Integration test
[TestFixture]
public class GetDataMethod
{
}
[TestFixture]
public class GetSequentialBoxes
{
private StringCalculator CreateNewCalculator()
{
return new StringCalculator();
}
[Test]
public void MethodCalled_DoesntReturnNull()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> result = sc.GetSequentialBoxes(new List<ShipmentInformation>(), 100, 1);
Assert.IsNotNull(result);
}
[Test]
public void EmptyDataPassedIn_ReturnsEmptyData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> result = sc.GetSequentialBoxes(new List<ShipmentInformation>(), 100, 1);
Assert.AreEqual(0, result.Count);
}
[Test]
public void OrderedData_ReturnsOrderedData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, 2, 1);
ShipmentInformation firstItem = result.FirstOrDefault();
ShipmentInformation secondItem = result.LastOrDefault();
Assert.IsTrue(firstItem.StartPP == 1 && secondItem.StartPP == 3);
}
[Test]
public void UnOrderedData_ReturnsEmptyData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
int NUMBER_GREATER_THAN_ONE = 2;
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, NUMBER_GREATER_THAN_ONE, 1);
Assert.AreEqual(0, result.Count);
}
[Test]
public void SequenceJumps_ClearsListAndStartsAgain()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 5, EndPP = 6, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, 2, 1);
Assert.IsTrue(result.First().StartPP == 5);
}
[Test]
public void LargestNumberOfItemsWithSameChipType_ReturnsDataWithOnlyThatChipType()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "B", StartPP = 5, EndPP = 6, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
int BoxSizeSlashSequenceJumpAllowed = 2;
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, BoxSizeSlashSequenceJumpAllowed, 1);
Assert.IsTrue(result.All(x => x.ChipType == "A"));
}
[Test]
public void TotalNumberOfRecordsPerChipTypeLessThanQuantity_ReturnsEmptyData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "B", StartPP = 5, EndPP = 6, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
int BoxSizeSlashSequenceJumpAllowed = 2;
int Quantity = 5;
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, BoxSizeSlashSequenceJumpAllowed, Quantity);
Assert.AreEqual(0, result.Count);
}
[Test]
public void DataReturned_WhenQuantityReached()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation() { ChipType = "A", StartPP = 1, EndPP = 2, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 3, EndPP = 4, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
data = new ShipmentInformation() { ChipType = "A", StartPP = 5, EndPP = 6, JacketNo = "A", ReqNo = "B" };
inputData.Add(data);
int BoxSizeSlashSequenceJumpAllowed = 2;
int Quantity = 4;
List<ShipmentInformation> result = sc.GetSequentialBoxes(inputData, BoxSizeSlashSequenceJumpAllowed, Quantity);
Assert.AreEqual(2, result.Count);
}
}
[TestFixture]
public class GetRecordsPerGenre
{
private StringCalculator CreateNewCalculator()
{
return new StringCalculator();
}
[Test]
public void MethodCalled_DoesntReturnNull()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> result = sc.GetRecordsPerSerialRange(new List<ShipmentInformation>());
Assert.IsNotNull(result);
}
[Test]
public void EmptyDataPassedIn_ReturnsEmptyData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> result = sc.GetRecordsPerSerialRange(new List<ShipmentInformation>());
Assert.AreEqual(0, result.Count);
}
[Test]
public void Data_ReturnsGroupedByData()
{
StringCalculator sc = CreateNewCalculator();
List<ShipmentInformation> inputData = new List<ShipmentInformation>();
ShipmentInformation data = new ShipmentInformation();
data.ChipType = "A";
data.ReqNo = "B";
data.JacketNo="C";
data.StartPP = 1;
data.EndPP = 2;
inputData.Add(data);
data = new ShipmentInformation();
data.ChipType = "A";
data.ReqNo = "B";
data.JacketNo = "C";
data.StartPP = 1;
data.EndPP = 2;
inputData.Add(data);
List<ShipmentInformation> result = sc.GetRecordsPerSerialRange(inputData);
Assert.AreEqual(1, result.Count);
}
}
At Jon, you may be biting off too much for a noob. implement TDD with a new feature, not an existing one. With this scenario, not only are you introducing testing, TDD, mocking. but now you have also introduced refactoring (yet another new concept).
once you have the the basics of TDD on greenfield code, you can then apply it brownfield code along with refactoring.
one last note: TDD doesn't require mocking, mocking is a separate concept that plays well with TDD, but it's not required. TDD, simply put: is driving out design through tests.
In order to unit test effectively you should adhere to some design principles. Loose coupling, Separation of concerns and Dependency Injection. The reason for this is, without, you will not be able to use mocks in your tests (which is what you will need to do)
I noticed in the first method in you above code you had:
//Get Connection String
//Do Database Queries and return shipmentInfo variable as List<ShipmentInformation>
I presume from this that you actually instantiate everything you need here to query the DB. This is not best practice and probably the root of your problems. You should be depending on abstractions and not concretions. A instance of a 'repository' interface (or similar) should be passed using Constructor injection.
A good book to get you started would be this: http://www.amazon.co.uk/Professional-ASP-NET-Design-Patterns-Millett/dp/0470292784
Hope this helps.