Linq to objects - Search collection with value from other collection - c#

I've tried to search SO for solutions and questions that could be similar to my case.
I got 2 collections of objects:
public class BRSDocument
{
public string IdentifierValue { get; set;}
}
public class BRSMetadata
{
public string Value { get; set;}
}
I fill the list from my datalayer:
List<BRSDocument> colBRSDocuments = Common.Instance.GetBRSDocuments();
List<BRSMetadata> colBRSMetadata = Common.Instance.GetMessageBRSMetadata();
I now want to find that one object in colBRSDocuments where x.IdentifierValue is equal to the one object in colBRSMetadata y.Value. I just need to find the BRSDocument that matches a value from the BRSMetadata objects.
I used a ordinary foreach loop and a simple linq search to find the data and break when the value is found. I'm wondering if the search can be done completely with linq?
foreach (var item in colBRSMetadata)
{
BRSDocument res = colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value);
if (res != null)
{
//Do work
break;
}
}
Hope that some of you guys can push me in the right direction...

Why not do a join?
var docs = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select d;
If there's only meant to be one then you can do:
var doc = docs.Single(); // will throw if there is not exactly one element
If you want to return both objects, then you can do the following:
var docsAndData = from d in colBRSDocuments
join m in colBRSMetadata on d.IdentiferValue equals m.Value
select new
{
Doc = d,
Data = m
};
then you can access like:
foreach (var dd in docsAndData)
{
// dd.Doc
// dd.Data
}

Use Linq ?
Something like this should do the job :
foreach (var res in colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).Where(res => res != null))
{
//Do work
break;
}
If you are just interested by the first item, then the code would be :
var brsDocument = colBRSMetadata.Select(item => colBRSDocuments.FirstOrDefault(x => x.IdentifierValue == item.Value)).FirstOrDefault(res => res != null);
if (brsDocument != null)
//Do Stuff

Related

Linq select Objects from List which have empty String

is it possible in Linq to select from IEnumerable of this object
public class Foo
{
public int Id { get; set; }
public string Type { get; set; }
}
where Type is "" ?
if I loop over the list with that
foreach (Foo f in dataFoos)
{
Console.WriteLine(f.Id + f.Type);
}
it looks like
1one
2
3three
I have tried
var emptyType0 = dataFoos.Where(f => f.Type.Length <= 1);
var emptyType1 = dataFoos.Where(f => f.Type == null || f.Type == "");
both did not return any result. Any hint on how to properly check if String values are empty ?
if I do that
var df = dataFoos.Where(f => String.IsNullOrWhiteSpace(f.Type));
foreach (Foo f in df)
{
Console.WriteLine(f.Id + f.Type);
}
var df1 = dataFoos.Where(f => !String.IsNullOrWhiteSpace(f.Type));
foreach (Foo f in df1)
{
Console.WriteLine(f.Id + f.Type);
}
the second loop does not return any value
I am using dotnetcore c#. Thanks for any hint
This should cover almost every type of null/blank/just whitespace
var emptyType1 = foos.Where(f => String.IsNullOrWhiteSpace(f.Type));
but more likely what you want to do is exclude those - not include them
var dataFoos = foos.Where(f => !String.IsNullOrWhiteSpace(f.Type));
foreach (Foo f in dataFoos)
{
Console.WriteLine(f.Id + f.Type);
}

How to find out duplicate Elements in Xelement

I am trying to find out the duplicate Elements in XElement , and make a generic function to remove duplicates .Something like:
public List<Xelement>RemoveDuplicatesFromXml(List<Xelement> xele)
{ // pass the Xelement List in the Argument and get the List back , after deleting the duplicate entries.
return xele;
}
the xml is as follows:
<Execute ID="7300" Attrib1="xyz" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
<Execute ID="7301" Attrib1="xyz" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
<Execute ID="7302" Attrib1="xyz1" Attrib2="abc" Attrib3="mno" Attrib4="pqr" Attrib5="BCD" />
I want get duplicates on every attribute excluding ID ,and then delete the one having lesser ID.
Thanks,
You can implement custom IEqualityComparer for this task
class XComparer : IEqualityComparer<XElement>
{
public IList<string> _exceptions;
public XComparer(params string[] exceptions)
{
_exceptions = new List<string>(exceptions);
}
public bool Equals(XElement a, XElement b)
{
var attA = a.Attributes().ToList();
var attB = b.Attributes().ToList();
var setA = AttributeNames(attA);
var setB = AttributeNames(attB);
if (!setA.SetEquals(setB))
{
return false;
}
foreach (var e in setA)
{
var xa = attA.First(x => x.Name.LocalName == e);
var xb = attB.First(x => x.Name.LocalName == e);
if (xa.Value == null && xb.Value == null)
continue;
if (xa.Value == null || xb.Value == null)
return false;
if (!xa.Value.Equals(xb.Value))
{
return false;
}
}
return true;
}
private HashSet<string> AttributeNames(IList<XAttribute> e)
{
return new HashSet<string>(e.Select(x =>x.Name.LocalName).Except(_exceptions));
}
public int GetHashCode(XElement e)
{
var h = 0;
var atts = e.Attributes().ToList();
var names = AttributeNames(atts);
foreach (var a in names)
{
var xa = atts.First(x => x.Name.LocalName == a);
if (xa.Value != null)
{
h = h ^ xa.Value.GetHashCode();
}
}
return h;
}
}
Usage:
var comp = new XComparer("ID");
var distXEle = xele.Distinct(comp);
Please note that IEqualityComparer implementation in this answer only compare LocalName and doesn't take namespace into considerataion. If you have element with duplicate local name attribute, then this implementation will take the first one.
You can see the demo here : https://dotnetfiddle.net/w2DteS
Edit
If you want to
delete the one having lesser ID
It means you want the largest ID, then you can chain the .Distinct call with .Select.
var comp = new XComparer("ID");
var distXEle = xele
.Distinct(comp)
.Select(z => xele
.Where(a => comp.Equals(z, a))
.OrderByDescending(a => int.Parse(a.Attribute("ID").Value))
.First()
);
It will guarantee that you get the element with largest ID.
Use Linq GroupBy
var doc = XDocument.Parse(yourXmlString);
var groups = doc.Root
.Elements()
.GroupBy(element => new
{
Attrib1 = element.Attribute("Attrib1").Value,
Attrib2 = element.Attribute("Attrib2").Value,
Attrib3 = element.Attribute("Attrib3").Value,
Attrib4 = element.Attribute("Attrib4").Value,
Attrib5 = element.Attribute("Attrib5").Value
});
var duplicates = group1.SelectMany(group =>
{
if(group.Count() == 1) // remove this if you want only duplicates
{
return group;
}
int minId = group.Min(element => int.Parse(element.Attribute("ID").Value));
return group.Where(element => int.Parse(element.Attribute("ID").Value) > minId);
});
Solution above will remove elements with lesser ID which have duplicates by attributes.
If you want return only elements which have duplicates then remove if fork from last lambda

Remove 'duplicates' from a list of pairings

Title could be misleading, so an example:
I have a class:
class Pair
{
Book Book1;
Book Book2;
}
I have a list of these:
var list = new List<Pair>();
list.Add(new Pair() {
Book1 = new Book() { Id = 123 },
Book2 = new Book() { Id = 456 }
});
list.Add(new Pair() {
Book1 = new Book() { Id = 456 },
Book2 = new Book() { Id = 123 }
});
Now, despite the fact the books are 'flipped', my system should treat these as duplicates.
I need a method to remove one of these 'duplicates' from the list (any one - so let's say the first to make it simple).
What I've Tried
var tempList = new List<Pair>();
tempList.AddRange(pairs);
foreach (var dup in pairs)
{
var toRemove = pairs.FirstOrDefault(o => o.Book1.Id == dup.Book2.Id
&& o.Book2.Id == dup.Book1.Id);
if (toRemove != null)
tempList.Remove(toRemove);
}
return tempList;
This returns no items (given the example above), as both Pair objects would satisfy the condition in the lambda, I only one to remove one though.
NOTE: This wouldn't happen if I just removed the element from the collection straight away (rather than from a temporary list) - but then I wouldn't be able to iterate over it without exceptions.
You can set up an IEqualityComparer<Pair> concrete class and pass that to the .Distinct() method:
class PairComparer : IEqualityComparer<Pair>
{
public bool Equals(Pair x, Pair y)
{
return (x.Book1.Id == y.Book1.Id && x.Book2.Id == y.Book2.Id)
|| (x.Book1.Id == y.Book2.Id && x.Book2.Id == y.Book1.Id);
}
public int GetHashCode(Pair obj)
{
return obj.Book1.Id.GetHashCode() ^ obj.Book2.Id.GetHashCode();
}
}
And then use it like so:
var distinctPairs = list.Distinct(new PairComparer());
The problem is that you are removing the both duplicates.
Try this:
var uniquePairs = list.ToLookup( p => Tuple.Create(Math.Min(p.Book1.Id, p.Book2.Id), Math.Max(p.Book1.Id, p.Book2.Id)) ).Select( g => g.First() ).ToList();
I would use the following
foreach (var dup in pairs)
{
var toRemove = pairs.FirstOrDefault(o => o.Book1.Id == dup.Book2.Id
&& o.Book2.Id == dup.Book1.Id
&& o.Book1.Id > o.Book2.Id);
if (toRemove != null)
tempList.Remove(toRemove);
}
This will specifically remove the duplicate that is "out of order". But this (and your original) will fail if the duplicate pairs have the books in the same order.
A better solution (since we're looping over ever pair anyways) would be to use a HashSet
var hashSet = new HashSet<Tuple<int,int>>();
foreach (var item in pairs)
{
var tuple = new Tuple<int,int>();
if (item.Book1.Id < item.Book2.Id)
{
tuple.Item1 = item.Book1.Id;
tuple.Item2 = item.Book2.Id;
}
else
{
tuple.Item1 = item.Book2.Id;
tuple.Item2 = item.Book1.Id;
}
if (hashSet.Contains(tuple))
{
tempList.Remove(dup);
}
else
{
hashSet.Add(tuple);
}
}
I've managed to find a solution, but it's one I'm not happy with. It seems too verbose for the job I'm trying to do. I'm now doing an additional check to see whether a duplicate has already been added to the list:
if(toRemove != null && tempList.Any(o => o.Book1.Id == toRemove.Book2.Id
&& o.Book2.Id == toRemove.Book1.Id))
tempList.Remove(toRemove);
I'm very much open to alternative suggestions.

List Remove Item

I have this following Codes for adding a value in my List.
public class Temp {
public object Id { get; set; }
public object Amount { get; set; }
public object TrasactionDateTime { get; set; }
}
private List<Temp> list = new List<Temp>();
Adding
list.Add(new Temp{ Id = GetData["Id"], Amount = GetData["Amount"], TrasactionDateTime = GetData["TransactionDateTime"] });
How do i remove and item in the list?
example
list.Remove(Id = "1");
You need to find the item from the List with Id = "1" and then remove it.
var item = list.FirstOrDefault(r=> r.Id.ToString() == "1");
if(item != null)
list.Remove(item);
You can also remove the item based on the index using List<T>.RemoveAt()
Try to use the .Find
The first element that matches the conditions defined by the specified
predicate, if found; otherwise, the default value for type T.
List<Temp> list = new List<Temp>();
var f = list.Find(c => c.Id == 1);
if (f == null) return;
var x = list.Remove(f);
list.RemoveAll(s => s.Id == "1");
list.RemoveAll (s => s.Id == "1"); // remove by condition
Note that this will remove all temps with given id.
If you need to remove the first temp found by id, first use First method to find him, and then call Remove for the instance:
var firstMatch = list.First (s => s.Id == "1");
list.Remove (firstMatch);
If you want to ensure there is only one temp with given id before removing him, use Single in a similar fashion:
var onlyMatch = list.Single (s => s.Id == "1");
list.Remove (onlyMatch);
Note that Single call fails if there is not exactly one item matching the predicate.

Lambda expression with two objects

I know that this can be rewritten using a lambda expression. But I cant seem to figure it out. does anyone have an opinion on how it should be written using a lambda.
foreach (var _systemItem in _systemData)
{
foreach (var _institutionItem in _institutionData)
{
if (_systemItem.LookupValue == _institutionItem.OriginalSystemLookupValue)
{
_values.Add(new LookupValue()
{
DisplayText = _institutionItem.LookupText,
Value = _institutionItem.LookupValue
});
}
else
{
_values.Add(new LookupValue()
{
DisplayText = _systemItem.LookupText,
Value = _systemItem.LookupValue
});
}
}
}
Like this:
values.AddRange(from s in _systemData
from i in institutionData
select s.LookupValue == i.OriginalSystemLookupValue ?
new LookupValue {
DisplayText = _institutionItem.LookupText,
Value = _institutionItem.LookupValue
}
: new LookupValue {
DisplayText = _systemItem.LookupText,
Value = _systemItem.LookupValue
}
);
Is _values a List<LookupValue> which is empty to start with? If so, that look might look like this:
_values = (from x in _systemData
from y in _institutionData
let item = x.LookupValue == y.OriginalSystemLookupValue ? x : y
select new LookupValue { DisplayText = item.LookupText,
Value = item.LookupValue })
.ToList();
That assumes that _systemItem and _institutionItem are the same type. If they're unrelated types, you might want to give them a common interface that defines LookupText and LookupValue (or even a ToLookupValue method) and then cast one of the operands in the conditional operator to the interface. For example:
_values = (from x in _systemData
from y in _institutionData
let item = x.LookupValue == y.OriginalSystemLookupValue
? (ILookupSource) x : y
select item.ToLookupValue())
.ToList();
Sure, I have an opinion. I'd write it like this:
var pairs = _systemData.SelectMany(s =>
_institutionData.Select(i => new { System = s, Institution = i }));
_values.AddRange(pairs.Select(x =>
{
bool match = x.System.LookupValue == x.Insitution.OriginalSystemLookupValue;
return match ? new LookupValue(x.Institution) : new LookupValue(x.System);
}));
And move the object initializers for LookupValue into real constructors that take an Institution or System.

Categories

Resources