How to Sort a Class List that contain both of double and string data

169 views Asked by At

Person class:

class Person
    {
        public string ID;
        public string Name;
        public string PClass;
        public string Age;
        public string Sex;
        public string Survived;
        public string SexCode;
        public Person(string id,string name,string pclass,string age,string sex,
            string survived,string sexcode)
        {
            ID = id;
            Name = name;
            PClass = pclass;
            Age = age;
            Sex = sex;
            Survived = survived;
            SexCode = sexcode;
        }

My program code:

class Program
    {
        static void Main(string[] args)
        {


            string[] data = File.ReadAllLines("titanic.csv");
            data = data.Skip(1).ToArray();
            List<Person> personList = new List<Person>();
            List<Person> personList_name = new List<Person>();
            List<Person> personList_pclass = new List<Person>();
            List<Person> personList_age = new List<Person>();
            List<Person> personList_sex = new List<Person>();
            List<Person> personList_id = new List<Person>();

            for (int i = 0; i < data.Length; i++)
            {
                string[] temp = Regex.Split(data[i], ",(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)");
                Person person = new Person(temp[0], temp[1], temp[2],temp[3],
                temp[4], temp[5], temp[6]);
                personList.Add(person);

            }

            personList_name = personList.OrderBy(x => x.Name).ToList();
            personList_pclass = personList.OrderBy(z => z.PClass).ToList();
            personList_sex = personList.OrderBy(w => w.Sex).ToList();
            int id_;
            int age_;
            personList_age = personList.OrderBy(y => int.TryParse(y.Age, out age_)).ToList();
            //personList_id = personList.OrderByDescending(int.TryParse(ID, out number)).ToList();
            personList_id = personList.OrderBy(o => int.TryParse(o.ID, out id_)).ToList();

            while (true)
            {
                Console.WriteLine(" Please select your filtring method:\n" +
                    "1-By Name\n2-By Pclass\n3-By Age\n4-By Sex\n5-By ID\n Press -1 to quit.");
                string selection = Console.ReadLine();
                if (selection == "-1")
                {
                    break;
                }

                        Console.WriteLine(("{0,-10}{1,70}{2,20}{3,20}{4,20}{5,20}{6,20}"), item.ID.Trim('"'), item.Name.Trim('"'), item.PClass.Trim('"')
                            , item.Age, item.Sex.Trim('"'), item.Survived.Trim('"')
                            , item.SexCode.Trim('"'));
                    }
                 }
                if (selection == "3")
                {
                    Console.WriteLine(("{0,-10}{1,70}{2,20}{3,20}{4,20}{5,20}{6,20}"), "ID", "NAME", "PCLASS", "AGE", "SEX", "SURVIVED", "SEXCODE");
                    foreach (var item in personList_age)
                    {
                        Console.WriteLine(("{0,-10}{1,70}{2,20}{3,20}{4,20}{5,20}{6,20}"), item.ID.Trim('"'), item.Name.Trim('"'), item.PClass.Trim('"')
                            , item.Age, item.Sex.Trim('"'), item.Survived.Trim('"')
                            , item.SexCode.Trim('"'));

                    }
                }

            }
        }
    }

I am able to sort string data from CSV file such as Name, but for age and ID I cannot obtain the correct order (either ascending or descending) I have searched a lot about this problem. Tried to use Icomparable and Tryparse but without any positive result. The problem is that Orderby deals with age and ID as string. But if I define Age and ID as double or int I will have "cannot convert" while defining temp array. Any helpful suggestion or solution PLEASE?

This is what happens when I order according to Age for example. It seems still ordering according to ID! 1
(source: up-00.com)

3

There are 3 answers

2
John Wu On BEST ANSWER

Instead of an expression, provide a function:

personList_age = personList.OrderBy
(
    y => {
             int age;
             bool ok = int.TryParse(y.Age, out age);
             return ok ? age : default(int);
         }
).ToList();

Or to keep things clean maybe write an extension method:

static void int ToInt(this string input)
{
    int n;
    bool ok = int.TryParse(input, out n);
    return ok ? n : default(int);
}

And call like this:

personList_age = personList.OrderBy( y => t.Age.ToInt());
1
Luchspeter On

Have you tried

personList.OrderBy(x => Convert.ToInt32(x.id)).ToList();

?

This way it should sort them by the int-value

0
pinkfloydx33 On

The problem is that TryParse returns a bool and not the parsed value. OrderBy then orders true vs false. In an ascending ordering, false comes before true.

If you are expecting these values to always be integers you have a few options.

The better (in my opinion) is to change those properties in your class to integers and do your parsing in the constructor, throwing an exception if a bad value is passed (not allowing a bad Person to be created).

The second option would be to switch to int.Parse within the OrderBy. This only works if all the values can definitely be parsed because otherwise an exception is thrown.

If you must use TryParse (for example it's not guaranteed to be integer data and you can't change your class definition), you can use a multi statement lambda:

personList.OrderBy(person => { 
     int value; 
     return int.TryParse(person.ID, out value) ? value : -1;
});

You may want to chose a different value for the failure case depending on where you want them to sort. In the above case all failures would come first--though that assumes that no ID could be negative. You could use int.MinValue instead. If you want them to come last you could use int.MaxValue--though if your IDs could legitimately be that large you'll have a problem (I think it is safe to assume at least that nobody will have an age that large).

Another option would be to filter out the failures by first using Select to create an anonymous type containing the data you need (successfully parsed, value if applicable and your original Person). Then use Where to filter our any values that failed to parse followed by an OrderBy on the parsed data. Finally use another Select to extract your Person from the anonymous type.

personList.Select(p => {
     int value; 
     var ok = int.TryParse(p.ID, out value);
     return new { Ok = ok, ID = value, Person = p };
})
.Where(result => result.Ok)
.OrderBy(result => result.ID)
.Select(result => result.Person);