The dataset I have is :
Name Order ID
Summary 1 147
Software Functionality -9 211
I have this LINQ query:
string value = projectA.OrderByDescending(a => a.Order)
.ThenBy(a => a.Name)
.ToList()
.First()
.ID
.ToString();
The answer I get is 211, but I think the answer should be 147. Why does this happen?
Note: the Order field is of type string.
You get the output you have because your Order is a string. As proof, this sample will show that you get the correct output when it's an integer:
void Main()
{
var list = new List<Project>() { new Project() { Order = 1, Id = 147, Name = "Summary" }, new Project() { Order = -9, Id = 211, Name = "Software Functionality" } };
int value= list.OrderByDescending(a => a.Order).ThenBy(a => a.Name).ToList().First().Id;
Console.WriteLine (value);
}
public class Project
{
public int Order {get;set;}
public int Id {get;set;}
public string Name {get;set;}
}
However, in case you do need it as a string: why doesn't it work like this?
Take a look at the CompareOptions enum. More specificically: CompareOptions.IgnoreSymbols.
Indicates that the string comparison must ignore symbols, such as white-space characters, punctuation, currency symbols, the percent sign, mathematical symbols, the ampersand, and so on. This is also explained here.
This essentially makes your -9 a 9.
You can bypass this easily by creating your own comparer and passing it what you need:
public class CustomComparer : IComparer<string>
{
public int Compare(string x,string y)
{
return CultureInfo.CurrentCulture.CompareInfo.Compare(x, y, CompareOptions.Ordinal);
}
}
which can be used as
new[] {"1", "-9"}.OrderByDescending(x => x, new CustomComparer())
I tried to reconstruct your issue. Here is my code (LinqPad), which works properly:
void Main()
{
List<ProgramA> progs = new List<ProgramA>{
new ProgramA("Summary", 1, 147),
new ProgramA("Software Functionality", -9, 211)
};
int value= progs.OrderByDescending(a => a.Order).ThenBy(a => a.Name).ToList().First().ID;
value.Dump();
}
// Define other methods and classes here
class ProgramA
{
private string sName = string.Empty;
private int iOrder = 0;
private int iID = 0;
public ProgramA(string _Name, int _Order, int _ID)
{
sName = _Name;
iOrder = _Order;
iID = _ID;
}
public string Name
{
get {return sName;}
set {sName = value;}
}
public int Order
{
get {return iOrder;}
set {iOrder = value;}
}
public int ID
{
get {return iID;}
set {iID = value;}
}
}
returns: 147
[EDIT]
#DStanley It is stored as "1" and "-9" in the list – user1989 6 mins ago
If Order is stored as string value, you can try to convert it into integer:
int value= progs.OrderByDescending(a => Convert.ToInt32(a.Order)).ThenBy(a => a.Name).ToList().First().ID;
string value= projectA.OrderByDescending(a => a.Order).ThenBy(a => a.Name).ToList().First().ID.ToString();
OrderByDescending => Orders
Output - -9, 1
Select First element
- 9 = 211. Logic is correct.
Related
I have two arrays of ArrayList.
public class ProductDetails
{
public string id;
public string description;
public float rate;
}
ArrayList products1 = new ArrayList();
ArrayList products2 = new ArrayList();
ArrayList duplicateProducts = new ArrayList();
Now what I want is to get all the products (with all the fields of ProductDetails class) having duplicate description in both products1 and products2.
I can run two for/while loops as traditional way, but that would be very slow specially if I will be having over 10k elements in both arrays.
So probably something can be done with LINQ.
If you want to use linQ, you need write your own EqualityComparer where you override both methods Equals and GetHashCode()
public class ProductDetails
{
public string id {get; set;}
public string description {get; set;}
public float rate {get; set;}
}
public class ProductComparer : IEqualityComparer<ProductDetails>
{
public bool Equals(ProductDetails x, ProductDetails y)
{
//Check whether the objects are the same object.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether the products' properties are equal.
return x != null && y != null && x.id.Equals(y.id) && x.description.Equals(y.description);
}
public int GetHashCode(ProductDetails obj)
{
//Get hash code for the description field if it is not null.
int hashProductDesc = obj.description == null ? 0 : obj.description.GetHashCode();
//Get hash code for the idfield.
int hashProductId = obj.id.GetHashCode();
//Calculate the hash code for the product.
return hashProductDesc ^ hashProductId ;
}
}
Now, supposing you have this objects:
ProductDetails [] items1= { new ProductDetails { description= "aa", id= 9, rating=2.0f },
new ProductDetails { description= "b", id= 4, rating=2.0f} };
ProductDetails [] items= { new ProductDetails { description= "aa", id= 9, rating=1.0f },
new ProductDetails { description= "c", id= 12, rating=2.0f } };
IEnumerable<ProductDetails> duplicates =
items1.Intersect(items2, new ProductComparer());
Consider overriding the System.Object.Equals method.
public class ProductDetails
{
public string id;
public string description;
public float rate;
public override bool Equals(object obj)
{
if(obj is ProductDetails == null)
return false;
if(ReferenceEquals(obj,this))
return true;
ProductDetails p = (ProductDetails)obj;
return description == p.description;
}
}
Filtering would then be as simple as:
var result = products1.Where(product=>products2.Contains(product));
EDIT:
Do consider that this implementation is not optimal..
Moreover- it has been proposed in the comments to your question that you use a data base.
This way performance will be optimized - as per the database implementation In any case- the overhead will not be yours.
However, you can optimize this code by using a Dictionary or a HashSet:
Overload the System.Object.GetHashCode method:
public override int GetHashCode()
{
return description.GetHashCode();
}
You can now do this:
var hashSet = new HashSet<ProductDetails>(products1);
var result = products2.Where(product=>hashSet.Contains(product));
Which will boost your performance to an extent since lookup will be less costly.
10k elements is nothing, however make sure you use proper collection types. ArrayList is long deprecated, use List<ProductDetails>.
Next step is implementing proper Equals and GetHashCode overrides for your class. The assumption here is that description is the key since that's what you care about from a duplication point of view:
public class ProductDetails
{
public string id;
public string description;
public float rate;
public override bool Equals(object obj)
{
var p = obj as ProductDetails;
return ReferenceEquals(p, null) ? false : description == obj.description;
}
public override int GetHashCode() => description.GetHashCode();
}
Now we have options. One easy and efficient way of doing this is using a hash set:
var set = new HashSet<ProductDetails>();
var products1 = new List<ProductDetails>(); // fill it
var products2 = new List<ProductDetails>(); // fill it
// shove everything in the first list in the set
foreach(var item in products1)
set.Add(item);
// and simply test the elements in the second set
foreach(var item in products2)
if(set.Contains(item))
{
// item.description was already used in products1, handle it here
}
This gives you linear (O(n)) time-complexity, best you can get.
I have enum called Department that has abbreviations. (e.g. CSIS, ECON etc.)
I have struct Course with fields name, number of the course(e.g. 101 Fundamentals Of Computing just the number)
I created the constructor with four parameters.
In Program cs I created the List of these courses. with the order of (name(which is string), Department(I got this through calling Department type in enum), CourseCode(enum members like CSIS)and credit hours).
I was required to print all the courses through toString().
public string Name
{
get { return name; }
set { name = value; }
}
public int Number
{
get { return number; }
set { number = value; }
}
public int CreditHours
{
get { return numOfCreditHours; }
set { numOfCreditHours = value; }
}
public override string ToString()
{
return String.Format("{ -30, 0}--{4, 1}/{2}--{3:0.0#}", name, Department, number, numOfCreditHours);
}
This is where I created my List: in Program cs.
static void Main(string[] args)
{
Course course = new Course(courses);
foreach (Course crs in courses)
{
Console.WriteLine(string.Join(" , ", crs));
}
Console.WriteLine();
//Array.Sort(courses);
Console.ReadLine();
}
private static List<Course> courses = new List<Course>()
{
new Course("Fundamentals of Programming", Department.CSIS, 1400, 4),
new Course("Intermediate Programming in C#", Department.CSIS,2530, 3),
new Course("Introduction to Marketing", Department.MKTG,1010, 3),
new Course("Algorithms and Data Structures", Department.CSIS,2420, 4),
new Course("Object Oriented Programming", Department.CSIS, 1410, 4)
};
I get format exception. I know why I get it. Because of string type. but some how I need to do it. Please help me with some explaining. and what I should do to get it right. Thanks.
it looks like you swapped places in string format:
return String.Format("{0,-30}--{1,4}/{2}---{3:0.0#}"
You're confusing indexes in the format string in "{ -30, 0}--{4, 1}/{2}--{3:0.0#}".
"{ -30, 0}" must be "{0, -30}".
"{4, 1}" must be "{1, 4}".
Then, returns must be:
return String.Format("{0,-30}--{1,4}/{2}--{3:0.0#}", name, Department, number, numOfCreditHours);
I am trying to create a class which compute the completeness of a profile of a user and I am trying to get the next steps (Evaluators) in order for the user to achieve the next completeness level.
public class Evaluator
{
public Evaluator(string name, int value, Func<CompletionStatus, bool> rule)
{
Name = name;
Value = value;
Action = rule;
}
public int Value { get; private set; }
public Func<Completion, bool> Rule { get; private set; }
public bool IsCompleted { get;set; }
public int Run(CompletionStatus status)
{
IsCompleted = Rule(status);
return IsCompleted ? Value : 0;
}
}
class CompletionManager
{
public List<Evaluator> Evaluators { get;set; }
public CompletionManager() {
Evaluators = new List<Evaluator>() {
new Evaluator("Email", 10, status => !String.IsNullOrWhiteSpace(status.Email)),
new Evaluator("DOB", 10, status => status.DateOfBirth.HasValue),
new Evaluator("Mobile", 20, status => !String.IsNullOrWhiteSpace(status.Mobile)),
new Evaluator("Address", 20, status => !String.IsNullOrWhiteSpace( status.Address)),
new Evaluator("Profession", 40, status => status.HasProfession),
};
}
Usage
Main() {
var status = new CompletionStatus
{
Email = "dummy#gmail.com",
DateOfBirth = null,
Mobile = "",
Address = "",
HasProfession = false,
};
CompletionManager cm = new CompletionManager();
var profileCompletion = (byte) cm.Evaluators
.Select(evaluator => evaluator.Run(status))
.Sum();
profileCompletion.Dump(); // result= 10% complete
var nextEvaluators = cm.EvaluatorsToTheNextLevel(profileCompletion); // get the next 40%
}
Problem: In this example - how do I get the list of Evaluator that correspond to the next 40% that the user have to complete so that the profile completion is >= 50%;
In the example above, I want to get the Evaluator("DOB"), Evaluator("Mobile") and Evaluator("Address")
class CompletionManager {
....
public List<Evaluator> EvaluatorsToTheNextLevel(int completionStatus) {
// assuming completionStatus = 10%, then we have to get the next 40% worth of evaluators
var percentBeforeNxtLevel = 50 - completionStatus;
return Evaluators.Where(e => e.IsCompleted == false && ???);
}
}
Note: order of Evaluators is also considered so if we are getting the next 40%, we dont want the Evaluator("Profession") as it is in the bottom of the stack
And Also: THIS should be flexible; if I do
var status = new CompletionStatus
{
Email = "",
DateOfBirth = null,
Mobile = "091920",
Address = "",
HasProfession = false,
};
then we should get Evaluator("Email"), Evaluator("DOB"), Evaluator("Address")
You could do something like that :
public List<Evaluator> EvaluatorsToTheNextLevel(int completionStatus) {
// assuming completionStatus = 10%, then we have to get the next 40% worth of evaluators
var percentBeforeNxtLevel = 50 - completionStatus;
var tmp = 0;
return Evaluators
.Where(e => e.IsCompleted == false)
.TakeWhile(x => {
tmp +=x.Value;
return tmp <= percentBeforeNxtLevel;
})
.ToList();
}
Of course, this could be also easily achieved with a simple while loop...
The line below will not work
.Select(evaluator => evaluator.Run(status))
You are enumerating through the evaluators one at a time in the order that they go into the List<>. Your function needs all the evaluators to you need to pass the entire list 'Evaluators'.
I am trying to use Group By method supported by LINQ.
I have this class
public class Attribute
{
public int Id {get;set;}
public string Name {get;set;}
public string Value {get;set;}
}
I have a service method that will retrive a IList
var attributes = _service.GetAll();
Id Name Value
7 Color Black
7 Color White
220 Size 16
Now I have another tow classes
one is
public class AttributeResourceModelSubItem
{
public string Name {get;set;}
public List<AttributeValueResourceModelSubItem> values { get; set; }
}
public class AttributeValueResourceModelSubItem
{
public int Id;
public string Name {get;set;}
}
I am trying to loop through the attributes list. and if the attribute id is the same, I wanna insert the records where id = to that id inside the AttributeValueResourceModelSubItem in which id = 1 and Name will be equal to the attribute value.
This what I got so far.
private IList<AttributeResourceModelSubItem> FormatAttributes(IList<Attribute> attributes)
{
Dictionary<int, Attribute> baseTypes = new Dictionary<int, Attribute>();
AttributeResourceModelSubItem attributeResourceModelSubItem = null;
var list = new IList<AttributeResourceModelSubItem>();
foreach (var item in attributes)
{
if (!baseTypes.ContainsKey(item.Id))
{
attributeResourceModelSubItem = new AttributeResourceModelSubItem()
attributeResourceModelSubItem.key = item.Name;
attributeResourceModelSubItem.values.Add(new AttributeValueResourceModelSubItem()
{
id = 1,
name = item.Value
});
list.Add(attributeResourceModelSubItem);
}
baseTypes.Add(item.Id, item);
}
return list;
}
Any help is appreciated.
It's pretty unclear from your example what you're actually trying to do, but this is the gist I get.
private IEnumerable<AttributeResourceModelSubItem> FormatAttributes(IEnumerable<Attribute> attributes)
{
return attributes.GroupBy(c => c.Id)
.Select(c => new AttributeResourceModelSubItem()
{
key = c.First().Name,
values = c.Select(x => new AttributeValueResourceModelSubItem()
{
id = 1,
name = x.value
}).ToList();
});
}
You should also definitely not use the word Attribute as a class name. That's already a .NET class.
I'll admit that I don't quite understand the id = 1 part, but I took that from your code. It also seems odd to group by the id then try and take the first name, but again that's what you have.
If you do, in fact, want to group by the name and take the id, which makes a little more sense, you'll want to swap a couple things around. Admittedly this structure still seems a little odd to me, but hopefully this will get you a couple steps closer to your goal.
private IEnumerable<AttributeResourceModelSubItem> FormatAttributes(IEnumerable<Attribute> attributes)
{
return attributes.GroupBy(c => c.name)
.Select(c => new AttributeResourceModelSubItem()
{
key = c.Key,
values = c.Select((item, index) => new AttributeValueResourceModelSubItem()
{
id = index + 1,
name = item.value
}).ToList();
});
}
I also made your id = 1 increment starting at one for each element in each values list. You might want that to be item.Id, or even just your original 1.
I have a string with the following structure:
Student Name________AgeAddress_______________________Bithday___Lvl
Example:
Jonh Smith 016Some place in NY, USA 01/01/2014L01
As you can see, there is no delimited character like | or ,
Also, there is no space between fields (if you check, there is no space between Age/Address and Birthday/Level.
The size of each field is static so if data's length is less then it will contains white spaces.
I have a class that need to be filled with that information:
public class StudentData
{
public char[] _name = new char[20];
public string name;
public char[] _age = new char[3];
public string age;
public char[] _address = new char[30];
public string address;
public char[] _bday = new char[10];
public string bday;
public char[] _level = new char[3];
public string level;
}
Is there any way to do this automatically and dynamically?
I mean I really don't want to code like this:
myClass.name = stringLine.substring(0,19);
myClass.age = stringLine.substring(20,22);
That's because I have way more fields that the ones added in this example & way more string lines with other different data.
Update: There were supposed to be a lot of spaces between "Smith" and "016", but I don't know how to edit it.
Update2: If I use StringReader.Read() I can evade to use substring and indexes, but it isn't still so dynamically because I would need to repeat those 3 lines for each field.
StringReader reader = new StringReader(stringLine);
reader.Read(myClass._name, 0 myClass._name.Length);
myClass.name = new string(myClass._name);
Given your requirement I came up with an interesting solution. All be-it it may be more complex and longer than using the String.SubString() method as stated.
However this solution is transferable to other types and other string. I used a concept of Attributes, Properties, and Reflection to parse a string by a Fixed Length and setting the class Properties.
Note I did change your StudentData class to follow a more conventional coding style. Following this handy guide on MSDN: http://msdn.microsoft.com/en-us/library/xzf533w0(v=vs.71).aspx
Here is the new StudentData class. Note it uses the properties as opposed to fields. (Not discussed here).
public class StudentData
{
string name;
string age;
string address;
string bday;
string level;
[FixedLengthDelimeter(0, 20)]
public string Name { get { return this.name; } set { this.name = value; } }
[FixedLengthDelimeter(1, 3)]
public string Age { get { return this.age; } set { this.age = value; } }
[FixedLengthDelimeter(2, 30)]
public string Address { get { return this.address; } set { this.address = value; } }
[FixedLengthDelimeter(3, 10)]
public string BDay { get { return this.bday; } set { this.bday = value; } }
[FixedLengthDelimeter(4, 3)]
public string Level { get { return this.level; } set { this.level = value; } }
}
Note on each of the properties there is an Attribute called FixedLengthDelimeter that takes two parameters.
OrderNumber
FixedLength
The OrderNumber parameter denotes the order in the string (not the position) but the order in which we process from the string. The second parameter denotes the Length of the string when parsing the string. Here is the full attribute class.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public sealed class FixedLengthDelimeterAttribute : Attribute
{
public FixedLengthDelimeterAttribute(int orderNumber, int fixedLength)
{
this.fixedLength = fixedLength;
this.orderNumber = orderNumber;
}
readonly int fixedLength;
readonly int orderNumber;
public int FixedLength { get { return this.fixedLength; } }
public int OrderNumber { get { return this.orderNumber; } }
}
Now the attribute is simple enough. Accepts the two paramters we discussed eariler in the constructor.
Finally there is another method to parse the string into the object type such as.
public static class FixedLengthFormatter
{
public static T ParseString<T>(string inputString)
{
Type tType = typeof(T);
var properties = tType.GetProperties(BindingFlags.Instance | BindingFlags.Public); //;.Where(x => x.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false).Count() > 0);
T newT = (T)Activator.CreateInstance(tType);
Dictionary<PropertyInfo, FixedLengthDelimeterAttribute> dictionary = new Dictionary<PropertyInfo, FixedLengthDelimeterAttribute>();
foreach (var property in properties)
{
var atts = property.GetCustomAttributes(typeof(FixedLengthDelimeterAttribute), false);
if (atts.Length == 0)
continue;
dictionary[property] = atts[0] as FixedLengthDelimeterAttribute;
}
foreach (var kvp in dictionary.OrderBy(x => x.Value.OrderNumber))
{
int length = kvp.Value.FixedLength;
if (inputString.Length < length)
throw new Exception("error on attribute order number:" + kvp.Value.OrderNumber + " the string is too short.");
string piece = inputString.Substring(0, length);
inputString = inputString.Substring(length);
kvp.Key.SetValue(newT, piece.Trim(), null);
}
return newT;
}
}
The method above is what does the string parsing. It is a pretty basic utility that reads all the properties that have the FixedLengthDelimeter attribute applied a Dictionary. That dictionary is then enumerated (ordered by OrderNumber) and then calling the SubString() method twice on the input string.
The first substring is to parse the next Token while the second substring resets the inputString to start processing the next token.
Finally as it is parsing the string it is then applying the parsed string to the property of the class Type provided to the method.
Now this can be used simply like this:
string data1 = "Jonh Smith 016Some place in NY, USA 01/01/2014L01";
StudentData student = FixedLengthFormatter.ParseString<StudentData>(data1);
What this does:
Parses a string against property attributes in a fixed length format.
What this does not do:
It does convert the parsed strings to another type. Therefore all the properties must be a string. (this can be easily adapted by adding some type casting logic in).
It is not well tested. This is only tested against a few samples.
It is not by all means the only or best solution out there.
You could use FileHelpers library (NuGet).
Just define the structure of your input file with attributes:
[FixedLengthRecord]
public class StudentData
{
[FieldFixedLength(20)]
[FieldTrim(TrimMode.Right)]
public string name;
[FieldFixedLength(3)]
public string age;
[FieldFixedLength(30)]
[FieldTrim(TrimMode.Right)]
public string address;
[FieldFixedLength(10)]
public string bday;
[FieldFixedLength(3)]
public string level;
}
Then simply read the file using FileHelperEngine<T>:
var engine = new FileHelperEngine<StudentData>();
var students = engine.ReadFile(filename);