Linq Outer Join в массиве объектов - PullRequest
1 голос
/ 23 марта 2012

Рассмотрим набор из 6 массивов [StringKey, Value] в псевдокоде:

object[,2][6] KeyValueArrays; //illustr as array, could also be List<> or Dict<>

Я хотел бы преобразовать это в одну таблицу:

object[,7] KeyWithUpTo6Values  //outer join of the 6 orig key-value sets

Если данный ключ встречается в (скажем) 3 из 6 исходных массивов [Key, Value], то объединенная строка для этого ключа будет содержать 3 значения и 3 нуля.

Вопрос: могу ли я сделать это с помощью LINQ, используя только простые контейнеры, такие как массивы, общие списки и словари?

Ответы [ 2 ]

2 голосов
/ 23 марта 2012

Я думаю, что я что-то упускаю, но тот факт, что в вашем вопросе упоминается общие списки и словари , я думаю, что когда вы говорите массив, вы имеете в виду некий король структуры векторных данных.

Таким образом, пары ключ-значение могут быть сохранены с использованием Dictionary<T1,T2>. Например, предположим, что вы используете ключ string и класс значений MyValueClass с одним свойством целочисленного типа. Ваша декларация данных будет выглядеть так:

class Program
{
    class MyValueClass
    {
        public int Value {get;set;}
    }

    // Other elements elided for clarity

    private Dictionary<string, MyValueClass> data = new Dictionary<string, MyValueClass>();

}

Теперь вы заявили, что у вас будет N таких структур, для которых вы хотите выполнить внешнее соединение. Например,

private Dictionary<string, MyValueClass>[] data = new Dictionary<string, MyValueClass>[6]();

Проблема здесь в том, что количество «столбцов» в типе объединенной структуры зависит от этого N, но если вы не используете какой-либо другой тип структуры данных (например, список) для представления ваша строка , вы не сможете сделать это динамически, т. е. для любого N, потому что данные в C # объявляются статически.

Чтобы проиллюстрировать это, проверьте запрос ниже, где я предполагаю, что массив имеет размерность 4:

var query = from d0 in _data[0]
            join d1 in _data[1] on d0.Key equals d1.Key into d1joined
            from d1 in d1joined.DefaultIfEmpty()
            join d2 in _data[2] on d1.Key equals d2.Key into d2joined
            from d2 in d2joined.DefaultIfEmpty()
            join d3 in _data[3] on d2.Key equals d3.Key into d3joined
            from d3 in d3joined.DefaultIfEmpty()
            select new
                     {
                         d0.Key,
                         D0 = d0.Value,
                         D1 = d1.Value,
                         D2 = d2.Value,
                         D3 = d3.Value,
                      };

Не зацикливайтесь на соединениях, я объясню позже, но проверьте оператор select new. Обратите внимание: когда Linq собирает этот анонимный тип, он должен знать точное число свойств - наших столбцов - потому что это часть синтаксиса.

Так что если вы хотите, вы можете написать запрос, чтобы выполнить то, что вы просите, но он будет работать только для известного значения N. В случае, если это окажется достаточным решением, на самом деле это просто, хотя пример I написано может быть немного сложнее. Возвращаясь к приведенному выше запросу, вы увидите повторяющийся шаблон из / join / from DefaultIfEmpty . Этот шаблон был скопирован с здесь , и он работает на самом деле просто: он соединяет две структуры одним ключом в другую результирующую структуру (into dnjoined выше). Linq обработает все записи в левом списке и для каждой из них обработает все записи в правом списке (декартовой план N1 x N2), например:

foreach (var d0 in _data[0])
{
    foreach (var d1 in _data[1])
    {
        if (d0.Key == d1.Key) 
        {
            // Produces an anonymous structure of { d0.Key, d0.Value, d1.Value }
            // and returns it.
        }
    }
}

Итак, операция внутреннее объединение такая же, как объединение каждой строки с последующим выбором тех, где ключи совпадают. Внешнее соединение отличается созданием строки, даже если ключ не совпадает, поэтому в нашем псевдокоде это будет выглядеть примерно так:

foreach (var d0 in _data[0])
{
    foreach (var d1 in _data[1])
    {
        if (d0.Key == d1.Key) 
        {
            // Produces an anonymous structure of { d0.Key, d0.Value, d1.Value }
            // and returns it.
        }
        else
        {
            // Produce a anonymous structure of {d0.Key, d0.Value, null}
        }     
    }
}

Блок else достигается в коде LINQ раньше, добавляя второе предложение where, которое запрашивает строки, даже если совпадений нет, - это пустой список, который может возвращать данные при вызове DefaultIfEmpty. (еще раз, смотрите ссылку выше, чтобы получить больше информации)

Ниже я напишу полный пример, который использует структуру данных и запрос linq, о котором я упоминал выше. Надеюсь, это говорит само за себя:

using System;
using System.Collections.Generic;
using System.Linq;

namespace TestZone
{
    class Example
    {
        #region Types
        class MyValue
        {
            public int Value { get; set; }

            public override string ToString()
            {
                return string.Format("MyValue(Value = {0})", Value);
            }
        }
        #endregion // Types

        #region Constants
        /// <summary>
        /// Our N
        /// </summary>
        private const int NumberOfArrays = 4;

        /// <summary>
        /// How many rows per dictionary
        /// </summary>
        private const int NumberOfRows = 10; 
        #endregion // Constants

        #region Fields
        private Dictionary<string, MyValue>[] _data = new Dictionary<string, MyValue>[NumberOfArrays]; 
        #endregion // Fields

        #region Constructor
        public Example()
        {
            for (var index = 0; index < _data.Length; index++)
            {
                _data[index] = new Dictionary<string, MyValue>(NumberOfRows);
            }
        } 
        #endregion // Constructor

        public void GenerateRandomData()
        {
            var rand = new Random(DateTime.Now.Millisecond);

            foreach (var dict in _data)
            {
                // Add a number of rows
                for (var i = 0; i < NumberOfRows; i++)
                {
                    var integer = rand.Next(10);    // We use a value of 10 so we have many collisions.
                    dict["ValueOf" + integer] = new MyValue { Value = integer };
                }
            }
        }

        public void OuterJoin()
        {
            // To get the outer join, we have to know the expected N before hand, as this example will show.
            // Do multiple joins
            var query = from d0 in _data[0]
                        join d1 in _data[1] on d0.Key equals d1.Key into d1joined
                        from d1 in d1joined.DefaultIfEmpty()
                        join d2 in _data[2] on d1.Key equals d2.Key into d2joined
                        from d2 in d2joined.DefaultIfEmpty()
                        join d3 in _data[3] on d2.Key equals d3.Key into d3joined
                        from d3 in d3joined.DefaultIfEmpty()
                        select new
                                   {
                                       d0.Key,
                                       D0 = d0.Value,
                                       D1 = d1.Value,
                                       D2 = d2.Value,
                                       D3 = d3.Value,
                                   };

            foreach (var q in query)
            {
                Console.WriteLine(q);
            }
        }
    }

    class Program
    {

        public static void Main()
        {
            var m = new Example();
            m.GenerateRandomData();
            m.OuterJoin();

        }
    }
}
0 голосов
/ 23 марта 2012

Многомерные массивы не поддерживают IEnumerable<T>, поэтому вы не сможете использовать LINQ.С другой стороны, зазубренные массивы могут обрабатываться LINQ.

...