Creating a list of an object which either does or doesn't exist in another different object list - C# - c#

So I am trying to compare two different lists that both contain differently structured objects. One is easily accessible while the other is very nested in arrays, but sadly, these are responses from API calls so it's not a structure I can change to make them easier to compare. I want to have a list of the complete structures of items found:
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
The way the objects are structured are as follows:
public class ObjectsA
{
public Structure1[] Structure1 {get; set;}
}
public class Structure1
{
public int id {get; set;} //1
}
And the other object looks like:
public class ObjectsB
{
public Array1[] array1{get; set}
}
public class Array1
{
public Array2[] array2{get; set;}
}
public class Array2
{
public string id {get; set;} //"0001"
}
In a list, I added all the objects that came back from the API call, so ObjectAList contains technically just 1 deserialized object response, which contains an array of objects, while ObjectBList contains a list of objects added to it via AddRange.
At first I tried to putting an Array.Exists() inside of 2 foreach() statements.
foreach (var arr1 in ObjectsBList){
foreach (var arr2 in a.Array2){
if (Array.Exists(ObjectAList.Structure1, item => item.id == Convert.ToInt32(arr2.id)) == true){
foundList.AddRange(ObjectAList.Structure1);
}
else{
notFoundList.AddRange(ObjectAList.Structure1)
}}};
This code seemed to keep looping on the "item => item.id == Convert.ToInt32(arr2.id)" part of it, so consequently, it kept going till it found its match and so the answer was always 'true', therefore just putting everything in the foundList. I know I'm probably going at this wrong. I'm just starting out C# programming and I'm having trouble wrapping my mind around some of these things and knowing what all functions exists to help with what I need, etc. Any help would be great!

You can use linq for querying the in-memory objects.
pseudo code
public class Test {
public void T()
{
var ObjectsBList = new ObjectsB();
var ObjectAList = new ObjectsA();
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
var bList = ObjectsBList
.array1
.SelectMany(x => x.array2)
.Select(x => Convert.ToInt32(x.id))
.Distinct()
.ToList();
if (ObjectAList.Structure1.Any(x => bList.Contains(x.id)))
{
foundList.AddRange(ObjectAList.Structure1);
}
else
{
notFoundList.AddRange(ObjectAList.Structure1);
}
}
}
More simplified version:
Introduce an ID list property
using System;
using System.Text;
using System.Collections.Generic;
using System.Collections;
public class ObjectsA
{
public Structure1[] Structure1 { get; set; }
public List<int> IDs
{
get
{
return Structure1.Select(x => x.id).Distinct().ToList();
}
}
}
public class Structure1
{
public int id { get; set; } //1
}
public class ObjectsB
{
public Array1[] array1 { get; set; }
public List<int> IDs
{
get
{
return array1
.SelectMany(x => x.array2)
.Select(x => Convert.ToInt32(x.id))
.Distinct()
.ToList();
}
}
}
public class Array1
{
public Array2[] array2 { get; set; }
}
public class Array2
{
public string id { get; set; } //"0001"
}
public class Test
{
public void T()
{
var ObjectsBList = new ObjectsB();
var ObjectAList = new ObjectsA();
var foundList = new List<Structure1>();
var notFoundList = new List<Structure1>();
if (ObjectAList.IDs.Any(x => ObjectsBList.IDs.Contains(x)))
{
foundList.AddRange(ObjectAList.Structure1);
}
else
{
notFoundList.AddRange(ObjectAList.Structure1);
}
}
}

var objA = new ObjectsA();
var objB = new ObjectsB();
var objAIds = objA.Structure1.Select(x => x.Id).Distinct();
var objBIds = objB.Array1.SelectMany(x => x.Array2).Select(x => int.Parse(x.Id)).Distinct();
var foundInBothList = objAIds.Intersect(objBIds);
var notFoundinBList = objAIds.Except(objBIds);
var inBoth = objA.Structure1.Where(x => foundInBothList.Contains(x.Id));
var notInB = objA.Structure1.Where(x => notFoundinBList.Contains(x.Id));
Starting from .NET 6
var objBIds = objB.Array1.SelectMany(x => x.Array2).Select(x => int.Parse(x.Id)).Distinct();
var foundList = objA.Structure1.IntersectBy(objBIds, x => x.Id);
var notFoundList = objA.Structure1.ExceptBy(objBIds, x => x.Id);

Related

List of 2 elements (item & quantity), Issues getting SUM in the GroupBy

I have a list of 2 items, model and qty. I am trying to get the sum of qty using Linq but am getting the following error at ModelList.Add(item.qty);
"cannot convert from int to string.
I need it to be an integer in order to get the Sum in the GroupBy statement below. What am I doing wrong?
[{"sector": "3","position": "3","qty": "1","model": "SBNHH-1D65C"
},{"sector": "1","position": "4","qty": "3","model": "SDFVR-U34R"}]
namespace Antenna
{
public static class Rfds
{
public class Antenna
{
public AntennaItems[] root { get; set; }
}
public class AntennaItems
{
public int sector { get; set; }
public int position { get; set; }
public int qty { get; set; }
public string model { get; set; }
}
[FunctionName("AntennaSort")]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var jsonexample = requestBody;
var jsonObj = JsonConvert.DeserializeObject<List<AntennaItems>>(requestBody);
List<string> ModelList = new List<string>();
foreach (var item in jsonObj)
{
if (item.model != "" && item.model != null)
{
ModelList.Add(item.model);
ModelList.Add(item.qty);
}
}
var ModelAndCount = ModelList.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Sum());
//var ModelAndCount = ModelList.GroupBy(x => x).ToDictionary(g => g.Key, g => g.Count());
return new OkObjectResult(ModelAndCount);
}
}
}
You issue lies here
ModelList.Add(item.qty);
Qty is an int and you are trying to add it to String Collection.Considering your eventual goal is to create the ModelAndCount Dictionary, you can directly query the jsonObj Collection, instead of creating an intermediate collection and then querying it.
Assuming that you want to Group By Model, You can do following
var ModelAndCount = jsonObj.Where(x=> !string.IsNullOrEmpty(x.model))
.GroupBy(x=>x.model)
.ToDictionary(k=>k.Key,v=>v.Sum(x=>x.qty));
Output
Here is a simple code that will GroupBy the data per model an Sum the quantity. Like this.
Instead of creating an empty list then going into foreach to check the model value. We can simply filter them using LinQ Where clause and IsNullOrWhiteSpace.
Then with a simple group we can compute the Sum. Here the result will be in a anonymous class.
public static void Run()
{
//Data Initilisation
var data = new[] {
new DataItem{ model="a" , qty=1},
new DataItem{ model="a" , qty=41},
new DataItem{ model="b" , qty=12},
new DataItem{ model="c" , qty=17},
};
var jsonData = JsonConvert.SerializeObject(data);
// Process
var jsonObj = JsonConvert.DeserializeObject<List<DataItem>>(jsonData);
var result = jsonObj.Where(x => !string.IsNullOrWhiteSpace(x.model))
.GroupBy(x => x.model)
.Select(x => new { model = x.Key, sum = x.Sum(y => y.qty) })
.ToList();
}
public class DataItem
{
public int qty { get; set; }
public string model { get; set; }
}
Result:
model: a sum: 42
model: b sum: 12
model: c sum: 17

compare 2 properties inside 2 linq lists and return a boolean based on the comparision

I've tried to find the answer to this but nothing seems to fit it quite right.
Requirement: From a known list of FooObjects, return a list of the Foo Id's whose data satisfies all the search criteria.
Here is my code:
class testClass
{
public class SearchItem
{
string Term { get; set; }
decimal Confidence { get; set; }
}
public class FooObject
{
public Guid Id { get; set; }
public List<Data> Data { get; set; }
}
public class Data
{
public string Text { get; set; }
public decimal Confidence { get; set; }
}
[Test]
public void Test()
{
var searchItems = new List<SearchTerm>
{
new SearchTerm{ Confidence = (decimal)1, Term = "TestWord" },
new SearchTerm{ Confidence = (decimal)1, Term = "TestWord2" },
};
var FooObjects = new List<FooObject>
{
new FooObject{Id = new Guid(), Data = new List<Data>
{
new Data{Text = "TestWord", Confidence = 1},
new Data{Text = "TestWord2", Confidence = 1},
new Data{Text = "SomeOtherWord", Confidence = 1},
}
}
};
//result is a list of the Foo IDs
var result = FooObjects.Where(foo => !searchItems.Select(item => item.Term).Except(foo.Data.Select(dt => dt.Text).Distinct()).Any())
.Select(foo => foo.Id).ToList();
Assert.That(result.Count, Is.EqualTo(1));
searchItems.Add(new SearchTerm{Term = "NotFoundString"});
result = FooObjects.Where(foo => !searchItems.Select(item => item.Term).Except(foo.Data.Select(dt => dt.Text).Distinct()).Any())
.Select(foo => foo.Id).ToList();
Assert.That(result.Count, Is.EqualTo(0));
}
}
I now need to modify this so that I can compare against the confidence of each word
Question:
How do I modify the LINQ to compare the confidence and the Term against my data
Instead of matching any criteria like #dymanoid said in his answer, you should be looking to satisfy all the search items/terms (you are mixing these up in your example code, be consistent).
var result = FooObjects
.Where(f => searchItems.All(
s => f.Data.Exists(d => d.Text == s.Term && d.Confidence == s.Confidence)))
.Select(f => f.Id);
Maybe you're looking for something like this:
var result = FooObjects
.Where(foo => foo.Data.Any(d => searchTerms.Any(
si => d.Term == si.Text && d.Confidence == si.Confidence)))
.Select(foo => foo.Id);
Keep in mind that this search isn't effective - if your data sets are large, the performance will be bad.

How could distinct multi array list on c#

Consider objModels is object of ChildModel class
public class ChildModel
{
public string Title { get; set; }
public long Id { get; set; }
}
It has some data like:
title,id: a,1 b,1 a,1
Two of these data are same, and after distinct it could be like: a,1 b,1
Question is how could I distinct it on c#
List<ChildModel> obj= objModels.ToList();
Also these aren't help
objModels.Distinct();
obj.Distinct();
You could use a library named MoreLINQ
This is the query you could use with MoreLINQ to find elements that are distinct by multiple properties:
var query = objModels.DistinctBy(p => new { p.Id, p.Title});
try this:
var answer= obj.DistinctBy(p => new { p.Id , p.Title });
and check this link for other way
I would do the following:
namespace ConsoleApplication7
{
class Program
{
static void Main()
{
List<ChildModel> list = new List<ChildModel>();
list.Add(new ChildModel { Title = "a", Id = 1 });
list.Add(new ChildModel { Title = "b", Id = 1 });
list.Add(new ChildModel { Title = "a", Id = 1 });
var x = list.Distinct(new ChildModelComparer()).ToList();
var y = x; //This has only got two child items.
}
}
class ChildModelComparer : IEqualityComparer<ChildModel>
{
public bool Equals(ChildModel x, ChildModel y)
{
return x.Id.Equals(y.Id) && x.Title.Equals(y.Title);
}
public int GetHashCode(ChildModel obj)
{
if (string.IsNullOrEmpty(obj.Title) && obj.Id == 0)
{
return 0;
}
return $"{obj.Id}{obj.Title}".GetHashCode();
}
}
public class ChildModel
{
public string Title { get; set; }
public long Id { get; set; }
}
}
you can use .GroupBy:
var result= obj
.GroupBy(p => p.Title)
.Select(g => g.First()) // choose which one
.ToList();
edit: if you want to GroupBy more than one Property you can just change p.Title to new {p.Title, p.Id} like so
var result= obj
.GroupBy(p => new {p.Title, p.Id})
.Select(g => g.First()) // choose which one
.ToList();

Linq Join Not Equal

I have 3 classes:
public class HoteAvail
{
public int HotelID { get; set; }
public string Name { get; set; }
public List<Room> Rooms { get; set; }
}
public class Room
{
public int RoomID { get; set; }
public string RoomName { get; set; }
}
public class DAL
{
public static List<HoteAvail> GetAll()
{
return new List<HoteAvail>()
{
new HoteAvail{HotelID=1,Name="Taj",Rooms=new List<Room>(){new Room{RoomID=1,RoomName="Deliux"}}},
new HoteAvail{HotelID=2,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=3,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
};
}
public static List<HoteAvail> GetAllII()
{
return new List<HoteAvail>()
{
new HoteAvail{HotelID=1,Name="Taj",Rooms=new List<Room>(){new Room{RoomID=1,RoomName="Deliux"},new Room{RoomID=1,RoomName="pp"}}},
new HoteAvail{HotelID=4,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=5,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
};
}
}
I want to join the values of DAL.GetAll() and DAL.GetAllII() and result should contain only those values whose HotelID doesnot matches.
The final result set should be like :
new HoteAvail{HotelID=2,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=3,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}},
new HoteAvail{HotelID=4,Name="x",Rooms=new List<Room>(){new Room{RoomID=2,RoomName="dd"},new Room{RoomID=1,RoomName="qq"}}},
new HoteAvail{HotelID=5,Name="y",Rooms=new List<Room>(){new Room{RoomID=3,RoomName="yy"},new Room{RoomID=1,RoomName="rr"}}}
I tried:
var groupBNames = new HashSet<string>(DAL.GetAll().Select(x => x.HotelID.ToString()));
var filteredEmployees = DAL.GetAllII().Where(x => !groupBNames.Contains(x.HotelID.ToString()));
var resultList = from a in DAL.GetAll()
where !(DAL.GetAllII().Any(HotelID => HotelID == a))
select a;
But I am not getting any success. Thanks in advance.
I'd recommend doing 2 excepts using a custom IEqualityComparer. You can use this method to create the comparer:
// create a comparer to compare HotelAvail objects by hotelId
// see http://www.codeducky.org/10-utilities-c-developers-should-know-part-two/
// for the implementation of EqualityComparers.Create, which is a nice shortcut
var comparer = EqualityComparers.Create<HoteAvail>(ha => ha.HotelId); // compare by hotelId
var results =
// first take all entries in GetAll() NOT IN GetAllII()
DAL.GetAll().Except(DAL.GetAllII(), comparer)
// then add all entries in GetAllII() NOT IN GetAll()
.Concat(DAL.GetAllII()).Except(DAL.GetAll(), comparer);
You could implement a custom IEqualityComparer<HoteAvail>:
public class HoteAvailComparer: IEqualityComparer<HoteAvail>
{
public bool Equals(HoteAvail x, HoteAvail y)
{
return x != null && y != null && x.HotelID == y.HotelID;
}
public int GetHashCode(HoteAvail obj)
{
return obj.HotelID;
}
}
that you can use for Enumerable.Except which is efficient since it's using a set:
var resultList = DAL.GetAll().Except(DAL.GetAllII(), new HoteAvailComparer());
Console.WriteLine(String.Join(",", resultList.Select(h => h.HotelID))); // 2,3
Update
It is giving me HotelId 2, 3 where as I want to join the values of
DAL.GetAll() and DAL.GetAllII() and result should contain only those
values whose HotelID doesnot matchesi.e. The result should have
HotelId 2,3,4,5
Then you need to use Except from both perspectives:
var hotelComparer = new HoteAvailComparer();
var all1 = DAL.GetAll();
var all2 = DAL.GetAllII();
var resultList = all1.Except(all2, hotelComparer).Concat(all2.Except(all1, hotelComparer));
The desired result 2,3,4,5:
Console.WriteLine(String.Join(",", resultList.Select(h => h.HotelID)));
Of course you could also use Concat and GroupBy, but it's less efficient and maintainable:
resultList = all1.Concat(all2).GroupBy(h => h.HotelID)
.Where(g => g.Count() == 1)
.SelectMany(g => g);
You can use the IEqualityComparer<HoteAvail> for many other LINQ methods like GroupBy+Distinct,Join,Intersect etc.

Build hierarchy from strings C#

I have a collection of strings:
"Alberton;Johannesburg"
"Allendale;Phoenix"
"Brackenhurst;Alberton"
"Cape Town;"
"Durban;"
"Johannesburg;"
"Mayville;Durban"
"Phoenix;Durban"
"Sandton;Johannesburg"
that I want to structure into a hierarchical structure in the fastest possible manner, like:
Johannesburg
Alberton
Brackenhurst
Sandton
Cape Town
Durban
Phoenix
Allandale
Mayville
Currently I have nested for loops and checks, but was hoping I could achieve this with a single LAMBDA query?
The above mentioned strings are in a List.
I prepared lambda-like solution, but you should really think if it's more readable/efficient then your current one:
Helper Extension Method:
public static class ChildrenGroupExtensions
{
public static List<CityInfo> GetChildren(this IEnumerable<IGrouping<string, City>> source, string parentName)
{
var cities = source.SingleOrDefault(g => g.Key == parentName);
if (cities == null)
return new List<CityInfo>();
return cities.Select(c => new CityInfo { Name = c.Name, Children = source.GetChildren(c.Name) }).ToList();
}
}
Helper Classes:
public class City
{
public string Name { get; set; }
public string Parent { get; set; }
}
public class CityInfo
{
public string Name { get; set; }
public List<CityInfo> Children { get; set; }
}
Usage:
var groups = (from i in items
let s = i.Split(new[] { ';' })
select new City { Name = s[0], Parent = s[1] }).GroupBy(e => e.Parent);
var root = groups.GetChildren(string.Empty);
Where items is your List<string>
You can look the results with simple helper method like that one:
private static void PrintTree(List<CityInfo> source, int level)
{
if (source != null)
{
source.ForEach(c =>
{
Enumerable.Range(1, level).ToList().ForEach(i => Console.Write("\t"));
Console.WriteLine(c.Name);
PrintTree(c.Children, level + 1);
});
}
}
And the results are:
Cape Town
Durban
Mayville
Phoenix
Allendale
Johannesburg
Alberton
Brackenhurst
Sandton
You haven't specified any specific data structure so I just used a class called Area with a list of children of itself. Also, it's in 2 lines of linq. There is also no check to see if an area is a child of 2 separate parents as the code is. Here's the code for the test I used(Relevant lines in-between the equals comments):
[TestFixture]
public class CitiesTest
{
[Test]
public void Test()
{
var strings = new List<string>
{
"Alberton;Johannesburg",
"Allendale;Phoenix",
"Brackenhurst;Alberton",
"Cape Town;",
"Durban;",
"Johannesburg;",
"Mayville;Durban",
"Phoenix;Durban",
"Sandton;Johannesburg"
};
//===================================================
var allAreas = strings.SelectMany(x=>x.Split(';')).Where(x=>!string.IsNullOrWhiteSpace(x)).Distinct().ToDictionary(x=>x, x=>new Area{Name = x});
strings.ForEach(area =>
{
var areas = area.Split(';');
if (string.IsNullOrWhiteSpace(areas[1]))
return;
var childArea = allAreas[areas[0]];
if (!allAreas[areas[1]].Children.Contains(childArea))
allAreas[areas[1]].Children.Add(childArea);
childArea.IsParent = false;
});
var result = allAreas.Select(x=>x.Value).Where(x => x.IsParent);
//===================================================
}
public class Area
{
public string Name;
public bool IsParent;
public List<Area> Children { get; set; }
public Area()
{
Children = new List<Area>();
IsParent = true;
}
}
}

Categories