1

Say I have two lists in c# as follows:

  • X , Y
  • 0.2 , 5.4
  • 0.6 , 4.1
  • 0.9 , 6.7
  • 10.58, 45.7
  • -1.54, -7.02
  • 6.5 , 6.66

Now, I would like to order the list of X but I would like the list of Y to sync with X's re-ordering.

Re-ordered list would look like:

  • X , Y
  • -1.54, -7.02
  • 0.2 , 5.4
  • 0.6 , 4.1
  • 0.9 , 6.7
  • 6.5 , 6.66
  • 10.58, 45.7

Any way of doing this using LINQ?

Thanks

leppie
  • 115,091
  • 17
  • 196
  • 297
user2822838
  • 337
  • 1
  • 3
  • 13
  • Do you really need to have two lists? A single list where each element is the pair of values would almost certainly be cleaner. See http://codeblog.jonskeet.uk/2014/06/03/anti-pattern-parallel-collections/ – Jon Skeet Jan 29 '15 at 11:50
  • Hi Jon, I have done this for performance reasons - having structure of arrays rather than arrays of structures... The lists can be 10K - 25K elements long and accessing and filtering these are quicker with SOA. – user2822838 Jan 29 '15 at 12:06
  • 1
    Not sure where SOA comes in, but it should be pretty trivial to access and filter them. Are your performance concerns theoretical, or have you actually *tried* this the more logical (IMO) way and found it to be too slow? – Jon Skeet Jan 29 '15 at 12:30
  • By SOA I meant structure of Arrays. Didn't mean to confuse it with Service oriented architecture. Yes, this was done post performance testing using profiling tools. – user2822838 Jan 29 '15 at 12:51

1 Answers1

1

1. Array.Sort<TKey, TValue>(keys, value) method

If your lists are actually System.Arrays or you do not care about few additional copying due to ToArray calls, then the simplest way is to use the Array.Sort<TKey, TValue>(keys, value) method

public static IList<Double> GetKeys()
{
    return new Double[] 
    {
        0.2,
        0.6, 
        0.9,
        10.58,
        -1.54,
        6.5
    }; 
}

public static IList<Double> GetValues()
{
    return new Double[]
    {
        5.4,
        4.1,
        6.7,
        45.7,
        -7.02,
        6.66
    };
}


public static void Print<T>(IEnumerable<T> items)
{
    if (items == null)
        throw new ArgumentNullException("items");

    foreach (var item in items)
    {
        Console.WriteLine(item);
    }
}

public static void PrintKeyValues<TKey, TValue>(IEnumerable<TKey> keys, IEnumerable<TValue> values)
{
    if (keys == null)
        throw new ArgumentNullException("keys");
    if (values == null)
        throw new ArgumentNullException("values");

    var pairs = keys
        .Zip(values,
            (key, value) =>
                String.Format("[{0}] = {1}", key, value));

    Print(pairs);
} 

static void Main(string[] args)
{
    var keys = GetKeys();
    var values = GetValues();

    Console.WriteLine("Before");
    PrintKeyValues(keys, values);

    Console.WriteLine();
    Console.WriteLine("After");

    var keysArray = keys.ToArray();
    var valuesArray = values.ToArray();
    Array.Sort(keysArray, valuesArray);

    PrintKeyValues(keysArray, valuesArray);

    Console.ReadKey();
}

2. Nearly pure LINQ

If you need LINQ solution, then you obviously have no problems with excessive copying. So, you have to sort one array, while preserving the indices with How to get index using LINQ? and then shuffle the associated array:

public static T[] ShuffleFromIndices<T>(this IList<T> items, IList<Int32> indices)
{
    if (items == null)
        throw new ArgumentNullException("items");
    if (indices == null)
        throw new ArgumentNullException("indices");
    if (items.Count != indices.Count)
        throw new ArgumentException("items and indices have different lengths");

    T[] result = new T[items.Count];

    for (int i = 0; i < items.Count; i++)
    {
        var shuffleIndex = indices[i];

        result[i] = items[shuffleIndex];
    }

    return result;
}

public static Tuple<TKey[], TValue[]> SortNotInPlace<TKey, TValue>(IList<TKey> keys, IList<TValue> values)
{
    if (keys == null)
        throw new ArgumentNullException("keys");
    if (values == null)
        throw new ArgumentNullException("values");
    if (keys.Count != values.Count)
        throw new ArgumentException("Keys and values have different lengths");

    var sortedKeysWithIndices = keys
        .Select((key, index) =>
            new { key, index })
        .OrderBy(keyIndex => keyIndex.key);

    var shuffleIndices = sortedKeysWithIndices
        .Select(keyIndex => keyIndex.index)
        .ToArray();

    var sortedValues = values.ShuffleFromIndices(shuffleIndices);

    var sortedKeys = sortedKeysWithIndices
            .Select(keyIndex => keyIndex.key)
            .ToArray();

    return new Tuple<TKey[], TValue[]>(sortedKeys,
        sortedValues);
}

static void Main(string[] args)
{
    var keys = GetKeys();
    var values = GetValues();

    Console.WriteLine("Before");
    PrintKeyValues(keys, values);


    Console.WriteLine();
    Console.WriteLine("With LINQ");

    var sorted = SortNotInPlace(keys, values);
    var sortedKeys = sorted.Item1;
    var sortedValues = sorted.Item2;
    PrintKeyValues(sortedKeys, sortedValues);

    Console.ReadKey();            
}

3. High performance

If you really care about performance and need no-additional-memory in-place sorting, then you will have to implement your own Sort method, that synchronously sorts both lists.

Community
  • 1
  • 1
Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53