允许重复键的c#可排序集合
本文关键字:排序 集合 许重复 | 更新日期: 2023-09-27 17:50:38
我正在编写一个程序来设置各种对象将在报告中出现的顺序。序列是Excel电子表格上的Y位置(单元格)。
下面是代码的演示部分。我想要完成的是有一个集合,它将允许我添加多个对象,并且我可以根据序列
获得一个排序的集合SortedList list = new SortedList();
Header h = new Header();
h.XPos = 1;
h.name = "Header_1";
list.Add(h.XPos, h);
h = new Header();
h.XPos = 1;
h.name = "Header_2";
list.Add(h.XPos, h);
我知道SortedList
不允许这样做,我一直在寻找替代方案。我不想消除重复项,并且已经尝试了List<KeyValuePair<int, object>>
。
谢谢。
使用您自己的IComparer!
就像在其他答案中已经说过的那样,您应该使用自己的比较器类。为此,我使用了一个通用的IComparer类,它可以与任何实现icomcomparable的类一起工作:
/// <summary>
/// Comparer for comparing two keys, handling equality as beeing greater
/// Use this Comparer e.g. with SortedLists or SortedDictionaries, that don't allow duplicate keys
/// </summary>
/// <typeparam name="TKey"></typeparam>
public class DuplicateKeyComparer<TKey>
:
IComparer<TKey> where TKey : IComparable
{
#region IComparer<TKey> Members
public int Compare(TKey x, TKey y)
{
int result = x.CompareTo(y);
if (result == 0)
return 1; // Handle equality as being greater. Note: this will break Remove(key) or
else // IndexOfKey(key) since the comparer never returns 0 to signal key equality
return result;
}
#endregion
}
您将在实例化新的SortedList, SortedDictionary等时使用它:
SortedList<int, MyValueClass> slist = new SortedList<int, MyValueClass>(new DuplicateKeyComparer<int>());
这里int是可重复的键。
您可以安全地使用List<>。List有一个Sort方法,该方法的重载接受IComparer。您可以创建自己的排序器类。下面是一个例子:
private List<Curve> Curves;
this.Curves.Sort(new CurveSorter());
public class CurveSorter : IComparer<Curve>
{
public int Compare(Curve c1, Curve c2)
{
return c2.CreationTime.CompareTo(c1.CreationTime);
}
}
我使用如下:
public class TupleList<T1, T2> : List<Tuple<T1, T2>> where T1 : IComparable
{
public void Add(T1 item, T2 item2)
{
Add(new Tuple<T1, T2>(item, item2));
}
public new void Sort()
{
Comparison<Tuple<T1, T2>> c = (a, b) => a.Item1.CompareTo(b.Item1);
base.Sort(c);
}
}
我的测试案例:
[TestMethod()]
public void SortTest()
{
TupleList<int, string> list = new TupleList<int, string>();
list.Add(1, "cat");
list.Add(1, "car");
list.Add(2, "dog");
list.Add(2, "door");
list.Add(3, "elephant");
list.Add(1, "coconut");
list.Add(1, "cab");
list.Sort();
foreach(Tuple<int, string> tuple in list)
{
Console.WriteLine(string.Format("{0}:{1}", tuple.Item1,tuple.Item2));
}
int expected_first = 1;
int expected_last = 3;
int first = list.First().Item1; //requires using System.Linq
int last = list.Last().Item1; //requires using System.Linq
Assert.AreEqual(expected_first, first);
Assert.AreEqual(expected_last, last);
}
输出:1:cab
1:coconut
1:car
1:cat
2:door
2:dog
3:elephant
问题是数据结构设计不符合要求:有必要为相同的xpo存储多个header。因此,SortedList<XPos, value>
的值不应该是Header
,而应该是List<Header>
。这是一个简单而微小的改变,但它解决了所有问题,并避免了像其他建议的解决方案那样产生新问题(见下面的解释):
using System;
using System.Collections.Generic;
namespace TrySortedList {
class Program {
class Header {
public int XPos;
public string Name;
}
static void Main(string[] args) {
SortedList<int, List<Header>> sortedHeaders = new SortedList<int,List<Header>>();
add(sortedHeaders, 1, "Header_1");
add(sortedHeaders, 1, "Header_2");
add(sortedHeaders, 2, "Header_3");
foreach (var headersKvp in sortedHeaders) {
foreach (Header header in headersKvp.Value) {
Console.WriteLine(header.XPos + ": " + header.Name);
}
}
}
private static void add(SortedList<int, List<Header>> sortedHeaders, int xPos, string name) {
List<Header> headers;
if (!sortedHeaders.TryGetValue(xPos, out headers)){
headers = new List<Header>();
sortedHeaders[xPos] = headers;
}
headers.Add(new Header { XPos = xPos, Name = name });
}
}
}
Output:
1: Header_1
1: Header_2
2: Header_3
请注意添加"funny"密钥,比如添加一个随机数或假装具有相同值的2个xpo不同,会导致许多其他问题。例如,它变得很难甚至不可能删除一个特定的头文件。
还要注意,如果只有少数List<Header>
需要排序,那么排序性能要比每个Header
都要好得多。示例:如果有100个xpo,每个xpo有100个标题,则需要对10000个Header
进行排序,而不是对100个List<Header>
进行排序。
当然,这个解决方案也有一个缺点:如果有许多xpo只有一个头,那么需要创建许多列表,这是一些开销。
更新22.12.2021
我终于找到时间写了一个叫做SortedBucketCollection
的集合,它的行为类似于SortedList
。它为每个项目使用2个键,第一个键与SortedList
键相同,许多项目可以为该键具有相同的值。第二个键用于区分为key1共享相同值的项。SortedBucketCollection
比SortedList<int, List<Header>>
使用更少的存储空间,因为它使用每个"bucket"。一个链表而不是一个List<>
.
使用SortedBucketCollection
的代码如下:
namespace SortedBucketCollectionDemo {
public record FinanceTransaction
(int No, DateTime Date, string Description, decimal Amount);
class Program {
static void Main(string[] args) {
//Constructing a SortedBucketCollection
var transactions =
new SortedBucketCollection<DateTime, int, FinanceTransaction>
(ft=>ft.Date, ft=>ft.No);
var date1 = DateTime.Now.Date;
//Adding an item to SortedBucketCollection
transactions.Add(new FinanceTransaction(3, date1, "1.1", 1m));
transactions.Add(new FinanceTransaction(1, date1, "1.2", 2m));
transactions.Add(new FinanceTransaction(0, date1, "1.3", 3m));
var date2 = date1.AddDays(-1);
transactions.Add(new FinanceTransaction(1, date2, "2.1", 4m));
transactions.Add(new FinanceTransaction(2, date2, "2.2", 5m));
//Looping over all items in a SortedBucketCollection
Console.WriteLine("foreach over all transactions");
foreach (var transaction in transactions) {
Console.WriteLine(transaction.ToString());
}
//Accessing one particular transaction
var transaction12 = transactions[date1, 1];
//Removing a transaction
transactions.Remove(transaction12!);
//Accessing all items of one day
Console.WriteLine();
Console.WriteLine("foreach over transactions of one day");
Console.WriteLine(date1);
foreach (var transaction in transactions[date1]) {
Console.WriteLine(transaction.ToString());
}
}
}
}
第一个foreach的输出:
FinanceTransaction { No = 1, Date = 07.11.2021 00:00:00, Description = 2.1, Amount = 4 }
FinanceTransaction { No = 2, Date = 07.11.2021 00:00:00, Description = 2.2, Amount = 5 }
FinanceTransaction { No = 0, Date = 08.11.2021 00:00:00, Description = 1.3, Amount = 3 }
FinanceTransaction { No = 1, Date = 08.11.2021 00:00:00, Description = 1.2, Amount = 2 }
FinanceTransaction { No = 3, Date = 08.11.2021 00:00:00, Description = 1.1, Amount = 1 }
请注意,项不是按照它们被添加的顺序迭代,而是按照它们的key1
和key2
排序。
有关SortedBucketCollection
的详细描述和源代码,请参阅我在CodeProject SortedBucketCollection上的文章:一个内存高效的SortedList接受具有相同键的多个项
最简单的解决方案(与上述所有解决方案相比):使用SortedSet<T>
,它接受IComparer<SortableKey>
类,然后以这种方式实现比较方法:
public int Compare(SomeClass x, SomeClass y)
{
var compared = x.SomeSortableKeyTypeField.CompareTo(y.SomeSortableKeyTypeField);
if (compared != 0)
return compared;
// to allow duplicates
var hashCodeCompare = x.GetHashCode().CompareTo(y.GetHashCode());
if (hashCodeCompare != 0)
return hashCodeCompare;
if (Object.ReferenceEquals(x, y))
return 0;
// for weird duplicate hashcode cases, throw as below or implement your last chance comparer
throw new ComparisonFailureException();
}
非常感谢您的帮助。在进一步搜索的过程中,我找到了这个解决方案。(可在Stackoverflow.com在其他问题)
首先,我创建了一个类来封装我的类对象(Headers,Footer等)
public class MyPosition
{
public int Position { get; set; }
public object MyObjects{ get; set; }
}
所以这个类应该保存对象,每个对象的PosX都是int Position
List<MyPosition> Sequence= new List<MyPosition>();
Sequence.Add(new MyPosition() { Position = 1, Headerobject });
Sequence.Add(new MyPosition() { Position = 2, Headerobject1 });
Sequence.Add(new MyPosition() { Position = 1, Footer });
League.Sort((PosA, PosB) => PosA.Position.CompareTo(PosB.Position));
最终我得到的是排序的"序列"列表。
您尝试了允许重复密钥的Lookup<TKey, TElement>
吗http://msdn.microsoft.com/en-us/library/bb460184.aspx
您可以使用SortedList,使用您的值作为TKey,并使用int (count)作为TValue。
下面是一个示例:一个对单词的字母进行排序的函数。
private string sortLetters(string word)
{
var input = new System.Collections.Generic.SortedList<char, int>();
foreach (var c in word.ToCharArray())
{
if (input.ContainsKey(c))
input[c]++;
else
input.Add(c, 1);
}
var output = new StringBuilder();
foreach (var kvp in input)
{
output.Append(kvp.Key, kvp.Value);
}
string s;
return output.ToString();
}
这个集合类将维护重复项并为重复项插入排序顺序。诀窍是用唯一的值标记项目因为它们被插入以保持稳定的排序顺序。然后我们把它包起来ICollection接口。
public class SuperSortedSet<TValue> : ICollection<TValue>
{
private readonly SortedSet<Indexed<TValue>> _Container;
private int _Index = 0;
private IComparer<TValue> _Comparer;
public SuperSortedSet(IComparer<TValue> comparer)
{
_Comparer = comparer;
var c2 = new System.Linq.Comparer<Indexed<TValue>>((p0, p1) =>
{
var r = _Comparer.Compare(p0.Value, p1.Value);
if (r == 0)
{
if (p0.Index == -1
|| p1.Index == -1)
return 0;
return p0.Index.CompareTo(p1.Index);
}
else return r;
});
_Container = new SortedSet<Indexed<TValue>>(c2);
}
public IEnumerator<TValue> GetEnumerator() { return _Container.Select(p => p.Value).GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public void Add(TValue item) { _Container.Add(Indexed.Create(_Index++, item)); }
public void Clear() { _Container.Clear();}
public bool Contains(TValue item) { return _Container.Contains(Indexed.Create(-1,item)); }
public void CopyTo(TValue[] array, int arrayIndex)
{
foreach (var value in this)
{
if (arrayIndex >= array.Length)
{
throw new ArgumentException("Not enough space in array");
}
array[arrayIndex] = value;
arrayIndex++;
}
}
public bool Remove(TValue item) { return _Container.Remove(Indexed.Create(-1, item)); }
public int Count {
get { return _Container.Count; }
}
public bool IsReadOnly {
get { return false; }
}
}
一个测试类
[Fact]
public void ShouldWorkWithSuperSortedSet()
{
// Sort points according to X
var set = new SuperSortedSet<Point2D>
(new System.Linq.Comparer<Point2D>((p0, p1) => p0.X.CompareTo(p1.X)));
set.Add(new Point2D(9,10));
set.Add(new Point2D(1,25));
set.Add(new Point2D(11,-10));
set.Add(new Point2D(2,99));
set.Add(new Point2D(5,55));
set.Add(new Point2D(5,23));
set.Add(new Point2D(11,11));
set.Add(new Point2D(21,12));
set.Add(new Point2D(-1,76));
set.Add(new Point2D(16,21));
var xs = set.Select(p=>p.X).ToList();
xs.Should().BeInAscendingOrder();
xs.Count.Should()
.Be(10);
xs.ShouldBeEquivalentTo(new[]{-1,1,2,5,5,9,11,11,16,21});
set.Remove(new Point2D(5,55));
xs = set.Select(p=>p.X).ToList();
xs.Count.Should()
.Be(9);
xs.ShouldBeEquivalentTo(new[]{-1,1,2,5,9,11,11,16,21});
set.Remove(new Point2D(5,23));
xs = set.Select(p=>p.X).ToList();
xs.Count.Should()
.Be(8);
xs.ShouldBeEquivalentTo(new[]{-1,1,2,9,11,11,16,21});
set.Contains(new Point2D(11, 11))
.Should()
.BeTrue();
set.Contains(new Point2D(-1, 76))
.Should().BeTrue();
// Note that the custom compartor function ignores the Y value
set.Contains(new Point2D(-1, 66))
.Should().BeTrue();
set.Contains(new Point2D(27, 66))
.Should().BeFalse();
}
标记结构
public struct Indexed<T>
{
public int Index { get; private set; }
public T Value { get; private set; }
public Indexed(int index, T value) : this()
{
Index = index;
Value = value;
}
public override string ToString()
{
return "(Indexed: " + Index + ", " + Value.ToString () + " )";
}
}
public class Indexed
{
public static Indexed<T> Create<T>(int indexed, T value)
{
return new Indexed<T>(indexed, value);
}
}
lambda比较器辅助器
public class Comparer<T> : IComparer<T>
{
private readonly Func<T, T, int> _comparer;
public Comparer(Func<T, T, int> comparer)
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_comparer = comparer;
}
public int Compare(T x, T y)
{
return _comparer(x, y);
}
}
问题是你使用了一些不是键的东西作为键(因为它发生了多次)
所以如果你有真实的坐标,你应该把Point
作为你的SortedList的键。
或者创建一个List<List<Header>>
,其中第一个列表索引定义x位置,而内部列表索引定义y位置(反之亦然,如果您喜欢)。
查找很酷,但如果您的目标是简单地遍历"键",同时允许它们被复制,则可以使用以下结构:
List<KeyValuePair<String, String>> FieldPatterns = new List<KeyValuePair<string, string>>() {
new KeyValuePair<String,String>("Address","CommonString"),
new KeyValuePair<String,String>("Username","UsernamePattern"),
new KeyValuePair<String,String>("Username","CommonString"),
};
那么你可以写:
foreach (KeyValuePair<String,String> item in FieldPatterns)
{
//use item.Key and item.Value
}
HTH
关键(双关语)是创建一个基于IComparable
的类,它保持相等和散列,但如果不相等,则永远不会与0比较。这是可以做到的,并且可以通过一些附加功能来创建—稳定排序(即首先添加到排序列表中的值将保持其位置),并且ToString()
可以简单地返回实际的键字符串值。
这里有一个struct键,应该可以做到这一点:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
namespace System
{
/// <summary>
/// Defined in Totlsoft.Util.
/// A key that will always be unique but compares
/// primarily on the Key property, which is not required
/// to be unique.
/// </summary>
public struct StableKey : IComparable<StableKey>, IComparable
{
private static long s_Next;
private long m_Sequence;
private IComparable m_Key;
/// <summary>
/// Defined in Totlsoft.Util.
/// Constructs a StableKey with the given IComparable key.
/// </summary>
/// <param name="key"></param>
public StableKey( IComparable key )
{
if( null == key )
throw new ArgumentNullException( "key" );
m_Sequence = Interlocked.Increment( ref s_Next );
m_Key = key;
}
/// <summary>
/// Overridden. True only if internal sequence and the
/// Key are equal.
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals( object obj )
{
if( !( obj is StableKey ) )
return false;
var dk = (StableKey)obj;
return m_Sequence.Equals( dk.m_Sequence ) &&
Key.Equals( dk.Key );
}
/// <summary>
/// Overridden. Gets the hash code of the internal
/// sequence and the Key.
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return m_Sequence.GetHashCode() ^ Key.GetHashCode();
}
/// <summary>
/// Overridden. Returns Key.ToString().
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Key.ToString();
}
/// <summary>
/// The key that will be compared on.
/// </summary>
public IComparable Key
{
get
{
if( null == m_Key )
return 0;
return m_Key;
}
}
#region IComparable<StableKey> Members
/// <summary>
/// Compares this Key property to another. If they
/// are the same, compares the incremented value.
/// </summary>
/// <param name="other"></param>
/// <returns></returns>
public int CompareTo( StableKey other )
{
var cmp = Key.CompareTo( other.Key );
if( cmp == 0 )
cmp = m_Sequence.CompareTo( other.m_Sequence );
return cmp;
}
#endregion
#region IComparable Members
int IComparable.CompareTo( object obj )
{
return CompareTo( (StableKey)obj );
}
#endregion
}
}
我就是这样解决这个问题的。这意味着它是线程安全的,但如果不需要的话,您可以简单地删除lock
。另请注意,不支持索引上任意的Insert
,因为这可能违反排序条件。
public class ConcurrentOrderedList<Titem, Tsort> : ICollection<Titem>
{
private object _lock = new object();
private SortedDictionary<Tsort, List<Titem>> _internalLists;
Func<Titem, Tsort> _getSortValue;
public ConcurrentOrderedList(Func<Titem,Tsort> getSortValue)
{
_getSortValue = getSortValue;
_internalLists = new SortedDictionary<Tsort, List<Titem>>();
}
public int Count { get; private set; }
public bool IsReadOnly => false;
public void Add(Titem item)
{
lock (_lock)
{
List<Titem> values;
Tsort sortVal = _getSortValue(item);
if (!_internalLists.TryGetValue(sortVal, out values))
{
values = new List<Titem>();
_internalLists.Add(sortVal, values);
}
values.Add(item);
Count++;
}
}
public bool Remove(Titem item)
{
lock (_lock)
{
List<Titem> values;
Tsort sortVal = _getSortValue(item);
if (!_internalLists.TryGetValue(sortVal, out values))
return false;
var removed = values.Remove(item);
if (removed)
Count--;
return removed;
}
}
public void Clear()
{
lock (_lock)
{
_internalLists.Clear();
}
}
public bool Contains(Titem item)
{
lock (_lock)
{
List<Titem> values;
Tsort sortVal = _getSortValue(item);
if (!_internalLists.TryGetValue(sortVal, out values))
return false;
return values.Contains(item);
}
}
public void CopyTo(Titem[] array, int arrayIndex)
{
int i = arrayIndex;
lock (_lock)
{
foreach (var list in _internalLists.Values)
{
list.CopyTo(array, i);
i += list.Count;
}
}
}
public IEnumerator<Titem> GetEnumerator()
{
foreach (var list in _internalLists.Values)
{
foreach (var item in list)
yield return item;
}
}
public int IndexOf(Titem item)
{
int i = 0;
var sortVal = _getSortValue(item);
lock (_lock)
{
foreach (var list in _internalLists)
{
if (object.Equals(list.Key, sortVal))
{
int intIndex = list.Value.IndexOf(item);
if (intIndex == -1)
return -1;
return i + intIndex;
}
i += list.Value.Count;
}
return -1;
}
}
public void Insert(int index, Titem item)
{
throw new NotSupportedException();
}
// Note this method is indeterminate if there are multiple
// items in the same sort position!
public void RemoveAt(int index)
{
int i = 0;
lock (_lock)
{
foreach (var list in _internalLists.Values)
{
if (i + list.Count < index)
{
i += list.Count;
continue;
}
else
{
list.RemoveAt(index - i);
return;
}
}
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
技巧是使用唯一键来扩展对象。请看下面通过的测试。我想要的让我的点按照X值排序。只是使用一个裸Point2D在我的比较函数将导致具有相同X值的点被消除。因此,我将Point2D包装在一个名为索引。
[Fact]
public void ShouldBeAbleToUseCustomComparatorWithSortedSet()
{
// Create comparer that compares on X value but when X
// X values are uses the index
var comparer = new
System.Linq.Comparer<Indexed<Point2D>>(( p0, p1 ) =>
{
var r = p0.Value.X.CompareTo(p1.Value.X);
return r == 0 ? p0.Index.CompareTo(p1.Index) : r;
});
// Sort points according to X
var set = new SortedSet<Indexed<Point2D>>(comparer);
int i=0;
// Create a helper function to wrap each point in a unique index
Action<Point2D> index = p =>
{
var ip = Indexed.Create(i++, p);
set.Add(ip);
};
index(new Point2D(9,10));
index(new Point2D(1,25));
index(new Point2D(11,-10));
index(new Point2D(2,99));
index(new Point2D(5,55));
index(new Point2D(5,23));
index(new Point2D(11,11));
index(new Point2D(21,12));
index(new Point2D(-1,76));
index(new Point2D(16,21));
set.Count.Should()
.Be(10);
var xs = set.Select(p=>p.Value.X).ToList();
xs.Should()
.BeInAscendingOrder();
xs.ShouldBeEquivalentTo(new[]{-1,1,2,5,5,9,11,11,16,21});
}
使此工作的实用程序是
接受lambda
参数的比较器public class Comparer<T> : IComparer<T>
{
private readonly Func<T, T, int> _comparer;
public Comparer(Func<T, T, int> comparer)
{
if (comparer == null)
throw new ArgumentNullException("comparer");
_comparer = comparer;
}
public int Compare(T x, T y)
{
return _comparer(x, y);
}
}
标记结构
public struct Indexed<T>
{
public int Index { get; private set; }
public T Value { get; private set; }
public Indexed(int index, T value) : this()
{
Index = index;
Value = value;
}
public override string ToString()
{
return "(Indexed: " + Index + ", " + Value.ToString () + " )";
}
}
public class Indexed
{
public static Indexed<T> Create<T>(int indexed, T value)
{
return new Indexed<T>(indexed, value);
}
}
这是我的看法。请注意,这里可能是龙,c#对我来说仍然很新。
- 允许重复键,值存储在列表中
- 我使用它作为一个排序队列,因此名称和方法
用法:
SortedQueue<MyClass> queue = new SortedQueue<MyClass>();
// new list on key "0" is created and item added
queue.Enqueue(0, first);
// new list on key "1" is created and item added
queue.Enqueue(1, second);
// items is added into list on key "0"
queue.Enqueue(0, third);
// takes the first item from list with smallest key
MyClass myClass = queue.Dequeue();
class SortedQueue<T> {
public int Count;
public SortedList<int, List<T>> Queue;
public SortedQueue() {
Count = 0;
Queue = new SortedList<int, List<T>>();
}
public void Enqueue(int key, T value) {
List<T> values;
if (!Queue.TryGetValue(key, out values)){
values = new List<T>();
Queue.Add(key, values);
Count += 1;
}
values.Add(value);
}
public T Dequeue() {
if (Queue.Count > 0) {
List<T> smallest = Queue.Values[0];
if (smallest.Count > 0) {
T item = smallest[0];
smallest.Remove(item);
return item;
} else {
Queue.RemoveAt(0);
Count -= 1;
return Dequeue();
}
}
return default(T);
}
}
创建类并查询列表:
Public Class SortingAlgorithm
{
public int ID {get; set;}
public string name {get; set;}
public string address1 {get; set;}
public string city {get; set;}
public string state {get; set;}
public int age {get; set;}
}
//declare a sorting algorithm list
List<SortingAlgorithm> sortAlg = new List<SortingAlgorithm>();
//Add multiple values to the list
sortAlg.Add( new SortingAlgorithm() {ID = ID, name = name, address1 = address1, city = city, state = state, age = age});
sortAlg.Add( new SortingAlgorithm() {ID = ID, name = name, address1 = address1, city = city, state = state, age = age});
sortAlg.Add( new SortingAlgorithm() {ID = ID, name = name, address1 = address1, city = city, state = state, age = age});
//query and order by the list
var sortedlist = (from s in sortAlg
select new { s.ID, s.name, s.address1, s.city, s.state, s.age })
.OrderBy(r => r.ID)
.ThenBy(r=> r.name)
.ThenBy(r=> r.city)
.ThenBy(r=>r.state)
.ThenBy(r=>r.age);