Unexpected result while generating string with Aggregate() in linq - c#

FIDDLE
Utility Function:
public static string GetProperties<T>(string alias="")
{
if (alias.Length>0)
{
return typeof(T).GetProperties().Select(x => x.Name).Aggregate((x, y) =>
alias + " = " + alias + "." + x + "," + Environment.NewLine + alias + " = " + alias + "." + y + ",");
}
else
{
return typeof(T).GetProperties().Select(x => x.Name).Aggregate((x, y) => x + Environment.NewLine + y);
}
}
Code:
public class ContainerInLog
{
public int ContainerInLogID { get; set; }
public int ContainerInID { get; set; }
public int CargoID { get; set; }
public int LoadStatus { get; set; }
}
public static void Main()
{
string list = GetProperties<ContainerInLog>("y");
Console.WriteLine(list);
}
Result:
y = y.y = y.y = y.ContainerInLogID,
y = y.ContainerInID,,
y = y.CargoID,,
y = y.LoadStatus,
Expected Result:
ContainerInLogID = y.ContainerInLogID,
ContainerInID = y.ContainerInID,
CargoID = y.CargoID,
LoadStatus = y.LoadStatus,

If you are really stuck on returning the entire concatenated string instead of returning an enumerable of them, I wouldn't use Aggregate here, just use string.Join. Also, you can simplify the statement by crafting the string inside the Select. For example:
return string.Join(
Environment.NewLine,
typeof(T)
.GetProperties()
.Select(x => $"{x.Name} = {alias}.{x.Name},"));
Bonus: If you change the separator to $",{Environment.NewLine}" you can remove the inline comma and you won't get the final comma on the end of your string (example fiddle).

#DavidG has the nicer solution here, but whats going wrong with your aggregation is the following:
.Aggregate((x, y) =>
alias + " = " + alias + "." + x + "," + Environment.NewLine + alias + " = " + alias + "." + y + ",");
The Aggregate selector function (your (x,y)) takes the following form:
Func<TAccumulate,TResult> resultSelector
That means in your case, x is the accumulated aggregate result already, eg "ContainerInLogID = y.ContainerInLogID".
But to make the next aggregate, you transform x again: alias + " = " + alias + "." + x, making "y = y.y = y.ContainerInLogID". And so on for each following property, each one adding another prefix of "y = y.".

Related

Check the value exists in List using C#

I have List as mentioned below. Now When I am going to add new string value into this list, My method GetNewCopiedValue has to check the new text value into the list lstNames.If the name does not exist in the list it should return the value as it is. If the new string value is exist already in the list, it has to return the string with respective index number as like EC(1).For example, If I am sending EC(1) again to the list, the method has to check the value in the list and It should return EC(3) since EC(1) is exist already in the list, the method has to check the similar values and it should return the value with next index number which is not there in the list.
Main()
{
List<string> lstNames=new List<string>{"Ecard","EC","EC(1)","EC(2)","NCard(1)"};
var copiedValue= GetNewCopiedValue(lstNames,EC(2));
Console.WriteLine("Copied Text is :"+copiedValue);
}
public static string GetNewCopiedValue(List<string> lstNames,string sourceLabel)
{
label = sourceLabel;
if (lstNames.Any(i => i.Equals(sourceLabel)))
{
var labelSubstring = sourceLabel.Substring(0, sourceLabel.Length - 2);
var sameNameList = lstNames.Where(i => i.Contains(labelSubstring)).ToList();
int count = sameNameList.Count+1;
label = labelSubstring + count + ")";
while (lstNames.Any(i => i.Equals(label)))
{
var indexLabel = sourceLabel.Substring(sourceLabel.Length-2,1);
var maxIndex = sameNameList.Max(i =>int.Parse( i.Substring(sourceLabel.Length - 2, 1)));
int labelCount = maxIndex + 1;
label = labelSubstring + labelCount + ")";
}
}
return label;
}
Actual Input:
EC(2)
Expected output:
EC(3)
I have tried with some logic but it was not working with all input strings. It worked for few cases only.
Please help me on this.
https://dotnetfiddle.net/dFrzhA
public static void Main() {
List<string> lstNames= new List<string>{"Ecard","EC","EC(1)","EC(2)","NCard(1)"};
var copiedValue= GetNewCopiedValue(lstNames, "EC(1)");
Console.WriteLine("Copied Text is :" + copiedValue);
}
public static string GetNewCopiedValue(List<string> lstNames, string ValueToCopyInList) {
string newName;
if (!lstNames.Contains(ValueToCopyInList)) {
newName = ValueToCopyInList;
} else {
int? suffix = ParseSuffix(ValueToCopyInList);
string baseName = suffix == null ? ValueToCopyInList : ValueToCopyInList.Substring(0, ValueToCopyInList.LastIndexOf('('));
suffix = suffix ?? 1;
newName = baseName + "(" + suffix + ")";
while (lstNames.Contains(newName)) {
suffix++;
newName = baseName + "(" + suffix + ")";
}
}
lstNames.Add(newName);
return newName;
}
public static int? ParseSuffix(string value) {
int output;
if (string.IsNullOrEmpty(value)) return null;
if (!value.EndsWith(")")) {
return null;
}
var idxStart = value.LastIndexOf('(');
var strResult = value.Substring(idxStart + 1, value.Length - (idxStart + 2));
if (int.TryParse(strResult, out output))
return output;
return null;
}

faster way to iterate through and compare two lists of classes

i need to iterate through and compare 2 lists of classes and compare the 2 and output the matching records. this is taking hours and i cant figure out a way to speed up the process. the lists are roughly 600k records a piece. here is my code for the class and code for iterating through and comparing.
class Person
{
string NPI;
string address;
string zip5;
string lname;
string lsk;
string state;
string fname;
string zipfull;
string seqNo;
public Person(string npi, string Address, string Zip5, string Lname, string LSK, string st, string Fname, string zipFull, string seqno)
{
this.NPI = npi;
this.address = Address;
this.zip5 = Zip5;
this.lname = Lname;
this.lsk = LSK;
this.state = st;
this.fname = Fname;
this.zipfull = zipFull;
this.seqNo = seqno;
}
public string getNPI()
{
return NPI;
}
public string getzip5()
{
return zip5;
}
public string getaddress()
{
return address;
}
public string Full()
{
string full = NPI + "," + address + "," + zip5 + "," + lname + "," + lsk + "," + state + "," + fname + "," + zipfull + "," + seqNo;
return full;
}
}
here is the code for iterating through. the fuzz.ratio is a fuzzy matching nuget package i downloaded and i know that isnt the problem as i have done speed tests with it and it is very fast
string inputfile = #"C:\Input_File_150k.csv";
string blacklist = #"C:\Blacklist1.csv";
List<Person> input = Readcsv(inputfile);
List<Person> BL = Readcsv(blacklist);
string outputtest = #"C:\outputtest.csv";
StringBuilder csvcontent = new StringBuilder();
int lengthinput = input.Count();
for(int i = 0; i <lengthinput; i++)
{
int lengthbl = BL.Count();
for(int x = 0; x < lengthbl; x++)
{
if(input[i].getzip5() == BL[x].getzip5())
{
if(input[i].getNPI() == BL[x].getNPI())
{
if(Fuzz.Ratio(input[i].getaddress(),BL[x].getaddress()) > 90)
{
csvcontent.AppendLine(input[i].Full());
}
}
}
}
}
File.AppendAllText(outputtest, csvcontent.ToString());
Try creating a dictionary from one list to use as a lookup while iterating the other. That will change the complexity from polynomial to linear.
string inputfile = #"C:\Input_File_150k.csv";
string blacklist = #"C:\Blacklist1.csv";
List<Person> input = Readcsv(inputfile);
var blAddresses = Readcsv(blacklist).ToDictionary(
x => (Zip : x.getzip5(), NPI : x.getNPI()),
x => x.getaddress());
string outputtest = #"C:\outputtest.csv";
StringBuilder csvcontent = new StringBuilder();
int lengthinput = input.Count();
for(int i = 0; i <lengthinput; i++)
{
var zip = input[i].getzip5();
var npi = input[i].getNPI();
if(blAddresses.TryGetValue((zip,npi), out var blAddress)
{
if(Fuzz.Ratio(input[i].getaddress(),blAddress) > 90)
{
csvcontent.AppendLine(input[i].Full());
}
}
}
File.AppendAllText(outputtest, csvcontent.ToString());
Specifically I've created a dictionary that keys on the Zip and NPI and gets the Address which is all that's needed. I'm using some C# 7 stuff like the value tuples, but that can be changed to reference tuples, anonymous class, or a custom class if needed.
Edit
Here's a change to make this work just as your current code does assuming that you have duplicate Zip/NPI values
string inputfile = #"C:\Input_File_150k.csv";
string blacklist = #"C:\Blacklist1.csv";
List<Person> input = Readcsv(inputfile);
var blAddresses = Readcsv(blacklist)
.GroupBy(x => (Zip : x.getzip5(), NPI : x.getNPI()))
.ToDictionary(
grp => grp.Key,
grp => grp.Select(y => y.getAddress()).ToList());
string outputtest = #"C:\outputtest.csv";
StringBuilder csvcontent = new StringBuilder();
int lengthinput = input.Count();
for(int i = 0; i <lengthinput; i++)
{
var zip = input[i].getzip5();
var npi = input[i].getNPI();
if(blAddresses.TryGetValue((zip,npi), out var blAddressList)
{
foreach(var blAddress in blAddressList)
{
if(Fuzz.Ratio(input[i].getaddress(),blAddress) > 90)
{
csvcontent.AppendLine(input[i].Full());
}
}
}
}
File.AppendAllText(outputtest, csvcontent.ToString());
Alternatively if you just need to filter out anything in the black list where the NPI is blank to have unique keys you can do this instead
var blAddresses = Readcsv(blacklist)
.Whree(x => x.getNPI().Length > 0)
.ToDictionary(
x => (Zip : x.getzip5(), NPI : x.getNPI()),
x => x.getaddress());
You could iterate through each list once to put all the values in a hash map. For instance, you can use the zip code as the key, and the value is an array of people who match that zip code.
Then you can iterate through the hashmap one record at a time, and only have to compare the people inside each hashmap bucket with eachother.
Unless all your people are in the same zip code (if so, hopefully one of your keys would work for this), this should be faster than N^2 comparison. Should be closer to O(N) depending on how many people are in each bucket.
in addition to above comments ( https://stackoverflow.com/a/60886352/5334191 and using Dictionaries),
in your loops
int lengthinput = input.Count();
**int lengthbl = BL.Count();** //move out of the loops
for(int i = 0; i <lengthinput; i++)
{
**var inputi = input[i];** //move out of the inner loop
for(int x = 0; x < lengthbl; x++)
{
**var blx = BL[x];**
if(inputi.getzip5() == blx.getzip5())
{
if(inputi.getNPI() == blx.getNPI())
{
if(Fuzz.Ratio(inputi.getaddress(),blx.getaddress()) > 90)
{
csvcontent.AppendLine(inputi.Full());
}
}
}
}
}
I would definity implement the IEqualityComparer interface.
That allows for much cleaner code and to also benefit from some of the Linq extension methods available on collections.
Those also support things like parallelism.
https://marcofranssen.nl/delegate-your-equality-comparisons/
I think this part of code may be slow
string full = NPI + "," + address + "," + zip5 + "," + lname + "," + lsk + "," + state + "," + fname + "," + zipfull + "," + seqNo;
What if you perform csvcontent.Append on those properties and , "manually" instead of using Full()? ps: it would require to add an possibility to read it from the outside, so public get / private set

c# UWA public variable

String[] vals = s.Split(';');
String o = "X=" + vals[0] + " Y=" + vals[1] + " Z=" + vals[2];
var xValue = vals[0];
var yValue = vals[1];
var zValue = vals[2];
I want to use my variable "xValue, yValue, zValue" on another page. How do I make it public for me to use it.
This is the public class I created
public class CommonData
{
public static string o = string.Empty;
public static void SetData(string s)
{
String[] vals = s.Split(';');
o = "X=" + vals[0] + " Y=" + vals[1] + " Z=" + vals[2];
}
}
Making them public will solve your issue. You can access them further using object of that class.
I simulated your case in a console application. Declare those variable outside of method.
class Program
{
public static string xValue;
public static string yValue;
public static string zValue;
static void Main(string[] args)
{
string s = "fdf;fdfd;fdf";
string[] vals = s.Split(';');
string o = "X=" + vals[0] + " Y=" + vals[1] + " Z=" + vals[2];
xValue = vals[0];
yValue = vals[1];
zValue = vals[2];
Console.ReadLine();
}
}

How to sort an array based on specific index [duplicate]

This question already has answers here:
How to sort an array containing class objects by a property value of a class instance? [duplicate]
(2 answers)
Closed 7 years ago.
I Have an array like below code :
struct Book_Struct
{
public string Title;
public string Auther;
public int Date;
public int ID;
}
static void Print(Book_Struct[] a, int b)
{
for (int i = 0; i < b; i++)
{
Console.WriteLine(" Name of Book " + (i + 1) + " is : " + "\" " + a[i].Title + " \"");
Console.WriteLine("Auther of Book " + (i + 1) + " is : " + "\" " + a[i].Auther + " \"");
Console.WriteLine(" Date of Book " + (i + 1) + " is : " + "\" " + a[i].Date + " \"");
Console.WriteLine(" ID of Book " + (i + 1) + " is : " + "\" " + a[i].ID + " \"");
Console.WriteLine("\n---------------------------------\n");
}
}
I want sort this array based on for example Title of Books. How do i it?
You could use Array.Sort:
Array.Sort(a, (b1, b2) => b1.Title.CompareTo(b2.Title));
or LINQ:
a = a.OrderBy(book => book.Title).ToArray();
The latter needs to recreate the array.
As an aside, use a class instead of a mutable struct.
Use LINQ's OrderBy to sort the array:
a = a.OrderBy(x => x.Title).ToArray();
Reference

Getting only the last entry on counting

I'm only getting the last entry of the counting typed like this
public string ZodziuSkaiciavimas()
{
foreach (var sentence in Sakiniai.TrimEnd('.').Split('.'))
{
Rezultatas=(eilute.ToString() + " sakinyje zodziu:" + (sentence.Trim().Split(' ').Count() + sentence.Trim().Split('-').Count() + sentence.Trim().Split(';').Count() + sentence.Trim().Split(':').Count() + sentence.Trim().Split(',').Count() - 4));
eilute++;
}
return Rezultatas;
And I need to get the answer with a return type.
If I type code like this than i get what i want,but no returns.
public string ZodziuSkaiciavimas()
{
foreach (var sentence in Sakiniai.TrimEnd('.').Split('.'))
{
Console.WriteLine(eilute.ToString() + " sakinyje zodziu:" + (sentence.Trim().Split(' ').Count() + sentence.Trim().Split('-').Count() + sentence.Trim().Split(';').Count() + sentence.Trim().Split(':').Count() + sentence.Trim().Split(',').Count() - 4));
eilute++;
}
return Rezultatas;
}
Why arent you appending your results as below
Rezultatas +=(eilute.ToString() + " sakinyje zodziu:" + (sentence.Trim().Split(' ').Count() + sentence.Trim().Split('-').Count() + sentence.Trim().Split(';').Count() + sentence.Trim().Split(':').Count() + sentence.Trim().Split(',').Count() - 4)) + "\n";
It looks like you want to return multiple numbers from your method, but Rezultatas is a single string. You can fix it by changing the return type to List<int>, and returning a list:
public List<int> ZodziuSkaiciavimas() {
var Rezultatas = new List<int>()
foreach (var sentence in Sakiniai.TrimEnd('.').Split('.')) {
var res = sentence.Trim().Split(' ', '-', ';', ':', ',').Length;
Rezultatas.Add(res);
}
return Rezultatas;
}
When the callers decide to print the Rezultatas they gets back from your method, they could decide what character to put between the numbers (say, a comma ',') and print it like this:
var numbers = ZodziuSkaiciavimas();
Console.WriteLine(string.Join(", ", numbers));

Categories

Resources