#c# #.net #list #dictionary
#c# #.net #Список #словарь
Вопрос:
Предположим, у меня есть два результата запроса, сохраненные в двух разных CSV-файлах (1.csv, 2.csv), данные выглядят следующим образом:
- 1.csv:
- 2.csv
dim1 amp; dim2 будут присутствовать в каждом результате таблицы запроса, поля могут быть разными.
Как я могу получить результат, который выглядит следующим образом?
- Результат
Поля в таблице результатов объединены уникальным образом, а строки сгруппированы по ключу (dim1 и dim2)
Я использовал List<List<string>>
для представления каждого результата таблицы:
public static List<List<string>> R1 { get; } = new List<List<string>>
{
new List<string> {"dim1", "dim2", "field1"},
new List<string> {"2", "wwa", "3"},
new List<string> {"1", "arr", "6"}
};
public static List<List<string>> R2 { get; } = new List<List<string>>
{
new List<string> {"dim1", "dim2", "field2", "field3"},
new List<string> {"2", "jaja", "1000", "2"},
new List<string> {"3", "wwa", "1000", "3"},
new List<string> {"1", "arr", "2000", "4"}
};
И я выбрал следующую стратегию:
var keys = 2;
var results = new List<List<string>>();
foreach (var r1 in R1)
{
var range = r1.GetRange(0, keys);
var hash = range.GetKey();
var found = false;
var row = new List<string>();
foreach (var r2 in R2)
{
if (r2.GetRange(0, keys).GetKey() == hash)
{
row.AddRange(range);
row.AddRange(r1.GetRange(keys, r1.Count - keys));
row.AddRange(r2.GetRange(keys, r2.Count - keys));
results.Add(row);
found = true;
}
}
if (!found)
{
row.AddRange(r1);
R2[0].GetRange(keys, R2[0].Count - keys).ForEach(e => row.Add(null));
results.Add(row);
}
}
foreach (var r2 in R2)
{
var range = r2.GetRange(0, keys);
var hash = range.GetKey();
var found = false;
var row = new List<string>();
foreach (var r in results)
{
if (r.GetRange(0, keys).GetKey() == hash)
found = true;
}
if (!found)
{
row.AddRange(range);
R1[0].GetRange(keys, R1[0].Count - keys).ForEach(e => row.Add(null));
row.AddRange(r2.GetRange(keys, r2.Count - keys));
results.Add(row);
}
}
Как вы можете видеть, у меня сложность 2xN ^ 2, и я думаю, что должно быть лучшее решение для генерации нового, List<List<string>>
которое подходит для приведенного выше примера.
Комментарии:
1. Вы изучали некоторые фреймворки ETL, которые поддерживают операции объединения?
2. это должно быть сделано в памяти, потому что столбцы являются динамическими, dim1, dim2 могут отличаться для других запросов, и существуют сценарии, в которых вы можете использовать одно или несколько измерений в качестве ключа группировки.
Ответ №1:
Объединение списка может быть выполнено следующим образом
private void JoinList(List<List<string>> listToJoin, int keysNumber, int paddingAfterKeys = 0)
{
// you need the padding if an element is found only in the second list
// this is a list that will be added to the result
var paddingList = new List<string>();
for (var i = 0; i < paddingAfterKeys; i )
{
// feel free to change it to null or what value fit your solution
paddingList.Add("0");
}
foreach (var t in listToJoin)
{
// create a key
var keyString = string.Join(',', t.Take(keysNumber));
if (result.TryGetValue(keyString, out var fieldsList))
{
// if the key already exist just add the values except the keys values this way you won't get duplicate keys
fieldsList.AddRange(t.Skip(keysNumber));
}
else
{
// get the keys, pad the list if needed and the rest of the keys
fieldsList = t.Take(keysNumber).ToList();
fieldsList.AddRange(paddingList);
fieldsList.AddRange(t.Skip(keysNumber));
// add new key to the dictionary and set the value in my program the result was a private variable of the class for the ease of use.
result[keyString] = fieldsList;
}
}
}
Если ключи не являются первыми, вы можете создать список, в котором хранятся индексы, и получить ключи из index.
Чтобы организовать это, вам понадобится что-то вроде этого:
public void ProcessLists()
{
const int keysNumber = 2;
var totalLength = R1[0].Count R2[0].Count - keysNumber;
// add first list to dictionary
JoinList(R1, keysNumber);
var paddingAfterKeys = R1[0].Count - keysNumber;
// add the second list to dictionary and add padding if a key was not found in dictionary
JoinList(R2, keysNumber, paddingAfterKeys);
// add padding to the end if a key was found in first list but not in second
paddingAfterKeys = R2[0].Count - keysNumber;
var paddingList = new List<string>();
for (var i = 0; i < paddingAfterKeys; i )
{
paddingList.Add("0");
}
foreach (var keyValuePair in result.Where(x => x.Value.Count < totalLength))
{
keyValuePair.Value.AddRange(paddingList);
}
}
Ответ №2:
Вы можете создавать строго типизированные объекты по образцу ваших записей CSV, как входной записи, так и вашей окончательной выходной записи.Затем используйте LINQ для объединения ваших записей.
Вот небольшой рабочий пример.
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp2
{
class Program
{
public static List<CsvOne> InitCsvOne()
{
//mock pulling in data
List<CsvOne> csv = new List<CsvOne>
{
new CsvOne { dim1 = 2,dim2 = "wwa", field1 =3 },
new CsvOne { dim1 = 1, dim2 = "arr", field1 = 6}
};
return csv;
}
public static List<CsvTwo> InitCsvTwo()
{
//mock pulling in data
List<CsvTwo> csv = new List<CsvTwo>
{
new CsvTwo { dim1 = 2,dim2 = "jaja", field2 = 1000, field3 =2 },
new CsvTwo { dim1 = 3, dim2 = "waa", field2 = 1000, field3 = 3 },
new CsvTwo { dim1 = 1, dim2 = "arr", field2 = 2000, field3 = 4},
};
return csv;
}
static void Main(string[] args)
{
var csvOne = InitCsvOne();
var csvTwo = InitCsvTwo();
var csvThree = new List<CsvThree>();
//get the ball rolling with csv one
csvOne.ForEach(record =>csvThree.Add(new CsvThree(record)));
//now either match up one with two or add with field1 being 0
//if we already have a matching dim1 and dim2, lets update the two new fields.
//note that we do not add another one if there are two the same
csvTwo.ForEach(record =>
{
if (csvThree.Any(t => (t.dim1 == record.dim1 amp;amp; t.dim2 == record.dim2)))
{
//combine the match with fields 2 and 3
var theMatch = csvThree.FirstOrDefault(t => (t.dim1 == record.dim1 amp;amp; t.dim2 == record.dim2));
theMatch.field2 = record.field2;
theMatch.field3 = record.field3;
}
else //add this new record to the list
{
csvThree.Add(new CsvThree(record));
}
});
csvThree = csvThree.OrderBy(t => t.dim1).ThenBy(t=>t.dim2).ToList(); //or whatever you want
//check it
Console.WriteLine($"Csv One Records");
Console.WriteLine($" dim1|dim2|field1");
csvOne.ForEach(record =>
{
Console.WriteLine(record.ToString());
});
Console.WriteLine($"Csv Two Records");
Console.WriteLine($"dim1|dim2|field2|field3");
csvTwo.ForEach(record =>
{
Console.WriteLine(record.ToString());
});
Console.WriteLine($"Csv Three Records");
Console.WriteLine($"dim1|dim2|field1|field2|field3");
csvThree.ForEach(record =>
{
Console.WriteLine(record.ToString());
});
Console.WriteLine("Press any key to exit...");
var wait = Console.ReadKey();
}
}
public abstract class dim
{
public int dim1 { get; set; }
public string dim2 { get; set; }
}
public class CsvOne:dim
{
public int field1 { get; set; }
public CsvOne()
{
field1 = 0;
}
public override string ToString()
{
return $"{dim1} |{dim2}|{field1}";
}
}
public class CsvTwo:dim
{
public int field2 { get; set; }
public int field3 { get; set; }
public CsvTwo()
{
field2 = field3 = 0;
}
public override string ToString()
{
return $"{dim1} |{dim2}|{field2}|{field3}";
}
}
public class CsvThree : dim
{
public int field1 { get; set; }
public int field2 { get; set; }
public int field3 { get; set; }
public CsvThree()
{
field1 = field2 = field3 = 0;
}
public CsvThree(CsvOne value)
{
field1 = field2 = field3 = 0;
dim1 = value.dim1;
dim2 = value.dim2;
field1 = value.field1;
}
public CsvThree(CsvTwo value)
{
field1 = field2 = field3 = 0;
dim1 = value.dim1;
dim2 = value.dim2;
field2 = value.field2;
field3 = value.field3;
}
public override string ToString()
{
return $"{dim1} |{dim2}|{field1}|{field2}|{field3}";
}
}
}
Приводит к:
Csv One Records
dim1|dim2|field1
2 |wwa|3
1 |arr|6
Csv Two Records
dim1|dim2|field2|field3
2 |jaja|1000|2
3 |waa|1000|3
1 |arr|2000|4
Csv Three Records
dim1|dim2|field1|field2|field3
1 |arr|6|2000|4
2 |jaja|0|1000|2
2 |wwa|3|0|0
3 |waa|0|1000|3
Press any key to exit...