How to index nested list? - c#

I am having a real problem iterating a list<list<object>> and would like to ask anybody for their insight.
as you can see I have a list<list<object>> posCheckOptions which contains 32 lists and each list contains x amount of objects, i.e 348 for 0, 325 for 1 etc.
for example, in this list of 348 there are duplicate elements which I would like to remove. in duplicate I mean, the name of the stock could be the same so, if I have say, VODAFONE 4 times, I would like to add the qty of shares from each one to the first time Vodafone is spotted and delete the duplicates.
for(int k = 0; k<posCheckOptions.Count; k++)
{
int l = 0;
int m = 1;
while (l != m)
{
foreach(var x in posCheckOptions[k][l].name)
{
if(posCheckOptions[k][l].date != posCheckOptions[k][m].date
&& posCheckOptions[k][l].strike != posCheckOptions[k][m].strike
&& posCheckOptions[k][l].callPut != posCheckOptions[k][m].callPut)
{
m++;
}
else
{
posCheckOptions[k][l].size = posCheckOptions[k][l].size + posheckOptions[k][m].size;
posCheckOptions[k].RemoveAt(m);
m--;
}
}
l++; m = l;
}
}
What I am trying to code, well at least the idea is, I start from posCheckOptions[0][0] and compare elements posCheckOptions[0][0].date to posCheckOptions[0][1].date (i compare 4 fields) (poscheckOptions is a list whos type T is a class with 76 varaibles). If what I compare is not equal (i.e. not duplicate I move the index up level and continue to the next, so on. On my travels if I find a duplicate element, i do the addition and remove, move the index back one and start until I reach the end.
im getting confused with the fact, im not sure if i need 2 index running in j posCheckOptions[0][j] because j only goes to j+1 once say index m has looped all 348 elements. This may not be 348 if i delete 2....
any advice is really welcome :-)

Looks like your problem can be easily solved using LINQ. I'm gonna show it on simplified example, so you'll have to adjust it you your real one.
Assume we have a class names Item as following:
public class Item
{
public int Key { get; set; }
public int SubKey { get; set; }
public int Quantity { get; set; }
}
And a list (List<Item>):
var items = new List<Item>() {
new Item { Key = 1, SubKey = 1, Quantity = 100 },
new Item { Key = 1, SubKey = 2, Quantity = 400 },
new Item { Key = 2, SubKey = 1, Quantity = 60 },
new Item { Key = 1, SubKey = 1, Quantity = 10 },
new Item { Key = 2, SubKey = 1, Quantity = 30 },
new Item { Key = 1, SubKey = 1, Quantity = 70 }
};
Now, we'd like to sum Quantity for elements with the same pair of Key and SubKey (what will also remove duplicates). There is GroupBy method within LINQ, so let's use it:
var groupedItems = items.GroupBy(x => new { x.Key, x.SubKey })
.Select(g => new Item {
Key = g.Key.Key,
SubKey = g.Key.SubKey,
Quantity = g.Sum(x => x.Quantity)
}).ToList();
As a result, we have a new List<Item> with only one element for every Key/SubKey pair and Quantity which is a sum of Quantities for items with that key pair.
Can it be expanded for <List<List<Item>> as an input and output? Sure it can.
Source nested Items collection:
var nestedItems = new List<List<Item>>() {
new List<Item>() {
new Item { Key = 1, SubKey = 1, Quantity = 100 },
new Item { Key = 1, SubKey = 2, Quantity = 400 },
new Item { Key = 2, SubKey = 1, Quantity = 60 },
new Item { Key = 1, SubKey = 1, Quantity = 10 },
new Item { Key = 2, SubKey = 1, Quantity = 30 },
new Item { Key = 1, SubKey = 1, Quantity = 70 }
},
new List<Item>() {
new Item { Key = 1, SubKey = 1, Quantity = 100 },
new Item { Key = 1, SubKey = 2, Quantity = 400 },
new Item { Key = 2, SubKey = 1, Quantity = 60 },
new Item { Key = 1, SubKey = 1, Quantity = 10 },
new Item { Key = 2, SubKey = 1, Quantity = 30 },
new Item { Key = 1, SubKey = 1, Quantity = 70 }
},
new List<Item>() {
new Item { Key = 1, SubKey = 1, Quantity = 100 },
new Item { Key = 1, SubKey = 2, Quantity = 400 },
new Item { Key = 2, SubKey = 1, Quantity = 60 },
new Item { Key = 1, SubKey = 1, Quantity = 10 },
new Item { Key = 2, SubKey = 1, Quantity = 30 },
new Item { Key = 1, SubKey = 1, Quantity = 70 }
}
};
And the query:
var nestedGroupedItems = nestedItems.Select(x => x.GroupBy(y => new {y.Key, y.SubKey })
.Select(g => new Item {
Key = g.Key.Key,
SubKey = g.Key.SubKey,
Quantity = g.Sum(y => y.Quantity)
}).ToList()).ToList();

I would suggest to do the following:
Implement the IComparable interface to the type of which the objects in the lists are. This way you can store the comparison logic inside the type itself.
Create a list of this type (just like the lists in posCheckOptions). Lets call it bigList
iterarte over all lists in posCheckOptions
iterate over each item in the contained list and check if the item is contained in bigList. If it is, delete it from the current inner list. If not add it to bigList

I'm making some assumptions about the Type that contains your stock information, feel free to adjust as necessary. What you can do is create a dictionary mapping stock names to the stock objects. Update the object in the dictionary if it exists, or add it to the dictionary.
var allStock = mylist.SelectMany(l => l.Select(inner => inner));
var lookup = new Dictionary<string, Stock>();
foreach (var stock in allStock)
{
if (!lookup.ContainsKey(stock.Name)) {
lookup.Add(stock.Name, stock);
continue;
}
lookup[stock.Name].Quantity += stock.Quantity;
}
Now you have a dictionary mapping the names of stock to the actual stock. Just iterate over the values to get a list back out if that's what you need.

Related

Find unique permutations for object

I have a list of products that have an ID and a Quantity, and I need to find a list of combinations of products that will fill a certain quantity.
E.g.
ProductID | Quantity
1 | 5
2 | 5
3 | 8
4 | 15
If I require a quantity of 15 then I want to get a list with the following combinations:
Products: {1, 2, 3}, {1, 3, 2}, {1, 2, 4}, {1, 3, 4}, {1, 4}
{2, 1, 3}, {2, 1, 4}, {2, 3, 1}, {2, 3, 4}, {2, 4}
{3, 1, 2}, {3, 1, 4}, {3, 2, 1}, {3, 2, 4}, {3, 4}
{4}
It's almost a permutation, but it's filtered out entries that sum up to more than what is required. I need to stop taking further items, if at any point, the current total sum of values goes beyond 15. Doing this way, if I had all permutations then I would have 24 results, but I only have 16.
E.g. if I take product 4 then I don't need to combine it with anything to make 15. Similarly, if I take product 1 then take product 4, I don't need to pick up anymore item since the sum is already beyond 15 (5 + 15 = 20).
I was able to get the code working by getting all Permutations (e.g. here) and then filtering that down to the ones I care about, however once you start getting a large number of products (e.g. 30) then you end up with 4.3 Billion combinations which causes out of memory exceptions.
How can I create only the required permutations in C#?
looks like only two rule:
1. elements picked are distinct.
2. sum of picked elements' qty must greater then goal, not just only equal to goal.
My example add some interface for sorting. Every kind of combination that can reach goal are listed. But I trying to list in unique form for reading. You can to oringinal expand job within each combination.
PS. for order purpose I add IComparable, not very important.
class Product: IComparable
{
public int ID { get; set; }
public uint Qty { get; set; }
public int CompareTo(object obj)
{
if (obj is Product)
return this.ID.CompareTo(((Product)obj).ID);
else
return -1;
}
public override string ToString()
{
return string.Format("Product: {0}", this.ID);
}
}
class Combination : List<Product>, IComparable
{
public int Goal { get; private set; }
public bool IsCompleted
{
get
{
return this.Sum(product => product.Qty) >= Goal;
}
}
public Combination(int goal)
{
Goal = goal;
}
public Combination(int goal, params Product[] firstProducts)
: this(goal)
{
AddRange(firstProducts);
}
public Combination(Combination inheritFrom)
: base(inheritFrom)
{
Goal = inheritFrom.Goal;
}
public Combination(Combination inheritFrom, Product firstProduct)
: this(inheritFrom)
{
Add(firstProduct);
}
public int CompareTo(object obj)
{
if (obj is Combination)
{
var destCombination = (Combination)obj;
var checkIndex = 0;
while (true)
{
if (destCombination.Count - 1 < checkIndex && this.Count - 1 < checkIndex)
return 0;
else if (destCombination.Count - 1 < checkIndex)
return -1;
else if (this.Count - 1 < checkIndex)
return 1;
else
{
var result = this[checkIndex].CompareTo(destCombination[checkIndex]);
if (result == 0)
checkIndex++;
else
return result;
}
}
}
else
return this.CompareTo(obj);
}
public override int GetHashCode()
{
unchecked
{
return this.Select((item, idx) => item.ID * (10 ^ idx)).Sum();
}
}
public override bool Equals(object obj)
{
if (obj is Combination)
return ((Combination)obj).GetHashCode() == this.GetHashCode();
else
return base.Equals(obj);
}
}
the testing part provide product list and the goal.
public static void Test()
{
var goal = 25;
var products = new[]
{
new Product() { ID = 1, Qty = 5 },
new Product() { ID = 2, Qty = 5 },
new Product() { ID = 3, Qty = 8 },
new Product() { ID = 4, Qty = 15 },
new Product() { ID = 5, Qty = 17 },
new Product() { ID = 6, Qty = 1 },
new Product() { ID = 7, Qty = 4 },
new Product() { ID = 8, Qty = 6 },
};
var orderedProducts = products.OrderBy(prod => prod.ID);
//one un-completed combination, can bring back muliple combination..
//that include completed or next-staged-uncompleted combinations
Func<Combination, IEnumerable<Combination>> job = null;
job = (set) =>
{
if (set.IsCompleted)
return new[] { set }.ToList();
else
{
return orderedProducts
.Where(product => set.Contains(product) == false && product.ID >= set.Last().ID)
.Select(product => new Combination(set, product))
.SelectMany(combination => job(combination));
}
};
var allPossibility = orderedProducts
.Select(product => new Combination(goal, product))
.SelectMany(combination => job(combination))
.Where(combination => combination.IsCompleted)
.Select(combination => new Combination(goal, combination.OrderBy(product => product.ID).ToArray()))
.OrderBy(item => item)
.ToList();
foreach (var completedCombination in allPossibility)
{
Console.WriteLine(string.Join<int>(", ", completedCombination.Select(prod => prod.ID).ToArray()));
}
Console.ReadKey();
}
This probably isn't the most efficient answer, but it does give the right answer:
void Main()
{
List<Product> products = new List<Product> { new Product { ProductID = 1, Quantity = 5 },
new Product { ProductID = 2, Quantity = 5 },
new Product { ProductID = 3, Quantity = 8 },
new Product { ProductID = 4, Quantity = 15 },
};
decimal requiredQuantity = 15;
if (requiredQuantity < products.Sum(p => p.Quantity))
{
var output = Permutations(products, requiredQuantity);
output.Dump();
}
else
{
products.Dump();
}
}
// Define other methods and classes here
private List<Queue<Product>> Permutations(List<Product> list, decimal requiredValue, Stack<Product> currentList = null)
{
if (currentList == null)
{
currentList = new Stack<Product>();
}
List<Queue<Product>> returnList = new List<System.Collections.Generic.Queue<Product>>();
foreach (Product product in list.Except(currentList))
{
currentList.Push(product);
decimal currentTotal = currentList.Sum(p => p.Quantity);
if (currentTotal >= requiredValue)
{
//Stop Looking. You're Done! Copy the contents out of the stack into a queue to process later. Reverse it so First into the stack is First in the Queue
returnList.Add(new Queue<Product>(currentList.Reverse()));
}
else
{
//Keep looking, the answer is out there
var result = Permutations(list, requiredValue, currentList);
returnList.AddRange(result);
}
currentList.Pop();
}
return returnList;
}
struct Product
{
public int ProductID;
public int Quantity;
}
I'll discuss the solution in terms of Python because I don't have C# installed on this Mac, but C# has iterators, so what I'm talking about will work.
First of all, as you discovered, you DO NOT want to return the whole list. It consumes a tremendous amount of memory. Instead return an iterator as in https://msdn.microsoft.com/en-us/library/65zzykke(v=vs.100).aspx that will return each element of your list in turn.
Secondly, you can build iterators out of iterators. The first one is the one that does subsets where the last element pushes you to your threshold and beyond:
def minimal_subset_iter (product, threshold):
# Sort smallest to the front so that we skip no combinations that
# will lead to our threshold in any order.
ids = list(sorted(product.keys(), key=lambda key: (product[key], key)))
# Figure out the size of the trailing sums.
remaining_sum = []
total_sum = sum(product.values())
for i in range(len(ids)):
remaining_sum.append(
total_sum - sum(product[ids[j]] for j in range(i)))
remaining_sum.append(0)
# We modify this in place and yield it over and over again.
# DO NOT modify it in the return, make a copy of it there.
chosen_set = []
def _choose (current_sum, i):
if threshold <= current_sum:
yield chosen_set
elif threshold <= current_sum + remaining_sum[i]:
# Can I finish without this element?
for x in _choose(current_sum, i+1):
yield x
# All of the ways to finish with this element.
chosen_set.append(ids[i])
current_sum = current_sum + product[ids[i]]
for x in _choose(current_sum, i+1):
yield x
# Cleanup!
chosen_set.pop()
return _choose(0, 0)
for x in minimal_subset_iter({1: 5, 2: 5, 3: 8, 4: 15}, 15):
print(x)
And now you need an iterator that turns a minimal subset into all permutations of that subset where the last element pushes you to the threshold.
I won't write that one since the principle is straightforward. And besides you have to translate it into a different language. But the idea is to pull out each possibility for that last element that reaches the end, run permutations of the rest, and append the last element back before yielding it.
This algorithm will be very efficient on memory (it basically keeps the dictionary and the current permutation) and also quite efficient on performance (it has a lot of lists to create, but will waste little time creating ones that it doesn't need). It does, however, take some time to wrap your head around this way of working.

Sort collection by another collection values

I have List<SomeData> data;
public class SomeData
{
public int Key { get; set;}
public decimal Value { get; set;}
}
Also i have List<int> DataOrder;
I need to sort List<SomeData>data by Key, placing it in same order as List<int> DataOrder values.
Is there any common algorithms for that?
Example:
List<SomeData> data = new List<SomeData>();
data.Add(new SomeData{ Key = 10, Value = 14 })
data.Add(new SomeData{ Key = 25, Value = 22 })
data.Add(new SomeData{ Key = 567, Value = 3 })
data.Add(new SomeData{ Key = 57, Value = 300 })
data.Add(new SomeData{ Key = 17, Value = 200 })
data.Add(new SomeData{ Key = 343, Value = 42 })
List<int> DataOrder = new List<int>{1, 25, 700, 567, 343, 350, 10};
Result after sorting:
foreach(var element in data)
{
Console.WriteLine(element.Key);
}
Out:
25
567
343
10
57
17
Edit: initial data array can have Key, that not contain in DataOrder
Such value should be placed at the end of result collection in any order.
Example changed to illustrate it.
What about joining:
var mySortedList = (from i in DataOrder
join d in data on i equals d.Key
select new SomeData
{
Key = d.Key,
Value = d.Value
});
EDIT: To also add those values from data that do NOT share any key within the DataOrder-list you may simply add a Union to the result as follows:
var result = mySortedList.Union(data.Where(x => !DataOrder.Contains(x.Key)));
Solved
public class SomeData
{
public int Key { get; set; }
public decimal Value { get; set; }
}
class Program
{
static void Main(string[] args)
{
List<SomeData> orders = new List<SomeData>();
orders.Add(new SomeData { Key = 10, Value = 14 });
orders.Add(new SomeData { Key = 25, Value = 22 });
orders.Add(new SomeData { Key = 567, Value = 3 });
orders.Add(new SomeData { Key = 57, Value = 300 });
orders.Add(new SomeData { Key = 17, Value = 200 });
orders.Add(new SomeData { Key = 343, Value = 42 });
List<int> ids = new List<int> { 1, 25, 700, 567, 343, 350, 10 };
//get orders only from ids with order
List<SomeData> existedOrders = (from order in orders
join id in ids
on new { onlyId = order.Key }
equals new { onlyId = id }
orderby ids.IndexOf(id)
select order).ToList();
//add others
existedOrders.AddRange(orders.Except(existedOrders).ToList());
}
}
//with #HimBromBeere solution you can reduce query
//get orders only from ids with order
List<SomeData> existedOrders = (from order in orders
join id in ids
on order.Key equals id
orderby ids.IndexOf(id)
select order).ToList();
int count = 0;
for(int i in DataOrder)
{
var index = data.IndexOf(d => d.Key == i);
swap(data[count], data[index]);
count++;
}
and swap function is for swap places of items.

How to select records with duplicate column by group using LINQ?

For some reason some records have the same ID. Aim is to list all the whole record which have the same ID. For example, how can group the following records by GroupId using LINQ, and find all records with the same ID and list them all? (thus merging all rows in each group into one)
var list = new List<Record>()
{
new Record() { GroupId = 0, ValueA = 20, ValueB = 300 },
new Record() { GroupId = 1, ValueA = 30, ValueB = 700 },
new Record() { GroupId = 1, ValueA = 40, ValueB = 500 },
new Record() { GroupId = 2, ValueA = 80, ValueB = 300 },
new Record() { GroupId = 2, ValueA = 20, ValueB = 200 },
new Record() { GroupId = 2, ValueA = 20, ValueB = 200 }
};
Expect result is the last 5 records.
Another way is:
var results = (from l in list
group new {l.ValueA, l.ValueB} by l.GroupId
into g
select new {GroupId = g.Key, Values = g.ToList()}).ToList();
If you prefer lambda expression, then
var results = (list.GroupBy(l => l.GroupId, l => new {l.ValueA, l.ValueB})
.Select(g => new {GroupId = g.Key, Values = g.ToList()})).ToList();
This should give you records based on unique GroupIdalong the other values associated.
EDIT
foreach (var result in results)
{
foreach (var value in result.Values)
{
int valueA = value.ValueA;
int valueB = value.ValueB;
}
}
I think you are looking for something similar to this.
Hope it helps.
Answer for "how can group the following records by GroupId using LINQ"
var groupList = list.GroupBy((mRecord) => mRecord.GroupId).ToList();
var groups = list.ToLookup(record => record.GroupId);
var groups1 = groups[1]; //all records with GroupId = 1

Sub Totalling Relational Datatable

I have a datatable with relational data structure, I need to sum up the sub nodes to their parent nodes all the way up to the top parent (NULL parent Id)
I have attached 2 images which shows the original table and another with the expected results
Cheers
I've taken an approach that simulates data as they could have been materialized from a database by some ORM, i.e. a class that contains data and a collection of children. Plus some "business logic" to calculate the required numbers. So you can choose a db approach as well as an in-memory approach.
In Linqpad:
void Main()
{
var data = new[]
{
new Record { Id = 1, ParentId = null, Qty = 1, Cost = 0.0m },
new Record { Id = 2, ParentId = 1, Qty = 2, Cost = 0.0m },
new Record { Id = 3, ParentId = 1, Qty = 3, Cost = 0.0m },
new Record { Id = 4, ParentId = 2, Qty = 4, Cost = 0.0m },
new Record { Id = 5, ParentId = 3, Qty = 5, Cost = 0.0m },
new Record { Id = 6, ParentId = 2, Qty = 6, Cost = 1.7m },
new Record { Id = 7, ParentId = 4, Qty = 7, Cost = 1.8m },
new Record { Id = 8, ParentId = 5, Qty = 8, Cost = 1.9m },
new Record { Id = 9, ParentId = 5, Qty = 9, Cost = 2.0m },
}.ToList();
// Mimic ORM's job:
data.ForEach(d => d.ChildRecords =
data.Where(c => c.ParentId == d.Id).ToList());
data.Select(d => new { d.Id, d.Cost, d.TotalCost } ).Dump();
}
class Record
{
public int Id { get; set; }
public int? ParentId { get; set; }
public int Qty { get; set; }
private decimal _cost = 0m;
public decimal Cost
{
get { return this._cost + this.ChildRecords.Sum(cr => cr.TotalCost); }
set { this._cost = value; }
}
public decimal TotalCost
{
get { return this.Qty * this.Cost; }
}
public ICollection<Record> ChildRecords;
}
Result:
Id Cost TotalCost
1 619.2 619.2
2 60.6 121.2
3 166 498
4 12.6 50.4
5 33.2 166
6 1.7 10.2
7 1.8 12.6
8 1.9 15.2
9 2 18
An optimization could be to apply some memoization, i.e. let the Cost property store the result of its getter in a private member variable.

C# datagridview math operation then pass data to a list

I have a datagridview on my Winform GUI which loads a CSV and gathers data with three columns:
File_ID, Details, End Line
0, sometext, 1
0, sometext, 3
0, sometext, 5
1, sometext, 9
1, sometext, 16
1, sometext, 23
2, sometext, 25
2, sometext, 27
2, sometext, 28
2, sometext, 30
I want this gridview to be translated to a list
so that I can do something like this:
pList.Add(new FileExtract(1, 1, 148165));
pList.Add(new FileExtract(2, 148165, 166926));
but a loop rather than hardcording Add(new FileExtract ... )
The list should look something like this:
ID, start, end
1, 0, 5
2, 6, 23
3, 24, 30
Note that:
- For first line: ID = 1, start = 0.
- ID = 1 relates to File_ID, think of ID as counter
- Start on ID 2, is end of ID 1 plus 1, start on ID 3 = end of ID 2 plus
The bit that works:
var filesplitc = from p in ListBoxEdit1
group p by p.file_id into grp
let MaxP = grp.Max(g => g.RunningTotal)
from p in grp
where p.RunningTotal == MaxP
select p;
var filesplitc1 = from p in filesplitc
select new { file_id = p.file_id, startingline = (p.file_id == 0) ? "0" : "", endingline = p.RunningTotal };
The bit that doesn't work:
var filesplitc2 = from p in filesplitc1
select new {
file_id = p.file_id,
startingline = p.startingline == "" ? ((from x in filesplitc1 where (Convert.ToInt32(x.file_id) <= Convert.ToInt32(p.file_id)) select x.endingline).Last()) : p.startingline,
endingline = p.endingline
};
You probably have to adjust it a bit because I don't know what data structure you put into the list box but should give you an idea. It's not Linq but trying to squash everything into a Linq query does not necessarily help readability and code maintenance.
class FileExtract
{
public int FileId { get; set; }
public int Start { get; set; }
public int End { get; set; }
}
var fileExtracts = new List<FileExtract>();
// take first entry as initializer
var current = new FileExtract
{
FileId = ListBoxEdit1.First().file_id,
Start = 0,
End = ListBoxEdit1.First().end_line
};
int lastEnd = current.End;
// skip first entry of the list as it was already used as initializer
foreach (var p in ListBoxEdit1.Skip())
{
if (current.FileId != p.file_id)
{
current.End = lastEnd;
fileExtract.Add(current);
current = new FileExtract { FileId = p.file_id, Start = lastEnd + 1, End = p.end_line };
}
lastEnd = p.end_line;
}

Categories

Resources