Can someone help me ordering this list? - c#

I am not the best programmer, so need some help to order this list. I had a few stabs at it, but still getting some cases which are wrong.
Essentially the list is the following:
#, ID, PreceedingID
A, 1 , 0
B, 2 , 3
C, 3 , 1
D, 4 , 2
I want to order it so that the list follows the preceeding id. The first item will always have the preceeding ID of 0.
#, ID, PreceedingID
A, 1 , 0
C, 3 , 1
B, 2 , 3
D, 4 , 2
Do you think you can help?
Thanks!

How about:
var data = new[] {
new Row{ Name = "A", ID = 1, PreceedingID = 0},
new Row{ Name = "B", ID = 2, PreceedingID = 3},
new Row{ Name = "C", ID = 3, PreceedingID = 1},
new Row{ Name = "D", ID = 4, PreceedingID = 2},
};
var byLastId = data.ToDictionary(x => x.PreceedingID);
var newList = new List<Row>(data.Length);
int lastId = 0;
Row next;
while (byLastId.TryGetValue(lastId, out next))
{
byLastId.Remove(lastId); // removal avoids infinite loops
newList.Add(next);
lastId = next.ID;
}
After this, newList has the data in the desired order.
In the above, Row is:
class Row
{
public string Name { get; set; }
public int ID { get; set; }
public int PreceedingID { get; set; }
}
But obviously substitute for your own type.

You can use for example dictionary to sort it:
Dictionary<..> d = new Dictionary<..>()
foreach(var el in list){
d[el.PreceedingID] = el; //put data to dict by PreecedingID
}
List<..> result = new List<..>();
int prec = 0; //get first ID
for(int i = 0; i < list.Length; ++i){
var actEl = d[prec]; //get next element
prec = actEl.ID; //change prec id
result.Add(actEl); //put element into result list
}

Related

Cartesian Product of an arbitrary number of objects [duplicate]

This question already has answers here:
Is there a good LINQ way to do a cartesian product?
(3 answers)
Closed 4 years ago.
I'm looking to get the Cartesian Product of an arbitrary number of objects in c#. My situation is slightly unusual - my inputs are not lists of base types, but objects which have a property that's a list of base types.
My input and output objects are as follows:
public class Input
{
public string Label;
public List<int> Ids;
}
public class Result
{
public string Label;
public int Id;
}
Some sample input data:
var inputs = new List<Input>
{
new Input { Label = "List1", Ids = new List<int>{ 1, 2 } },
new Input { Label = "List2", Ids = new List<int>{ 2, 3 } },
new Input { Label = "List3", Ids = new List<int>{ 4 } }
};
And my expected output object:
var expectedResult = new List<List<Result>>
{
new List<Result>
{
new Result{Label = "List1", Id = 1},
new Result{Label = "List2", Id = 2},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 1},
new Result{Label = "List2", Id = 3},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 2},
new Result{Label = "List2", Id = 2},
new Result{Label = "List3", Id = 4}
},
new List<Result>
{
new Result{Label = "List1", Id = 2},
new Result{Label = "List2", Id = 3},
new Result{Label = "List3", Id = 4}
}
};
If I knew the number of items in 'inputs' in advance I could do this:
var knownInputResult =
from id1 in inputs[0].Ids
from id2 in inputs[1].Ids
from id3 in inputs[2].Ids
select
new List<Result>
{
new Result { Id = id1, Label = inputs[0].Label },
new Result { Id = id2, Label = inputs[1].Label },
new Result { Id = id3, Label = inputs[2].Label },
};
I'm struggling to adapt this to an arbitrary number of inputs - is there a possible way to do this?
I consider this duplicate of question linked in comments, but since it was reopened and you struggle to adapt that question to your case, here is how.
First grab function by Eric Lippert from duplicate question as is (how it works is explained there):
public static class Extensions {
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
return sequences.Aggregate(
emptyProduct,
(accumulator, sequence) =>
from accseq in accumulator
from item in sequence
select accseq.Concat(new[] { item })
);
}
}
Then flatten your input. Basically just attach corresponding label to each id:
var flatten = inputs.Select(c => c.Ids.Select(r => new Result {Label = c.Label, Id = r}));
Then run cartesian product and done:
// your expected result
var result = flatten.CartesianProduct().Select(r => r.ToList()).ToList();
I'm not proud of the amount of time I spent messing with this, but it works.
It's basically black magic, and I would replace it the first chance you get.
public static List<List<Result>> Permutate(IEnumerable<Input> inputs)
{
List<List<Result>> results = new List<List<Result>>();
var size = inputs.Select(inp => factorial_WhileLoop(inp.Ids.Count)).Aggregate((item, carry) => item + carry) - 1;
for (int i = 0; i < size; i++) results.Add(new List<Result>());
foreach (var input in inputs)
{
for (int j = 0; j < input.Ids.Count; j++)
{
for (int i = 0; i < (size / input.Ids.Count); i++)
{
var x = new Result() { Label = input.Label, Id = input.Ids[j] };
results[(input.Ids.Count * i) + j].Add(x);
}
}
}
return results;
}
public static int factorial_WhileLoop(int number)
{
var result = 1;
while (number != 1)
{
result = result * number;
number = number - 1;
}
return result;
}

Getting inappropriate rank while merging and arranging 2 list

Below is my models:
public class Test
{
public int TestId { get; set; }
public List<VariantsRank> VariantsRanks { get; set; }
}
public class VariantsRank
{
public int VariantId { get; set; }
public string Name { get; set; }
public int Rank { get; set; }
}
I have an existing Test instance which contains the following values for VariantsRanks
VariantId = 10, Name = "V1", Rank = 0
VariantId = 11, Name = "V2", Rank = 1
I then need to merge the following VariantsRank
VariantId = 12, Name = "V3", Rank = 0
VariantId = 13, Name = "V4", Rank = 1
and increment the Rank to produce the following output
VariantId = 10, Name = "V1", Rank = 0
VariantId = 11, Name = "V2", Rank = 1
VariantId = 12, Name = "V3", Rank = 2
VariantId = 13, Name = "V4", Rank = 3
and I use the following code which works correctly (List1 is the original list, and List2 is the list to be merged)
int highestOrder = (List1.VariantsRanks.Max(cpo => cpo.Rank)) + 1;
foreach (var rank in List2.VariantsRanks)
{
var match = List1.VariantsRanks.FirstOrDefault(x => x.VariantId == rank.VariantId);
if (match != null) // found
{
match.Rank = rank.Rank;
}
else
{
rank.Rank = highestOrder;
highestOrder = highestOrder + 1;
List1.VariantsRanks.Add(rank);
}
}
I then need to merge the following VariantsRank to the new list (note the matching VariantId values, but they are in reverse order)
VariantId = 13, Name = "V4", Rank = 0
VariantId = 12, Name = "V3", Rank = 1
so that the output should be
VariantId = 10, Name = "V1", Rank = 0
VariantId = 11, Name = "V2", Rank = 1
VariantId = 13, Name = "V4", Rank = 2
VariantId = 12, Name = "V3", Rank = 3
however the above code instead outputs
VariantId = 10, Name = "V1", Rank = 0
VariantId = 11, Name = "V2", Rank = 1
VariantId = 12, Name = "V3", Rank = 1
VariantId = 13, Name = "V4", Rank = 0
and the Rank values are not incremented correctly
How do I modify the code to ensure that duplicate VariantId are not added, but increment the Rank?
You issue is that in the 2 merge, your adding items with values that match the VariantId in the existing list. This means that you hit the code in the if block, which resets the values of the existing items to the value of the Rank in the model your posting.
For example, in the first iteration of your loop, match is the existing item with VariantID = 13 and your set its Rank to equal the value of rank.Rank which is 0.
You need to first remove any matches from your existing list, and then iterate through the posted values, update their Rank and add to the collection.
You code should be
// Get the VariantId values of the list to be merged
var ids = List2.VariantsRanks.Select(x => x.VariantId);
// Remove any matches from the existing list
List1.VariantsRanks.RemoveAll(x => ids.Contains(x.VariantId));
// Calculate the current highest rank
int highestOrder = (List1.VariantsRanks.Max(x => x.Rank));
foreach (var rank in List2.VariantsRanks)
{
// Update the rank
rank.Rank = ++highestOrder; // pre-increment
// Add to the existing list
List1.VariantsRanks.Add(rank);
}
Based on comments in chat that the the second list might contain items that need to be inserted in the middle of the first list, then the code would need to be
// Get the VariantId's of the first and last items in the list to be merged
var firstID = List2.VariantsRanks.First().VariantId;
var lastID = List2.VariantsRanks.Last().VariantId;
// Get the indexers of those items in the original list
var firstIndex = List1.VariantsRanks.FindIndex(x => x.VariantId == firstID);
var lastIndex = List1.VariantsRanks.FindIndex(x => x.VariantId == lastID);
if (firstIndex > lastIndex) // in case they are in descending order
{
var temp = lastIndex;
lastIndex = firstIndex;
firstIndex = temp;
}
// Remove matches from the original list
for (int i = firstIndex; i < lastIndex + 1; i++)
{
List1.VariantsRanks.RemoveAt(firstIndex);
}
// Inset the items from the list to be merged
for(int i = 0; i < List2.VariantsRanks.Count; i++)
{
List1.VariantsRanks.Insert(firstIndex + i, List2.VariantsRanks[i]);
}
/ Re-number the Rank
for(int i = 0; i < List1.VariantsRanks.Count; i++)
{
List1.VariantsRanks[i].Rank = i;
}
Note, the above will only work if the values of VariantId in the merged list are consecutive (in either ascending or descending order)

merge 2 complex lists in c#

I am programming in silverlight (c# .net)
lets say I have a list of type "data"
public class data
{
public string QUOTE_ID { get; set; }
public string EVENT_ACTION_CD { get; set; }
public string CUSTOMER_NAME { get; set; }
public string ADAPTIV_CODE { get; set; }
}
the problem is some of the data comes from 1 database and the other data comes from another, so right now i get the data in 2 steps - so i have something like this (using random numbers):
input1 = new List<data> //data return from database 1
//(the data is actually returned as a datable which i convert to a list
//to put to a datagrid, but the end result is shown below)
{
new data { QUOTE_ID = "1", EVENT_ACTION_CD = "2"},
new Project { QUOTE_ID = "2", EVENT_ACTION_CD = "4"},
new Project { QUOTE_ID = "3", EVENT_ACTION_CD = "5"}
};
input2 = new List<data> //data return from database 2
{
new data { QUOTE_ID = "1", CUSTOMER_NAME = "2", ADAPTIV_CODE ="5"},
new Project { QUOTE_ID = "2", CUSTOMER_NAME = "4", ADAPTIV_CODE = "5"},
new Project { QUOTE_ID = "3", CUSTOMER_NAME = "5", ADAPTIV_CODE = "7"}
};
so i should have 2 lists like
input1:
(1, 2, null, null
2, 4, null, null
3, 5, null, null)
and
input2:
(1, null, 2, 5
2, null, 4, 5
3. null, 5, 7)
how do i join them together to form one input list to become
(1, 2, 2, 5
2, 4, 4, 5
3, 5, 5, 7)
Use linq with a join operator.
See http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
var resultList = (from item in input1
join item2 in input2 on item2.QUOTE_ID equals input2.QUOTE_ID
let item.CUSTOMER_NAME = item2.CUSTOMER_NAME
let item.ADAPTIV_CODE = item2.ADAPTIV_CODE
select item).ToList();
A normal for loop would work for you:
for(int i = 0; i < input1.Count; i++){
if(input1[i].QUOTE_ID == null) input1[i].QUOTE_ID = input2[i].QUOTE_ID;
if(input1[i].EVENT_ACTION_CD == null) input1[i].EVENT_ACTION_CD = input2[i].EVENT_ACTION_CD;
if(input1[i].CUSTOMER_NAME == null) input1[i].CUSTOMER_NAME = input2[i].CUSTOMER_NAME;
if(input1[i].ADAPTIV_CODE == null) input1[i].ADAPTIV_CODE = input2[i].ADAPTIV_CODE;
}
The result will be saved into the input1. The code also supposes input1 and input2 have the same Count.
var input3 = input1.Join(
input2,
d1 => d1.QUOTE_ID,
d2 => d2.QUOTE_ID,
(d1, d2) => new data() {
QUOTE_ID = d1.QUOTE_ID,
EVENT_ACTION_CD = d1.EVENT_ACTION_CD,
CUSTOMER_NAME = d2.CUSTOMER_NAME,
ADAPTIV_CODE = d2.ADAPTIV_CODE
}
);

Using LINQ to count value frequency

I have a table
ID|VALUE
VALUE is an integer field with possible values between 0 and 4. How can I query the count of each value?
Ideally the result should be an array with 6 elements, one for the count of each value and the last one is the total number of rows.
This simple program does just that:
class Record
{
public int Id { get; set; }
public int Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<Record> records = new List<Record>()
{
new Record() { Id = 1, Value = 0},
new Record() { Id = 2, Value = 1 },
new Record() { Id = 3, Value = 2 },
new Record() { Id = 4, Value = 3 },
new Record() { Id = 5, Value = 4 },
new Record() { Id = 6, Value = 2 },
new Record() { Id = 7, Value = 3 },
new Record() { Id = 8, Value = 1 },
new Record() { Id = 9, Value = 0 },
new Record() { Id = 10, Value = 4 }
};
var query = from r in records
group r by r.Value into g
select new {Count = g.Count(), Value = g.Key};
foreach (var v in query)
{
Console.WriteLine("Value = {0}, Count = {1}", v.Value, v.Count);
}
}
}
Output:
Value = 0, Count = 2
Value = 1, Count = 2
Value = 2, Count = 2
Value = 3, Count = 2
Value = 4, Count = 2
Slightly modified version to return an Array with only the count of values:
int[] valuesCounted = (from r in records
group r by r.Value
into g
select g.Count()).ToArray();
Adding the rows count in the end:
valuesCounted = valuesCounted.Concat(new[] { records.Count()}).ToArray();
Here is how you would get the number of rows for each value of VALUE, in order:
var counts =
from row in db.Table
group row by row.VALUE into rowsByValue
orderby rowsByValue.Key
select rowsByValue.Count();
To get the total number of rows in the table, you can add all of the counts together. You don't want the original sequence to be iterated twice, though; that would cause the query to be executed twice. Instead, you should make an intermediate list first:
var countsList = counts.ToList();
var countsWithTotal = countsList.Concat(new[] { countsList.Sum() });

What is the easiest way to fixed pivot transform this data in C# (linq?)?

Given this example data (in .NET classes where Po, Sku, Qty are properties):
PO, Sku, Qty
1,ABC,1
1,DEF,2
1,GHI,1
1,QWE,1
1,ASD,1
1,ZXC,5
1,ERT,1
2,QWE,1
2,ASD,11
2,ZXC,1
3,ERT,1
3,DFG,1
3,DFH,1
3,CVB,4
3,VBN,1
3,NMY,1
I need to transform it into a fixed column format, with a max of 5 SKUs per line (repeating the PO if needed for > 5):
PO, SkuA, QtyA, SkuB, QtyB, SkuC, QtyC, SkuD, QtyD, SkuE, QtyE
1, ABC, 1, DEF, 2, GHI, 1, QWE, 1, ASD, 1
1, ZXC, 5, ERT, 1, , , , , ,
2, QWE, 1, ASD, 11, ZXC, 1, , , ,
3, ERT, 1, DFG, 1, DFH, 1, CVB, 4, VBN, 1
3, NMY, 1, , , , , , , ,
Output can be CSV (which is what I'm outputting), or .NET classes - no matter there. Is there a simple way to do this in Linq by grouping by PO, then by counts of 5?
EDIT: I have no control of over the destination format. And for anyone interested, it's VendorNet and VendorBridge that require this nonsense.
Firstly, here's the query that will generate the correct hierarchy of objects. I'm using anonymous types but it's easy enough to change it to use your own proper classes.
var query = yourData
.GroupBy
(
x => x.PO
)
.SelectMany
(
x => x.Select
(
(y, i) => new { y.PO, y.Sku, y.Qty, Key = i / 5 }
)
)
.GroupBy
(
x => new { x.PO, x.Key }
);
Using LINQ to create the CSV from the query results is bit of a hack, but it gets the job done. (The "benefit" of using LINQ is that you could chain the original query and the CSV generation into a single, massive statement, should you wish.)
IEnumerable<string> csvLines = query
.Select
(
x => x.Aggregate
(
new { Count = 0, SB = new StringBuilder() },
(a, y) => new
{
Count = a.Count + 1,
SB = ((a.SB.Length == 0) ? a.SB.Append(y.PO) : a.SB)
.Append(", ").Append(y.Sku).Append(", ").Append(y.Qty)
},
a => a.SB.ToString() + string.Join(", , ", new string[6 - a.Count])
)
);
string csv = string.Join(Environment.NewLine, csvLines.ToArray());
In my opinion, creating the CSV without using LINQ makes the code much more readable:
StringBuilder sb = new StringBuilder();
foreach (var group in query)
{
int count = 0;
foreach (var item in group)
{
if (count++ == 0)
{
sb.Append(item.PO);
}
sb.Append(", ").Append(item.Sku).Append(", ").Append(item.Qty);
}
while (count++ < 5)
{
sb.Append(", , ");
}
sb.Append(Environment.NewLine);
}
string csv = sb.ToString();
Here you go. I didn't format the output the way you wanted. But this should give you an idea of how to pivot rows. Hope this helps :-)
public class MyClass
{
public int PO { get; set; }
public String SKU { get; set; }
public int Qty { get; set; }
public static IEnumerable<MyClass> GetList()
{
return new List<MyClass>()
{
new MyClass {PO = 1, SKU = "ABC", Qty = 1},
new MyClass {PO = 1, SKU = "DEF", Qty = 2},
new MyClass {PO = 1, SKU = "GHI", Qty = 1},
new MyClass {PO = 1, SKU = "QWE", Qty = 1},
new MyClass {PO = 1, SKU = "ASD", Qty = 1},
new MyClass {PO = 1, SKU = "ZXC", Qty = 5},
new MyClass {PO = 1, SKU = "ERT", Qty = 1},
new MyClass {PO = 2, SKU = "QWE", Qty = 1},
new MyClass {PO = 2, SKU = "ASD", Qty = 1},
new MyClass {PO = 2, SKU = "ZXC", Qty = 5},
};
}
}
EDIT: I've fixed the query based on Luke's comment
var lQuery =
MyClass.GetList()
.GroupBy(pArg => pArg.PO)
.Select(pArg => new
{
Test = pArg.Select((pArg1, pId) =>
new {ID = (pId / 5),
pArg1.PO, pArg1.SKU, pArg1.Qty})
.GroupBy(pArg1 => pArg1.ID)
.Select(pArg1 =>
pArg1.Aggregate(pArg.Key.ToString(),
(pSeed, pCur) =>
pSeed + pCur.SKU + ","))
});

Categories