Use linq to match up pairs of rows in a set - c#

In the system I use modifications to data are received in pairs of rows old and new with a RowMod flag, for example deleted, added, updated and unchanged rows come through as:
RowID Data RowMod
Row1 "fish" ""
Row1 "fish" "D"
Row2 "cat" "A"
Row3 "fox" ""
Row3 "dog" "U"
Row4 "mouse" ""
I'd like to match these up using the RowID that each row has and get something like:
RowID OldData NewData RowMod
Row1 "fish" null "D"
Row2 null "cat" "A"
Row3 "fox" "dog" "U"
Row4 "mouse" "mouse" ""

class Program
{
static void Main(string[] args)
{
IEnumerable<DataRow> rows = new[]
{
new DataRow(1,"fish",""),
new DataRow(1,"fish","D"),
new DataRow(2,"cat","A"),
new DataRow(3,"fox",""),
new DataRow(3,"dog","U"),
new DataRow(4,"mouse","")
};
var result = rows
.GroupBy(x => x.Id)
.Select(g => new
{
Count = g.Count(),
Id = g.First().Id,
FirstRow = g.First(),
LastRow = g.Last()
}).Select(item => new
{
RowId = item.Id,
OldData = item.Count == 1 && item.FirstRow.RowMod != "" ? null : item.FirstRow.Data,
NewData = item.LastRow.RowMod == "D" ? null : item.LastRow.Data,
RowMod = item.LastRow.RowMod
});
//Or using query syntax
var result2 = from x in rows
orderby x.Id, x.RowMod
group x by x.Id into g
select new
{
RowId = g.First().Id,
OldData = g.Count() == 1 && g.First().RowMod != "" ? null : g.First().Data,
NewData = g.Last().RowMod == "D" ? null : g.Last().Data,
RowMod = g.Last().RowMod
};
// Test
Console.WriteLine("RowID\tOldData\tNewData\tRowMod");
foreach (var item in result)
{
Console.WriteLine("{0}\t'{1}'\t'{2}'\t'{3}'",item.RowId,item.OldData ?? "null",item.NewData ?? "null",item.RowMod);
}
}
}
public class DataRow
{
public int Id { get; set; }
public string Data { get; set; }
public string RowMod { get; set; }
public DataRow(int id, string data, string rowMod)
{
Id = id;
Data = data;
RowMod = rowMod;
}
}
Output:
RowID OldData NewData RowMod
1 'fish' 'null' 'D'
2 'null' 'cat' 'A'
3 'fox' 'dog' 'U'
4 'mouse' 'mouse' ''

I am not sure if this is the best way to achieve your requirement but this is what I have:-
var result = rows.GroupBy(x => x.RowId)
.Select(x =>
{
var firstData = x.FirstOrDefault();
var secondData = x.Count() == 1 ? x.First().RowMod == "A" ? firstData : null
: x.Skip(1).FirstOrDefault();
return new
{
RowId = x.Key,
OldData = firstData.RowMod == "A" ? null : firstData.Data,
NewData = secondData != null ? secondData.Data : null,
RowMod = String.IsNullOrEmpty(firstData.RowMod) && secondData != null ?
secondData.RowMod : firstData.RowMod
};
});
Working Fiddle.

Getting the two parts of the intended object can be done iteratively:
foreach(var rowId in myList.Select(x => x.RowId).Distinct())
{
//get the left item
var leftItem = myList.SingleOrDefault(x => x.RowId == rowId && String.IsNullOrWhiteSpace(x.rowmod);
//get the right item
var rightItem = myList.SingleOrDefault(x => x.RowId == rowId && !String.IsNullOrWhiteSpace(x.rowmod);
}
Your question doesn't specify how you create the second object. Is it a different class?
Either way, you can extrapolate from the above snippet that either item might be null if it doesn't exist in the original set.
All you need to do is use those found objects to create your new object.

While I love LINQ a lot, I don't think it is appropriate here as you want to buffer some values while iterating. If you do this with LINQ, it will be at best not performing well, at worst it will iterate the collection multiple times. It also looks way cleaner this way in my opinion.
IEnumerable<TargetClass> MapOldValues(IEnumerable<SourceClass> source)
{
var buffer = new Dictionary<string, string>();
foreach(var item in source)
{
string oldValue;
buffer.TryGetValue(item.RowId, out oldValue);
yield return new TargetClass
{
RowId = item.RowId,
OldData = oldValue,
NewData = (item.RowMod == "D" ? null : item.Data),
RowMod = item.RowMod };
// if the rows come sorted by ID, you can clear old values from
// the buffer to save memory at this point:
// if(oldValue == null) { buffer.Clear(); }
buffer[item.RowId] = item.Data;
}
}
if you then only want the latest updates, you can go with LINQ:
var latestChanges = MapOldValues(source).GroupBy(x => x.RowId).Select(x => x.Last());

I guess there are more elegant ways to do it, but this produces the output you expect:
public class MyClass
{
public int RowID { get; set; }
public string Data { get; set; }
public string RowMod { get; set; }
}
var result = (from id in myList.Select(x => x.RowID).Distinct()
let oldData = myList.Where(x => x.RowID == id).SingleOrDefault(x => x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => x.RowMod.Equals("")).Data
: null
let newData = myList.Where(x => x.RowID == id).SingleOrDefault(x => !x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => !x.RowMod.Equals("")).Data
: null
let rowMod = myList.Where(x => x.RowID == id).SingleOrDefault(x => !x.RowMod.Equals("")) != null
? myList.Where(x => x.RowID == id).Single(x => !x.RowMod.Equals("")).RowMod
: null
select new
{
RowID = id,
OldData = oldData,
NewData = rowMod == null ? oldData : rowMod.Equals("D") ? null : newData,
RowMod = rowMod
});
foreach (var item in result)
{
Console.WriteLine("{0} {1} {2} {3}", item.RowID, item.OldData ?? "null", item.NewData ?? "null", item.RowMod ?? "-");
}

Related

How to make a custom order with Dictionary in C#?

I am trying to sort all the items in a list according to their status. I tried to do it in the following way with the Dictionary but it gives me an error. Could you help me to do it correctly?
The order according to their status should be as follows: Activated, Paused, Expired and Drained.
var table = _plapsaContext.Coupons;
var query = _plapsaContext.Coupons.AsQueryable();
query.Select(e => new CouponDto{
Id = e.Id,
StartingDate = e.StartingDate,
EndingDate = e.EndingDate,
Amount = e.Amount,
TotalCoupons = e.TotalCoupons,
MinimumAmount = e.MinimumAmount,
RestCoupons = e.RestCoupons,
ContractId = e.ContractId,
Status = (e.EndingDate.Date < DateTime.Now.Date && e.Status != CouponStatus.Paused && e.Status != CouponStatus.Drained) ? CouponStatus.Expired : e.Status,
ContractCode = e.ContractId.HasValue ? e.Contract.Code.ToString() : null,
OwnerAssociationCode = e.OwnerAssociactionCode,
BuildingManagerName = e.ContractId.HasValue ? e.Contract.BuildingManagerName : null,
ExcludeFunctionalUnits = e.ExcludeFunctionalUnits
});
Dictionary<CouponStatus, int> orderCoupons = new Dictionary<CouponStatus, int>
{
{ CouponStatus.Activated, 0 },
{ CouponStatus.Paused, 1 },
{ CouponStatus.Expired, 2 },
{ CouponStatus.Drained, 3 },
};
Array.Sort(query.ToArray(), (p, q) => orderCoupons[p.Status].CompareTo(orderCoupons[q.Status]));
Console.WriteLine(query);
return (IQueryable<CouponDto>)query;
I hope you can help me! Thank you very much!
This query should sort in desired way. I don't think that you need sorting dictionary here. The following query will sort data on the server side.
var query = _plapsaContext.Coupons.AsQueryable();
var dtoQuery = query
.Select(e => new CouponDto
{
Id = e.Id,
StartingDate = e.StartingDate,
EndingDate = e.EndingDate,
Amount = e.Amount,
TotalCoupons = e.TotalCoupons,
MinimumAmount = e.MinimumAmount,
RestCoupons = e.RestCoupons,
ContractId = e.ContractId,
Status = (e.EndingDate.Date < DateTime.Now.Date && e.Status != CouponStatus.Paused && e.Status != CouponStatus.Drained) ? CouponStatus.Expired : e.Status,
ContractCode = e.ContractId.HasValue ? e.Contract.Code.ToString() : null,
OwnerAssociationCode = e.OwnerAssociactionCode,
BuildingManagerName = e.ContractId.HasValue ? e.Contract.BuildingManagerName : null,
ExcludeFunctionalUnits = e.ExcludeFunctionalUnits
});
dtoQuery = dtoQuery
.OrderBy(e => e.Status == CouponStatus.Activated ? 0
: e.Status == CouponStatus.Paused ? 1
: e.Status == CouponStatus.Expired ? 2
: e.Status == CouponStatus.Drained : 3
);
return dtoQuery;

Update list by another list (linq)

I have List of object of class "Data" that look like:
class Data
{
int code;
string name;
...
DateTime date_update;
}
and I have another list of class, like:
class RefCodes
{
int old_code;
int new_code;
string new_name;
DateTime date_update;
}
The list of "Data" contains like 1,000 objects.
The list of "RefCodes" contains like 30 objects.
I need to replace in list "Data",
the fields:
"code" to be with value of "new_code",
and the "name" to be with value of "new_name".
The replacement need to be only for the objects that their code exist in list "RefCodes".
by the query: if code in Data.code == RefCodes.old_code
How can I do it?
I think you're looking for this:
foreach (var rcodeObj in RefCode)
{
foreach(var obj in (Data.Where(t => t.code == rcodeObj.old_code)))
{
obj.code = rcodeObj.new_code;
obj.name = rcodeObj.new_name;
}
}
If you are using C#6 you could use linq to do something like this
var updatedData = data.Select(x => new Data
{
code = refCodes.FirstOrDefault(y => y.old_code == x.code)?.new_code ?? x.code,
name = refCodes.FirstOrDefault(y => y.old_code == x.code)?.new_name ?? x.name,
});
You can use the following code:
foreach (var x in DataList)
{
var itemRefCode = RefCodesList.FirstOrDefault(d => d.old_code == x.code);
if (itemRefCode != null)
{
x.code = itemRefCode.new_code;
x.name = itemRefCode.new_name;
}
}
You can iterate through each of the lists and update the values as follows. Here I am using some sample inputs as shown below. Note that I am considering the fields of the classes to be public, for simplicity:
List<Data> dataList = new List<Data>
{
new Data { code = 1, name = "A" },
new Data { code = 2, name = "B" },
new Data { code = 10, name = "C" },
};
List<RefCodes> refList = new List<RefCodes>
{
new RefCodes { old_code = 1, new_code = 11, new_name = "X" },
new RefCodes { old_code = 2, new_code = 22, new_name = "Y" }
};
Console.WriteLine("Before");
dataList.ForEach(data => Console.WriteLine(data.code + ": " + data.name));
Console.WriteLine("");
Here is the code to do the updating:
foreach (var refCodes in refList)
{
foreach (var data in dataList)
{
if (data.code == refCodes.old_code)
{
data.code = refCodes.new_code;
data.name = refCodes.new_name;
}
}
}
Console.WriteLine("After");
dataList.ForEach(data => Console.WriteLine(data.code + ": " + data.name));
Output:
Before
1: A
2: B
10: C
After
11: X
22: Y
10: C
Would this solve your problem:
public void Update( List<Data> data, List<RefCodes> refCodes )
{
List<RefCodes> differences = refCodes
.Where( r => data.Any( d => r.old_code == d.code ) )
.ToList();
differences.ForEach( ( RefCodes item ) =>
{
Data element = data.FirstOrDefault( d => d.code == item.old_code );
element.code = item.new_code;
element.name = item.new_name;
} );
}
What you need is a Left Outer Join.
For example,
IEnumerable<Data> query = from data in dataList
join refCode in refList on data.code equals refCode.old_code into joined
from subCode in joined.DefaultIfEmpty()
select new Data
{
code = subCode?.new_code ?? data.code,
name = subCode?.new_name ?? data.name,
date_update = subCode == null ? data.date_update : DateTime.Now
};
will return a sequence with the result you expect.
**Let say tempAllocationR is list 1 and tempAllocationV is List2 **
var tempAllocation = new List<Object>();
if (tempAllocationR.Count > 0 && tempAllocationV.Count > 0)
{
foreach (TempAllocation tv in tempAllocationV)
{
var rec = tempAllocationR.FirstOrDefault(tr => tr.TERR_ID == tv.TERR_ID && tr.TERR == tv.TERR && tr.Team == tv.Team);
if (rec != null)
{
rec.Vyzulta = tv.Vyzulta;
}
else
{
tempAllocationR.Add(tv);
}
}
tempAllocation = tempAllocationR;
}
else if (tempAllocationV.Count == 0 && tempAllocationR.Count > 0)
{
tempAllocation = tempAllocationR;
}
else if (tempAllocationR.Count == 0 && tempAllocationV.Count > 0)
{
tempAllocation = tempAllocationV;
}

Linq query with All rule in array

I have a list of images and I want to search for multiple keywords with a BOTH rules
For example if I search for "dancing child" I want to show a list of items with both keywords dancing and child
I implemented a query something like this:
List<string> target_keywords = //an array contains Keywords to Lookup
var RuleAny_results = (from imageItem in images
select new{ imageItem,
Rank =target_keywords.Any(x => imageItem.Title != null && imageItem.Title.ToLower().Contains(x)) ? 5 :
target_keywords.Any(x => imageItem.Name != null && imageItem.Name.ToLower().Contains(x)) ? 4 :
0
}).OrderByDescending(i => i.Rank);
//exclude results with no match (ie rank=0 ) and get a Distinct set of items
_searchResult = (from item in RuleAny_results
where item.Rank != 0
select item.imageItem).Distinct().ToList();
But this will return results with any of the items in the target_keywords, e.g. if I search for "dancing child" above code returns list of items with any of the keywords dancing or child. But I want the list with Both dancing and child keywords only
So how can I convert the query so that it fetch all records that contains BOTH keywords?
System.Linq.Enumerable::All is what you want.
using System.Linq;
using System.Collections.Generic;
struct ImageItem {
public string Title { get; set; }
public string Name { get; set; }
}
bool Contains(string toSearch, string x) {
return toSearch != null && toSearch.ToLower().Contains(x);
}
IEnumerable<ImageItem> FilterItems(IEnumerable<string> targetKeywords, IEnumerable<ImageItem> items) {
return items.Where(item => targetKeywords.All(x => Contains(item.Name, x) || Contains(item.Title, x)));
}
Try this:--
you have to just replace Any keyword in syntax with All
And one more rank condition for all keyword in both fields
Replace target_keywords.Any( with target_keywords.All(
List<string> target_keywords = //an array contains Keywords to Lookup
var RuleAny_results = (from imageItem in images
select new{ imageItem,
Rank =target_keywords.Any(x => imageItem.Title != null && imageItem.Title.ToLower().Contains(x)) ? 5 :
target_keywords.All(x => imageItem.Name != null && imageItem.Name.ToLower().Contains(x)) ? 4 :
target_keywords.All(x => (imageItem.Name != null && imageItem.Name.ToLower().Contains(x)) || imageItem.Title != null && imageItem.Title.ToLower().Contains(x)) ? 3 :
0
}).OrderByDescending(i => i.Rank);
//exclude results with no match (ie rank=0 ) and get a Distinct set of items
_searchResult = (from item in RuleAny_results
where item.Rank != 0
select item.imageItem).Distinct().ToList();
class ImageDemo
{
public string Title { get; set; }
public string Name { get; set; }
}
static void TestCode()
{
List<string> target_keywords = new List<string>(){"dancing","child"};
List<ImageDemo> images = new List<ImageDemo>()
{
new ImageDemo{Title = "dancing"} ,
new ImageDemo{Name = "child"} ,
new ImageDemo{Title = "child", Name="dancing"} ,
new ImageDemo{Title = "dancing", Name="child"} ,
new ImageDemo{Name="dancing child"} ,
new ImageDemo{Title="dancing child"}
};
var searchFuncs = target_keywords.Select(x =>
{
Func<ImageDemo, bool> func = (img) =>
{
return (img.Title ?? string.Empty).Contains(x) || (img.Name ?? string.Empty).Contains(x);
};
return func;
});
IEnumerable<ImageDemo> result = images;
foreach (var func in searchFuncs)
{
result = result.Where(x => func(x));
}
foreach (var img in result)
{
Console.WriteLine(string.Format("Title:{0} Name:{1}", img.Title, img.Name));
}
}
is it the right code you want now?

How to use local variables in a lambda expression

I have 2 list object of type of some class,
class person
{
public string id { get; set; }
public string name { get; set; }
}
List<person> pr = new List<person>();
pr.Add(new person { id = "2", name = "rezoan" });
pr.Add(new person { id = "5", name = "marman" });
pr.Add(new person { id = "3", name = "prithibi" });
List<person> tem = new List<person>();
tem.Add(new person { id = "1", name = "rezoan" });
tem.Add(new person { id = "2", name = "marman" });
tem.Add(new person { id = "1", name = "reja" });
tem.Add(new person { id = "3", name = "prithibi" });
tem.Add(new person { id = "3", name = "prithibi" });
Now i have to get all the ids from "pr" ListObject that has no entry or odd number of entries in the "tem" ListObejct. using lamda.
To do this i have used,
HashSet<string> inconsistantIDs = new HashSet<string>(pr.Select(p => p.id).Where(p => tem.FindAll(t => t.id == p).Count == 0 || tem.FindAll(t => t.id == p).Count % 2 != 0));
and it works fine.
but you can see from the code i have used tem.FindAll(t => t.id == p).Count twice to comapre with ==0 and %2!=0.
Is there any way to use tem.FindAll(t => t.id == p).Count once and
save it to a temporary variable and then compare this variable with
==0 and %2!=0.
More simply i just want to use it once for two condition here.
Use a statement lambda instead of an expression lambda
var inconsistantIDs = new HashSet<string>(
pr.Select(p => p.id).Where(p =>
{
var count = tem.FindAll(t => t.id == p).Count;
return count == 0 || count % 2 != 0;
}
));
Perhaps simply:
var query = pr.Where(p => { int c = tem.Count(p2 => p.id == p2.id); return c == 0 || c % 2 != 0; });
returns two persons:
2 "rezoan"
5 "marman"
Besides statement lambda you can use let clause:
HashSet<string> inconsistantIDs = new HashSet<string>(
from p in pr
let count = tem.FindAll(t => t.id == p).Count
where count == 0 || count % 2 != 0
select p.id
);
HashSet<string> inconsistantIDs = new HashSet<string>(
pr.Select(p => new { Id = p.id, Cnt = tem.FindAll(t => t.id == p.id).Count() })
.Where(p => p.Cnt == 0 || p.Cnt % 2 != 0)
.Select(p => p.Id);
On a side note, strictly performance wise, you would get better performance if you created a hash mapping of each ID to its count and then search it in a loop.
Right now you have a O(n*m) algorithm, which would be reduced to O(n+m):
// create a map (id -> count), O(m) operation
var dictionary = new Dictionary<string, int>();
foreach (var p in tem)
{
var counter = 0;
dictionary.TryGetValue(p.id, out counter);
counter++;
dictionary[p.id] = counter;
}
// search the map, O(n) operation
var results = new HashSet<string>();
foreach (var p in pr)
{
var counter = 0;
dictionary.TryGetValue(p.id, out counter);
if (counter == 0 || counter % 2 != 0)
results.Add(p.id);
}

Linq Conditional .Any() Select

How can I perform a conditional select on a column value, where I have a preference over which value is returned. If I can't find the top choice, I settle on the next, if available, and then if not the next, etc. As it looks right now, it would take 3 total queries. Is there a way to simplify this further?
var myResult = string.Empty;
if (myTable.Where(x => x.ColumnValue == "Three").Any())
{
myResult = "Three"; // Can also be some list.First().Select(x => x.ColumnValue) if that makes it easier;
}
else if (myTable.Where(x => x.ColumnValue == "One").Any())
{
myResult = "One";
}
else if (myTable.Where(x => x.ColumnValue == "Two").Any())
{
myResult = "Two";
}
else
{
myResult = "Four";
}
You could use a string[] for your preferences:
string[] prefs = new[]{ "One", "Two", "Three" };
string myResult = prefs.FirstOrDefault(p => myTable.Any(x => x.ColumnValue == p));
if(myResult == null) myResult = "Four";
Edit Enumerable.Join is a very efficient hash table method, it also needs only one query:
string myResult = prefs.Select((pref, index) => new { pref, index })
.Join(myTable, xPref => xPref.pref, x => x.ColumnValue, (xPref, x) => new { xPref, x })
.OrderBy(x => x.xPref.index)
.Select(x => x.x.ColumnValue)
.DefaultIfEmpty("Four")
.First();
Demo
I wrote an extension method that effectively mirrors Tim Schmelter's answer (was testing this when he posted his update. :-()
public static T PreferredFirst<T>(this IEnumerable<T> data, IEnumerable<T> queryValues, T whenNone)
{
var matched = from d in data
join v in queryValues.Select((value,idx) => new {value, idx}) on d equals v.value
orderby v.idx
select new { d, v.idx };
var found = matched.FirstOrDefault();
return found != null ? found.d : whenNone;
}
// usage:
myResult = myTable.Select(x => x.ColumnValue)
.PreferredFirst(new [] {"Three", "One", "Two"}, "Four");
I've written one that will quit a little more early:
public static T PreferredFirst<T>(this IEnumerable<T> data, IList<T> orderBy, T whenNone)
{
// probably should consider a copy of orderBy if it can vary during runtime
var minIndex = int.MaxValue;
foreach(var d in data)
{
var idx = orderBy.IndexOf(d);
if (idx == 0) return d; // best case; quit now
if (idx > 0 && idx < minIndex) minIndex = idx;
}
// return the best found or "whenNone"
return minIndex == int.MaxValue ? whenNone : orderBy[minIndex];
}
I use a weighted approach in SQL where I assign a weight to each conditional value. The solution would then be found by finding the highest or lowest weight depending on your ordering scheme.
Below would be the equivalent LINQ query. Note that in this example I am assigning a lower weight a higher priority:
void Main()
{
// Assume below list is your dataset
var myList =new List<dynamic>(new []{
new {ColumnKey=1, ColumnValue ="Two"},
new {ColumnKey=2, ColumnValue ="Nine"},
new {ColumnKey=3, ColumnValue ="One"},
new {ColumnKey=4, ColumnValue ="Eight"}});
var result = myList.Select(p => new
{
ColVal = p.ColumnValue,
OrderKey = p.ColumnValue == "Three" ? 1 :
p.ColumnValue == "One" ? 2 :
p.ColumnValue == "Two" ? 3 : 4
}).Where(i=> i.OrderKey != 4)
.OrderBy(i=>i.OrderKey)
.Select(i=> i.ColVal)
.FirstOrDefault();
Console.WriteLine(result ?? "Four");
}
How about something like this:
var results = myTable.GroupBy(x => x.ColumnValue).ToList();
if (results.Contains("Three")) {
myResult = "Three";
} else if (results.Contains("One")) {
myResult = "One";
} else if (results.Contains("Two")) {
myResult = "Two";
} else {
myResult = "Four";
}

Categories

Resources