LINQ Sort Numeric Data Only But Retain The List Intact

Viewed 126

I have the following program. My data types are all string. From the list I want to sort those entries that have numeric values. Those entries without numeric data will be always on the last items. Here's my code. Please help.

 class Program
{
    static void Main(string[] args)
    {
        var persons = new List<Person>();
        persons.Add(new Person { Name = "John",   Position = "100" });
        persons.Add(new Person { Name = "Peter",  Position = "78"  });
        persons.Add(new Person { Name = "Larry",  Position = "NA"  });
        persons.Add(new Person { Name = "Lovely", Position = ""    });
        persons.Add(new Person { Name = "Rose",   Position = "50"  });
        persons.Add(new Person { Name = "Drew",   Position = "34"  });

        //Help me do the sorting for position that is a number. Priority in sorting are positions which are numbers
        var sortedPersons = persons.OrderBy(x=>double.Parse(x.Position)).ToList();
        foreach (var person in sortedPersons)
            Console.WriteLine("Name: {0}, Position: {1}", person.Name, person.Position);
        Console.ReadLine();
    }
}
public class Person
{
    public string Name { get; set; }
    public string Position { get; set; }
}

Based on my sample, I want to sort the items as

  1. Drew
  2. Rose
  3. Peter
  4. John
  5. Lovely
  6. Larry

(for Lovely and Larry it doesn't matter for me how they are sorted as long as I got sorted correctly Drew to John).

4 Answers

Try using .TryParse() instead, and assign max value to anything that can't be parsed

You can either use double.MaxValue or double.PositiveInfinity as default value. The latter is the greater of the two.

persons.OrderBy(x => double.TryParse(x.Position, out double d) ? d : double.PositiveInfinity)

You can implement a custom IComparer<string> and pass its instance as the second argument to OrderBy method

public class PositionComparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        double xValue = int.TryParse(x, out var xParsed) ? xParsed : double.PositiveInfinity;
        double yValue = int.TryParse(y, out var yParsed) ? yParsed : double.PositiveInfinity;

        return xValue.CompareTo(yValue);
    }
}

Example of the usage

var sortedPersons = persons.OrderBy(x => x.Position, new PositionComparer()).ToList();

It provides a correct order and allows you to reuse this code for comparison of such values in other places of your code.

Name: Drew, Position: 34
Name: Rose, Position: 50
Name: Peter, Position: 78
Name: John, Position: 100
Name: Larry, Position: NA
Name: Lovely, Position:

IMO, it's also easier to understand, maintain and debug, than Linq methods and predicates

If Position must be a valid int (say, Position = "7.83e+1" considered to be not numeric and should be in the end of the list) we can sort by tuple:

  var sortedPersons = persons
    .OrderBy(item => (!int.TryParse(item.Position, out var v), v))
    .ToList();

Here, we sort first by tuple's Item1 which detrmine if item.Position is int or not:

!int.TryParse(item.Position, out int v)

so the valid integers comes first. Then we sort by tuple's Item2 which is parsed value

v

to have Positions ordered. If Position should be double (say, Position = "-7.83e-10" is perfectly valid) you can either use Innat3's solution or change int to double:

  var sortedPersons = persons
    .OrderBy(item => (!double.TryParse(item.Position, out var v), v))
    .ToList();
var sortedPersons = persons.Where(x => int.TryParse(x.Position, out int res))
                           .OrderBy(x => int.Parse(x.Position))
                           .Concat(persons.Where(x => ! int.TryParse(x.Position, out int res)));
Related