Preserve all matching Excel rows when merging
This commit is contained in:
1 parent
8fa4a3d7b3
commit
5c9572ce9d
3 files changed
+173
-196
No files matched your search
+28
-54
@@ -137,8 +137,8 @@ public sealed class MainWindow : Window
|
||||
private readonly RadioButton _rbBoth = new() { Content = "Совпадения + чего нет в A" };
|
||||
private readonly RadioButton _rbExact = new() { Content = "Точное совпадение", IsChecked = true };
|
||||
private readonly RadioButton _rbPartial = new() { Content = "Частичное (по фрагменту)" };
|
||||
private readonly CheckBox _chkDedupe = new() { Content = "Удалять дубликаты по ключевым колонкам" };
|
||||
private readonly CheckBox _chkCleanCols = new() { Content = "Убирать одинаковые колонки после слияния", IsChecked = true };
|
||||
private readonly CheckBox _chkDedupe = new() { Content = "Удалять полностью одинаковые строки" };
|
||||
private readonly CheckBox _chkCleanCols = new() { Content = "Объединять одноимённые колонки", IsChecked = true };
|
||||
|
||||
private readonly RadioButton _rbFuzzy = new() { Content = "Нечёткое (по сходству %)" };
|
||||
private readonly Slider _slFuzzy = new()
|
||||
@@ -561,7 +561,7 @@ private Control BuildFilePanel(TextBox tb, Button browse, ComboBox cb, StackPane
|
||||
|
||||
var colLabel = new TextBlock
|
||||
{
|
||||
Text = "Ключевые колонки для сопоставления",
|
||||
Text = "Ключи (выберите соответствующие поля в одинаковом порядке)",
|
||||
Foreground = Ui.Muted,
|
||||
FontWeight = FontWeight.SemiBold,
|
||||
FontSize = 12
|
||||
@@ -900,6 +900,11 @@ private Control BuildFilePanel(TextBox tb, Button browse, ComboBox cb, StackPane
|
||||
ShowError("Отметьте хотя бы одну ключевую колонку в каждой таблице (по умолчанию отмечена первая).");
|
||||
return;
|
||||
}
|
||||
if (mainUsed.Count(x => x) != secUsed.Count(x => x))
|
||||
{
|
||||
ShowError("Выберите одинаковое число ключевых колонок в обеих таблицах.");
|
||||
return;
|
||||
}
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
_cts = cts;
|
||||
@@ -978,8 +983,9 @@ private Control BuildFilePanel(TextBox tb, Button browse, ComboBox cb, StackPane
|
||||
sb.Append($"Совпадений: {r.Matches.Count} · Только в A: {r.OnlyMain.Count} · Только в B: {r.OnlySecondary.Count}.");
|
||||
if (r.Pairs.Count > 0)
|
||||
{
|
||||
double avg = Math.Round(r.Pairs.Average(p => p.BCount), 2);
|
||||
int max = r.Pairs.Max(p => p.BCount);
|
||||
var countsPerMainRow = r.Pairs.GroupBy(p => p.RowA).Select(group => group.First().BCount).ToList();
|
||||
double avg = Math.Round(countsPerMainRow.Average(), 2);
|
||||
int max = countsPerMainRow.Max();
|
||||
sb.Append($" Пары A↔B: {r.Pairs.Count}, ср. N→M: {avg.ToString(CultureInfo.InvariantCulture)}, макс: {max}.");
|
||||
}
|
||||
sb.Append($" Ключи заполнены: A={covA.ToString(CultureInfo.InvariantCulture)}%, B={covB.ToString(CultureInfo.InvariantCulture)}%.");
|
||||
@@ -1081,59 +1087,27 @@ private Control BuildFilePanel(TextBox tb, Button browse, ComboBox cb, StackPane
|
||||
break;
|
||||
|
||||
default:
|
||||
// Build a union of both schemas. Columns with the same header are
|
||||
// aligned; B-only columns get their own position in the result.
|
||||
headers = new List<string>(main.Columns);
|
||||
var mainColumnMap = Enumerable.Range(0, main.Columns.Count).ToArray();
|
||||
var secondaryColumnMap = new int[sec.Columns.Count];
|
||||
var matchedSecondaryColumns = new HashSet<int>();
|
||||
|
||||
for (int b = 0; b < sec.Columns.Count; b++)
|
||||
{
|
||||
int matchingMain = -1;
|
||||
for (int a = 0; a < main.Columns.Count; a++)
|
||||
{
|
||||
if (!matchedSecondaryColumns.Contains(a) &&
|
||||
string.Equals(main.Columns[a].Trim(), sec.Columns[b].Trim(), StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
matchingMain = a;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchingMain >= 0)
|
||||
{
|
||||
secondaryColumnMap[b] = matchingMain;
|
||||
matchedSecondaryColumns.Add(matchingMain);
|
||||
}
|
||||
else
|
||||
{
|
||||
var header = sec.Columns[b];
|
||||
if (headers.Contains(header, StringComparer.OrdinalIgnoreCase))
|
||||
header = $"B: {header}";
|
||||
secondaryColumnMap[b] = headers.Count;
|
||||
headers.Add(header);
|
||||
}
|
||||
}
|
||||
|
||||
string[] CombineRows(string[] rowA, string[] rowB)
|
||||
{
|
||||
var combined = new string[headers.Count];
|
||||
Array.Fill(combined, "");
|
||||
for (int a = 0; a < mainColumnMap.Length && a < rowA.Length; a++)
|
||||
combined[mainColumnMap[a]] = rowA[a];
|
||||
for (int b = 0; b < secondaryColumnMap.Length && b < rowB.Length; b++)
|
||||
combined[secondaryColumnMap[b]] = rowB[b];
|
||||
return combined;
|
||||
}
|
||||
|
||||
foreach (var pair in result.Pairs)
|
||||
data.Add(CombineRows(pair.RowA, pair.RowB));
|
||||
headers = _merged?.Columns ?? new List<string>(main.Columns);
|
||||
data = _merged is not null ? new List<string[]>(_merged.Rows) : new List<string[]>();
|
||||
sources = new List<string>();
|
||||
foreach (var _ in result.Pairs) sources.Add("A+B");
|
||||
foreach (var r in result.OnlySecondary)
|
||||
{
|
||||
data.Add(CombineRows(Array.Empty<string>(), r));
|
||||
var row = new string[headers.Count];
|
||||
Array.Fill(row, "");
|
||||
if (_merged is not null)
|
||||
{
|
||||
for (int b = 0; b < _merged.SecondaryColumnMap.Length && b < r.Length; b++)
|
||||
{
|
||||
int destination = _merged.SecondaryColumnMap[b];
|
||||
if (destination >= 0 && destination < row.Length) row[destination] = r[b];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int b = 0; b < r.Length && b < row.Length; b++) row[b] = r[b];
|
||||
}
|
||||
data.Add(row);
|
||||
sources.Add("B");
|
||||
}
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user