unequal size lists to merge - c#

I have searched without success to a similar situation as follows.
I have two lists, list A and list B.
List A is composed of 10 objects created from ClassA which contains only strings.
List B is composed of 100 objects created from ClassB which only contains decimals.
List A is the header information.
List B is the data information.
The relationship between the two lists is:
Row 1 of list A corresponds to rows 1-10 of list B.
Row 2 of list A corresponds to rows 11-20 of list B.
Row 3 of list A corresponds to rows 21-30 of list B.
etc.........
How can I combine these two lists so that when I display them on the console the user will see a header row followed immediately by the corresponding 10 data rows.
I apologize if this has been answered before.

Ok, that should work. Let me know in case I got anything wrong.
List<ClassA> listA = GetListA()// ...
List<ClassB> listB = GetListA()// ...
if(listB.Count % listA.Count != 0)
throw new Exception("Unable to match listA to listB");
var datasPerHeader = listB.Count / listA.Count;
for(int i = 0; i < listA.Count;i++)
{
ClassA header = listA[i];
IEnumerable<ListB> datas = listB.Skip(datasPerHeader*i).Take(datasPerHeader);
Console.WriteLine(header.ToString());
foreach(var data in datas)
{
Console.WriteLine("\t{0}", data.ToString());
}
}

Here is some code that should fulfill your request - I am going to find a link for the partition extension as I can't find it in my code anymore:
void Main()
{
List<string> strings = Enumerable.Range(1,10).Select(x=>x.ToString()).ToList();
List<decimal> decimals = Enumerable.Range(1,100).Select(x=>(Decimal)x).ToList();
var detailsRows = decimals.Partition(10)
.Select((details, row) => new {HeaderRow = row, DetailsRows = details});
var headerRows = strings.Select((header, row) => new {HeaderRow = row, Header = header});
var final = headerRows.Join(detailsRows, x=>x.HeaderRow, x=>x.HeaderRow, (header, details) => new {Header = header.Header, Details = details.DetailsRows});
}
public static class Extensions
{
public static IEnumerable<List<T>> Partition<T>(this IEnumerable<T> source, Int32 size)
{
for (int i = 0; i < Math.Ceiling(source.Count() / (Double)size); i++)
yield return new List<T>(source.Skip(size * i).Take(size));
}
}
That Partition method is the one that does the grunt work...
And here is the link to the article - LINK
EDIT 2
Here is better code for the Main() method... Rushed to answer and forgot brain:
void Main()
{
List<string> strings = Enumerable.Range(1,10).Select(x=>x.ToString()).ToList();
List<decimal> decimals = Enumerable.Range(1,100).Select(x=>(Decimal)x).ToList();
var detailsRows = decimals.Partition(10);
var headerRows = strings; //just renamed for clarity from other code
var final = headerRows.Zip(detailsRows, (header, details) => new {Header = header, Details = details});
}

This should be pretty straight forward unless I'm missing something.
var grouped = ListA.Select((value, index) =>
new {
ListAItem = value,
ListBItems = ListB.Skip(index * 10).Take(10)
})
.ToList();
Returns back an anonymous type you can loop through.
foreach (var group in grouped)
{
Console.WriteLine("List A: {0}", group.Name);
foreach (var listBItem in group.ListBItems)
{
Console.WriteLine("List B: {0}", listBItem.Name);
{
}

The easiest way may be something like this:
var listA = new List<string>() { "A", "B", "C", ... }
var listB = new List<decimal>() { 1m, 2m, 3m, ... }
double ratio = ((double)listA.Count) / listB.Count;
var results =
from i in Enumerable.Range(0, listB.Count)
select new { A = listA[(int)Math.Truncate(i * ratio)], B = listB[i] };
Or in fluent syntax:
double ratio = ((double)listA.Count) / listB.Count;
var results = Enumerable.Range(0, listB.Count)
.Select(i => new { A = listA[(int)Math.Truncate(i * ratio)], B = listB[i] });
Of course if you know you will always have 10 items in listB for each item in listA, you can simplify this to:
var results =
from i in Enumerable.Range(0, listB.Count)
select new { A = listA[i / 10], B = listB[i] };
Or in fluent syntax:
var results = Enumerable.Range(0, listB.Count)
.Select(i => new { A = listA[i / 10], B = listB[i] });
This will return a result set like
{ { "A", 1 },
{ "A", 2 },
{ "A", 3 }
..,
{ "A", 10 },
{ "B", 11 },
{ "B", 12 },
{ "B", 13 },
...
{ "B", 20 },
{ "C", 21 },
...
{ "J", 100 }
}

Related

IEnumerable.Select() when attribute is known only at runtime

Say I have a data class like this and a list of its objects:
public class DataSet
{
public int A { get; set; }
public string B { get; set; }
public double C { get; set; }
}
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
I would like to do a Select() on the list, based on different properties. For example, if I need a list of property A, I could do this easily:
var listA = data.Select(x => x.A).ToList();
All good so far.
But in my program, I need to do the above, only, I wouldn't know whether I need a list of A or B or C until runtime. This 'knowledge' of what to select is stored in a list of strings, and I need to iterate it and extract only the appropriate lists. Something like this:
// GetKeys() will return the keys that I need to extract.
// So at one time keyList could have "A" and "B", another time "B" and "C" etc.
List<string> keyList = GetKeys();
foreach (var key in keyList)
{
// What do I do here?
data.Select(x =>???).ToList();
}
Is this possible at all? I'm fine with even a non-LINQ solution, if it achieves my goal.
EDIT:
Clarifying the requirement.
The end result I want is a separate list based on each 'key' mentioned above. So, something like
List<List<object>>
The count in outer list would be the count of keyList.
The inner list would have as many items as in DataSet.
This would probably not be the most efficient solution, but you could use Reflection for a fully dynamic solution:
private static List<List<object>> SelectDynamicData<T>(IEnumerable<T> data, List<string> properties)
{
// get the properties only once per call
// this isn't fast
var wantedProperties = typeof(T)
.GetProperties()
.Where(x => properties.Contains(x.Name))
.ToArray();
var result = new Dictionary<string, List<object>>();
foreach (var item in data)
{
foreach (var wantedProperty in wantedProperties)
{
if (!result.ContainsKey(wantedProperty.Name))
{
result.Add(wantedProperty.Name, new List<object>());
}
result[wantedProperty.Name].Add(wantedProperty.GetValue(item));
}
}
return result.Select(x => x.Value).ToList();
}
And, of course, you'd need to do a double foreach or a LINQ query to print that. For example:
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
var selectedData = SelectDynamicData(data, new List<string> { "A", "C" });
foreach (var list in selectedData)
{
foreach (object item in list)
{
Console.Write(item + ", ");
}
Console.WriteLine();
}
Using Creating Expression Trees by Using the API you can build an expression tree to represent the linq query you were hard coding in order to make it more dynamic.
Expression<Func<TModel, object>> GetPropertyExpression<TModel>(string propertyName) {
// Manually build the expression tree for
// the lambda expression v => v.PropertyName.
// (TModel v) =>
var parameter = Expression.Parameter(typeof(TModel), "v");
// (TModel v) => v.PropertyName
var property = Expression.Property(parameter, propertyName);
// (TModel v) => (object) v.PropertyName
var cast = Expression.Convert(property, typeof(object));
var expression = Expression.Lambda<Func<TModel, object>>(cast, parameter);
return expression;
}
Review the comments to understand the building of the expression tree.
This now can be used with the data to extract the desired result.
Following similar to what was provided in another answer it would be simplified to
List<List<object>> SelectDynamicData<T>(IEnumerable<T> data, List<string> properties) {
return properties
.Select(_ => data.Select(GetPropertyExpression<T>(_).Compile()).ToList())
.ToList();
}
Both methods are displayed in the following example
[TestMethod]
public void TestMethod1() {
var data = new List<DataSet>
{
new DataSet() { A = 1, B = "One", C = 1.1 },
new DataSet() { A = 2, B = "Two", C = 2.2 },
new DataSet() { A = 3, B = "Three", C = 3.3 }
};
var propertyKnownAtRuntime = "A";
var expression = GetPropertyExpression<DataSet>(propertyKnownAtRuntime);
var listA = data.Select(expression.Compile()).ToList();
//Produces
// { 1, 2, 3}
var listAC = SelectDynamicData(data, new List<string> { "A", "C" });
//Produces
//{
// { 1, 2, 3},
// { 1.1, 2.2, 3.3 }
//}
}
You can use reflection, for example
string key = "A";
var query = data.Select(x =>
{
var prop = x.GetType().GetProperty(key); //NOTE: if key does not exist this will return null
return prop.GetValue(x);
});
foreach (var value in query)
{
Console.WriteLine(value); //will print 1, 2, 3
}

LINQ - merge 2 lists but with specific order

I want to merge 2 lists into one list, however, it have to be in special order.
For example, I have a list of type A:
{ A, A ,A ,A ,A, A .... }
And also a list of type B:
{B, B, B , B ....}
And desired result should be like this:
{A, A, B, A, A, B, A, A, B}
The merge should take the 2 items from list A, and then 1 item from List B.
One thing to note is, in case of one list gets empty, fill all the rest items with the second list's items.
I trying to find an elegant way to do it with LINQ.
Heres my code, but it's a bit long and I hope there is a better way to do it via linq:
Thanks a lot.
public IList<PersonBase> Order(IList<Person1> people1, IList<Person2> people2)
{
if (people1.IsNullOrEmpty())
return people2;
if (people2.IsNullOrEmpty())
return people1;
List<PersonBase> orderedList = new List<PersonBase>();
var people1Count = 0;
var people2Count = 0;
while (people2Count < people2.Count || people1Count < people1.Count)
{
var people1ToAdd = tags.Skip(people1Count).Take(1).ToList();
people1Count = people1.Count();
orderedList.AddRange(people1ToAdd);
if (people1Count >= people1.Count)
{
orderedList.AddRange(people2.Skip(people2Count));
break;
}
var people2ToAdd = people2.Skip(peopleCount).Take(2).ToList();
people2Count = people2.Count();
orderedList.AddRange(people2ToAdd);
if (people2Count >= people2.Count)
{
orderedList.AddRange(people1.Skip(people1Count));
break;
}
}
return orderedList;
}
This is pretty horrible code but does what you want it too. Basically we keep track of each lists index and have an int that keeps track of which list to use to populate the result array.
List<int> list1 = new List<int>() { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
List<int> list2 = new List<int>() { 2, 2, 2, 2, 2 };
int list1Counter = 0;
int list2Counter = 0;
int arraychecker = 1;
int[] resultArray = new int[list1.Count + list2.Count];
for (int i = 0; i < resultArray.Length; i++)
{
if (list1Counter < list1.Count && list2Counter < list2.Count)
{
if (arraychecker == 1 || arraychecker == 2)
{
resultArray[i] = list1[list1Counter];
list1Counter++;
arraychecker++;
}
else
{
resultArray[i] = list2[list2Counter];
list2Counter++;
arraychecker = 1;
}
}
else if (list1Counter < list1.Count)
{
resultArray[i] = list1[list1Counter];
list1Counter++;
}
else
{
resultArray[i] = list2[list2Counter];
list2Counter++;
}
}
You can calculate the index for each item and then order by that index.
var mergedList =
listA.Select((item, index) =>
new { Index = index / 2 * 3 + (i % 2), Item = item})
.Concat(listB.Select((item, index) =>
new { Index = i * 3 + 2, Item = item}))
.OrderBy(x => x.Index)
.Select(x => x.Item)
.ToList();
Or write a method. This is more efficient, since it doesn't need to sort; It just runs through each list once.
static IEnumerable<T> Alternate<T>(IEnumerable<T> sourceA, IEnumerable<T> sourceB) {
using (IEnumerator<T> eA = sourceA.GetEnumerator(), eB = sourceB.GetEnumerator()) {
bool aHasItems = true, bHasItems = true;
while (aHasItems || bHasItems) {
if (eA.MoveNext()) yield return eA.Current;
if (aHasItems = eA.MoveNext()) yield return eA.Current;
if (bHasItems = eB.MoveNext()) yield return eB.Current;
}
}
}
This is the best I could do:
string[] test1 = { "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A", "A" };
string[] test2 = { "B", "B" };
string[] result = test2.SelectMany((value, key) => test1.Skip(key * 2).Take(2).Concat(test2.Skip(key).Take(1))).ToArray();
result = result.Concat(test1.Skip(result.Length / 3 * 2).Take(test1.Length - result.Length / 3 * 1)).ToArray();
This will take 2 from array1 then 1 from array 2 then add whats left of the longer one. Output:
AABAABAAAAAAAAA

Compare two list of objects C#

I want to compare two list of objects. These lists contains the same type of objects. I create a new List in my programme and i want to compare it at the old list which is in the database. I get it with a stored procedure, then i put it into an object.
The old list : the new list :
*Category 1* Category 5
*Category 2* Category 6
*Category 3* *Category 4*
Category 4
Here the aim is to delete the first three Category in the old list, beacause they don't exist in the new list. And to delete the Category 4 in the new list because category 4 already exists in the old list.
It is possible to use à method like Equals() or use two foreach loop to browse the lists ?
Thanks for you answers and advises
You can use the linq, except and where
var a = new List<string> { "a", "b", "c" };
var b = new List<string> { "c", "d", "e" };
var temp = a.Intersect(b).ToList();
b = b.Except(a).ToList();
a = temp;
Output:
a: "c"
b: "d", "e"
Note: It is probably more efficient to do this without linq
var a = new List<string> { "a", "b", "c" };
var b = new List<string> { "c", "d", "e" };
for(int i = 0; i < a.Count; i++)
if(b.Contains(a[i]))
b.Remove(a[i]);
else
a.Remove(a[i--]);
If you need to compare based on a particular value
for(int i = 0; i < a.Count; i++)
{
var obj = b.Where(item => item.Category == a[i].Category);
if(obj.Any())
b.Remove(obj.First());
else
a.Remove(a[i--]);
}
It's not the most pretty of implementations but the fastest way you can do this is:
var tempA = new HashSet<int>(inputA.Select(item => item.Id));
var tempB = new HashSet<int>(inputB.Select(item => item.Id));
var resultA = new List<Category>(inputA.Count);
var resultB = new List<Category>(inputB.Count);
foreach (var value in inputA)
if (tempB.Contains(value.Id))
resultA.Add(value);
foreach (var value in inputB)
if (!tempA.Contains(value.Id))
resultB.Add(value);
resultA.TrimExcess();
resultB.TrimExcess();
// and if needed:
inputA = resultA;
inputB = resultB;
If you need more than item.id as unique then use a new Tuple such as:
inputA.Select(item => new Tuple<int, string>(item.Id, item.Title));
Another option is to override .GetHashCode in your category class such as:
public override int GetHashCode()
{
return Id.GetHashCode();
}
public override bool Equals(object obj)
{
var typedObj = obj as Category;
if (typedObj == null)
return false;
return Title == typedObj.Title && Id == typedObj.Id && Rank == typedObj.Rank;
}
I would solve this by sorting the two list and iterating over the first and second list. I would compare the current item of the first list to the current item from the second. If a match is found I remove the match from the second list and I move to the next item in both lists, otherwise the current item of the first list is removed from it and the iteration continues in the first list.

LINQ group by sum of property

I'm trying to create a LINQ query which is a derivative of SelectMany.
I have N items:
new {
{ Text = "Hello", Width = 2 },
{ Text = "Something else", Width = 1 },
{ Text = "Another", Width = 1 },
{ Text = "Extra-wide", Width = 3 },
{ Text = "Random", Width = 1 }
}
I would like the result to be a List<List<object>>(), where:
List<List<object>> = new {
// first "row"
{
{ Text = "Hello", Width = 2 },
{ Text = "Something else", Width = 1 },
{ Text = "Another", Width = 1 }
},
// second "row"
{
{ Text = "Extra-wide", Width = 3 },
{ Text = "Random", Width = 1 }
}
}
So the items are grouped into "rows" where Sum(width) in the internal List is less than or equal to a number (maxWidth - in my instance, 4).
It's kinda a derivative of GroupBy, but the GroupBy is dependent on earlier values in the array - which is where I get stumped.
Any ideas would be appreciated.
We can combine the ideas of LINQ's Aggregate method with a GroupWhile method to group consecutive items while a condition is met to build an aggregate value for the current group to be used in the predicate:
public static IEnumerable<IEnumerable<T>> GroupWhileAggregating<T, TAccume>(
this IEnumerable<T> source,
TAccume seed,
Func<TAccume, T, TAccume> accumulator,
Func<TAccume, T, bool> predicate)
{
using (var iterator = source.GetEnumerator())
{
if (!iterator.MoveNext())
yield break;
List<T> list = new List<T>() { iterator.Current };
TAccume accume = accumulator(seed, iterator.Current);
while (iterator.MoveNext())
{
accume = accumulator(accume, iterator.Current);
if (predicate(accume, iterator.Current))
{
list.Add(iterator.Current);
}
else
{
yield return list;
list = new List<T>() { iterator.Current };
accume = accumulator(seed, iterator.Current);
}
}
yield return list;
}
}
Using this grouping method we can now write:
var query = data.GroupWhileAggregating(0,
(sum, item) => sum + item.Width,
(sum, item) => sum <= 4);
You can sort of do that with the Batch method from MoreLinq library which is available as a NuGet package. The result is a List<IEnumerable<object>>. Here is the code:
class Obj
{
public string Text {get;set;}
public int Width {get;set;}
}
void Main()
{
var data = new [] {
new Obj { Text = "Hello", Width = 2 },
new Obj { Text = "Something else", Width = 1 },
new Obj { Text = "Another", Width = 1 },
new Obj { Text = "Extra-wide", Width = 3 },
new Obj { Text = "Random", Width = 1 }
};
var maxWidth = data.Max (d => d.Width );
var result = data.Batch(maxWidth).ToList();
result.Dump(); // Dump is a linqpad method
Output
I don't think you can do that with LINQ. One alternative approach would be the following:
var data = ... // original data
var newdata = new List<List<object>>();
int csum = 0;
var crow = new List<object>();
foreach (var o in data) {
if (csum + o.Width > 4) { //check if the current element fits into current row
newdata.Add(crow); //if not add current row to list
csum = 0;
crow = new List<object>(); //and create new row
}
crow.Add(o); //add current object to current row
csum += o.Width;
}
if (crow.Count() > 0) //last row
newData.Add(c);
EDIT: The other answer suggests to use Batch from the MoreLinq Library. In fact, the above source code, is more or less the same, what Batch does, but not only counting the elements in each batch but summing up the desired property. One could possibly generalize my code with a custom selector to be more flexible in terms of "batch size".

Combining lists in linq

in linq, is it possible to combine many lists (of the same type), such that two lists,
list 1 = {a,b,c} and list 2 = {x,y,z}
turns into {[1,a] , [1,b] , [1,c] , [2,x] , [2,y] , [2,z] }
where [] represents a pair containing a "list identifier"
The problem is from having decks of arbitrary cards, where each deck is a list in a collection of lists.
I'm trying to create a query such that I can select only cards in a certain deck, or cards similar to 2 or more decks.
This is probably a duplicate question, but I don't know how to search for the question further then I already have.
List<List<int>> lists;
var combined = lists.Select((l, idx) => new { List = l, Idx = idx })
.SelectMany(p => p.List.Select(i => Tuple.Create(p.Idx + 1, i)));
var list1 = new List<string>() {a,b,c};
var list2 = new List<string>() {x,y,z};
var combined = list1.Select(x => new { id = 1, v = x }).Concat(list2.Select(x => new { id = 2, v = x }));
Normally I'd suggest Enumerable.Zip for combining multiple lists, however you seem to actually want to concatenate multiple lists with a list counter.
public IEnumerable<Tuple<int,T>> Combine<T>(params IEnumerable<T>[] lists) {
return lists.Select((x,i) => x.Select(y => Tuple.Create(i+1,y))).SelectMany (l =>l);
}
UPDATE
Completely missed that SelectMany has the index option so the above code can be written as
public IEnumerable<Tuple<int,T>> Combine<T>(params IEnumerable<T>[] lists) {
return lists.SelectMany((x,i) => x.Select(y => Tuple.Create(i+1,y)));
}
Then you can do
var list1 = new List<string> { "a", "b", "c" };
var list2 = new List<string> { "x", "y", "z" };
var combined = Combine(list1,list2);
Combined will be enumerable of tuples, with Item1 being the list index identifier (starting at 1) and Item2 being the value.
This method will handle multiple lists so you could just as easily call it with:
var list3 = new List<string> { "f", "g" };
var combined = Combine(list1,list2,list3);
You can merge the lists like:
var first = new List<string> {"a","b","c"};
var second = new List<string> {"x","y","z"};
var merged = first.Select(item => new { ListIndex = 1, Value = item}).ToList();
merged.AddRange(second.Select(item => new { ListIndex = 2, Value = item});
//or use concat
var merged = first.Select(item => new { ListIndex = 1, Value = item});
.Concat(second.Select(item => new { ListIndex = 2, Value = item});
Alternatively if you have the sources in something like:
List<List<string>> lists = new List<List<string>>
{
new List<string> {"a","b","c"},
new List<string> {"x","y","z"}
};
you can do:
var merged = lists.SelectMany((item, index) =>
item.Select(s => new { ListIndex = index, Value = s}));
Note that this will produce a 0-based list, so if you really need a 1-base list, just do ListIndex = index +1.
Also, if you will use this a lot, I would create it as an specific entity, something like
struct ListIdentValue
{
public int ListIndex {get; private set;}
public string Value {get; private set;}
public ListIdentValue(int listIndex, string value) {...}
}
Try using Concat
new[] {'a','b','c'}
.Select(v=>new Tuple<int,char>(1, v))
.Concat(
new[] {'x','y','z'}.Select(v=>new Tuple<int,char>(2, v))
)
string[] a = { "a", "b", "c" };
string[] b = { "x", "z", "y" };
var t =
(
from ai in a
select new { listNo = 1, Item = ai }
).Union
(
from bi in b
select new { listNo = 2, Item = bi }
);
or
var t =
(
from ai in a
select new object[] { 1, ai }
).Union
(
from bi in b
select new object[] { 2, bi }
);

Categories

Resources