使用 Epplus 删除 Excel 中的重复行
Deleting duplicate rows in Excel using Epplus
我有一个包含多行和多列的工作表。我想删除此工作表中的所有 duplicate 行。换句话说,应该删除此屏幕截图中 highlighted 行,下面的行应上移:
结果应如下:
我正在使用以下代码片段:
List<int> rowsToDelete = new List<int>();
for (int row = 1; row <= worksheet.Dimension.End.Row; row++)
{
string a = worksheet.Cells[row,1].Value.ToString();
string b = worksheet.Cells[row,2].Value.ToString();
string c = worksheet.Cells[row,3].Value.ToString();
int i = row + 1;
while (worksheet.Cells[i,1].Value.ToString().Equals(a) &&
worksheet.Cells[i,2].Value.ToString().Equals(b) &&
worksheet.Cells[i,3].Value.ToString().Equals(c))
{
rowsToDelete.add(i);
i++;
}
}
foreach (var row in rowsToDelete)
{
worksheet.Delete(row);
}
它没有删除正确的行。我该如何解决这个问题?
这是使用 Epplus 4.5.3.3 和 .NET Framework 4.6.1
我已经用另一种方式解决了您的问题:我创建了两个额外的列,“CONCAT”和“COUNT”:
- “CONCAT”包含公式
=A2+B2+C2
(直到数组末尾)
- "COUNT" 包含公式
==COUNTIF(D:D,D2)
(也直到数组末尾)
从那时起,只需编写一个 VBA 宏,将值“E9”检查回“E2”,如果值大于 1,则删除整行。
我只能假设你误解了我对发布的 while
声明的评论…
while (worksheet.Cells[i,1].Value.ToString().Equals(a) &&
worksheet.Cells[i,2].Value.ToString().Equals(b) &&
worksheet.Cells[i,3].Value.ToString().Equals(c)) { …
只有当重复的行是连续的时,这才有效。例如,使用第一张发布的图片,假设有第九 (9) 行,并且在这一行中我们有“重复”单元格值“a”、“b”和“c”。因此,当 while
循环开始时,第 2 行的计算结果为 true
,因为该行是第 1 行的副本。因此,行索引 2 被添加到列表中。在 while
循环的下一次迭代中,我们将添加第 3 行作为副本。但是,当我们到达第 4 行时,while
条件将计算为 false
,因为第 4 行不是第 1 行的副本。因此,while
循环将“退出”并且代码将循环回到初始 for
循环以检查下一行是否重复。此时,永远不会检查第 9 行的重复项,因此将其保留为重复行。
要点是,如果其中一行不是重复行,您不想停止检查重复行。您需要继续遍历所有行,因为重复行可能在任何行上。
还应注意,避免“检查”已标记为重复的重复行可能会有所帮助。例如,使用相同的第一张图片,在第一次遍历“第一”行的行时,会将第 2 行和第 3 行添加为“重复”行。因此,当 while
循环退出并且我们循环回到下一行以检查它将是第 2 行时。但是第 2 行已经被标记为重复项,因此实际上没有必要检查该行是否有重复项.在下面的解决方案中,将检查我们正在检查的行是否已标记为重复。如果该行被标记为重复,那么我们将跳过该行。
接下来,实际删除行的最后一个 foreach
循环可能有一些问题。例如,假设要删除的行列表包含第 2、3 和 7 行。因此在 foreach
循环内...代码删除第 2 行。删除此行后,第 3 行现在是第 2 行并且第 4 行现在是第 3 行等等......因此在循环的下一次迭代中它将删除第 3 行,现在是第 2 (2) 行。我希望很清楚,以自上而下的方式删除行将行不通,因为一旦删除第一行,该行下方的所有行索引都会更改。
因此,如果我们想删除行索引列表中的适当行,那么,我们可以通过自下而上的方式删除行来完成此操作。如果我们自下而上删除行,那么我们就不必像自上而下删除行时那样担心混淆索引。
鉴于所有这些,我建议您将此问题分为两个步骤。第一步只是填充重复行的列表。请记住,由于我们将以自上而下的方式检查重复行,因此行索引列表可能不一定是有序的。例如,如果我们按照之前的建议添加重复的第 9 行,那么要删除的行索引列表将为 { 2, 3, 9, 7 }。 9 在 7 之前,因为第 9 行被发现与第 1 行重复,第 7 行被发现与第 6 行重复。这里的要点是列表可能不一定是有序的,这将创建如上所述的问题。
因此,在我们得到要删除的行索引列表后,我们将对列表进行排序。这会将列表设置为 { 2, 3, 7, 9 }。此时我们可以简单地从列表底部开始删除行,或者在下面的示例中我们将简单地反转列表,使其变为 { 9, 7, 3, 2 }。然后我们将有一个 ints
的有序列表,从高到低排列。现在 for
循环遍历列表应该不会混淆行索引。
为了提供帮助,我建议您创建一个方法,该方法接受一个打开的工作表和 return我们要删除的“未排序”行索引列表。为了简化事情,所有代码所做的就是添加重复行的行索引。遍历下面的代码,我们首先遍历工作表中的所有行。如果我们到达已被标记为重复的行,那么我们将跳过该行。
如果该行未标记为重复,则代码将启动另一个 for
循环,从下一行开始到最后一行结束。同样,如果我们到达已经标记为重复的行,那么我们将跳过该行。一旦代码遍历了所有行,我们只需 return 要删除的行索引列表。
private List<int> GetDuplicateRowsToDelete(ExcelWorksheet worksheet) {
List<int> rowsToDelete = new List<int>();
string a, b, c;
for (int i = 1; i <= worksheet.Dimension.End.Row; i++) {
if (!rowsToDelete.Contains(i)) {
a = worksheet.Cells[i, 1].Value.ToString();
b = worksheet.Cells[i, 2].Value.ToString();
c = worksheet.Cells[i, 3].Value.ToString();
for (int j = i + 1; j <= worksheet.Dimension.End.Row; j++) {
if (!rowsToDelete.Contains(j)) {
if (worksheet.Cells[j, 1].Value.ToString().Equals(a) &&
worksheet.Cells[j, 2].Value.ToString().Equals(b) &&
worksheet.Cells[j, 3].Value.ToString().Equals(c)) {
rowsToDelete.Add(j);
}
}
}
}
}
return rowsToDelete;
}
最后我们可以利用这个方法获取要删除的索引,然后我们将列表排序和反转,然后从下往上删除行。像……
private void button1_Click(object sender, EventArgs e) {
FileInfo newFile = new FileInfo(@"D:\Test\Excel_Test\RemoveDup1.xlsx");
using (ExcelPackage pck = new ExcelPackage(newFile)) {
using (ExcelWorksheet worksheet = pck.Workbook.Worksheets[0]) {
List<int> rowsToDel = GetDuplicateRowsToDelete(worksheet);
rowsToDel.Sort();
rowsToDel.Reverse();
foreach (int rowIndex in rowsToDel) {
worksheet.DeleteRow(rowIndex);
}
pck.Save();
}
}
MessageBox.Show("Removed duplicates complete");
}
我希望这是有道理的并且有所帮助。
我有一个包含多行和多列的工作表。我想删除此工作表中的所有 duplicate 行。换句话说,应该删除此屏幕截图中 highlighted 行,下面的行应上移:
结果应如下:
我正在使用以下代码片段:
List<int> rowsToDelete = new List<int>();
for (int row = 1; row <= worksheet.Dimension.End.Row; row++)
{
string a = worksheet.Cells[row,1].Value.ToString();
string b = worksheet.Cells[row,2].Value.ToString();
string c = worksheet.Cells[row,3].Value.ToString();
int i = row + 1;
while (worksheet.Cells[i,1].Value.ToString().Equals(a) &&
worksheet.Cells[i,2].Value.ToString().Equals(b) &&
worksheet.Cells[i,3].Value.ToString().Equals(c))
{
rowsToDelete.add(i);
i++;
}
}
foreach (var row in rowsToDelete)
{
worksheet.Delete(row);
}
它没有删除正确的行。我该如何解决这个问题?
这是使用 Epplus 4.5.3.3 和 .NET Framework 4.6.1
我已经用另一种方式解决了您的问题:我创建了两个额外的列,“CONCAT”和“COUNT”:
- “CONCAT”包含公式
=A2+B2+C2
(直到数组末尾) - "COUNT" 包含公式
==COUNTIF(D:D,D2)
(也直到数组末尾)
从那时起,只需编写一个 VBA 宏,将值“E9”检查回“E2”,如果值大于 1,则删除整行。
我只能假设你误解了我对发布的 while
声明的评论…
while (worksheet.Cells[i,1].Value.ToString().Equals(a) &&
worksheet.Cells[i,2].Value.ToString().Equals(b) &&
worksheet.Cells[i,3].Value.ToString().Equals(c)) { …
只有当重复的行是连续的时,这才有效。例如,使用第一张发布的图片,假设有第九 (9) 行,并且在这一行中我们有“重复”单元格值“a”、“b”和“c”。因此,当 while
循环开始时,第 2 行的计算结果为 true
,因为该行是第 1 行的副本。因此,行索引 2 被添加到列表中。在 while
循环的下一次迭代中,我们将添加第 3 行作为副本。但是,当我们到达第 4 行时,while
条件将计算为 false
,因为第 4 行不是第 1 行的副本。因此,while
循环将“退出”并且代码将循环回到初始 for
循环以检查下一行是否重复。此时,永远不会检查第 9 行的重复项,因此将其保留为重复行。
要点是,如果其中一行不是重复行,您不想停止检查重复行。您需要继续遍历所有行,因为重复行可能在任何行上。
还应注意,避免“检查”已标记为重复的重复行可能会有所帮助。例如,使用相同的第一张图片,在第一次遍历“第一”行的行时,会将第 2 行和第 3 行添加为“重复”行。因此,当 while
循环退出并且我们循环回到下一行以检查它将是第 2 行时。但是第 2 行已经被标记为重复项,因此实际上没有必要检查该行是否有重复项.在下面的解决方案中,将检查我们正在检查的行是否已标记为重复。如果该行被标记为重复,那么我们将跳过该行。
接下来,实际删除行的最后一个 foreach
循环可能有一些问题。例如,假设要删除的行列表包含第 2、3 和 7 行。因此在 foreach
循环内...代码删除第 2 行。删除此行后,第 3 行现在是第 2 行并且第 4 行现在是第 3 行等等......因此在循环的下一次迭代中它将删除第 3 行,现在是第 2 (2) 行。我希望很清楚,以自上而下的方式删除行将行不通,因为一旦删除第一行,该行下方的所有行索引都会更改。
因此,如果我们想删除行索引列表中的适当行,那么,我们可以通过自下而上的方式删除行来完成此操作。如果我们自下而上删除行,那么我们就不必像自上而下删除行时那样担心混淆索引。
鉴于所有这些,我建议您将此问题分为两个步骤。第一步只是填充重复行的列表。请记住,由于我们将以自上而下的方式检查重复行,因此行索引列表可能不一定是有序的。例如,如果我们按照之前的建议添加重复的第 9 行,那么要删除的行索引列表将为 { 2, 3, 9, 7 }。 9 在 7 之前,因为第 9 行被发现与第 1 行重复,第 7 行被发现与第 6 行重复。这里的要点是列表可能不一定是有序的,这将创建如上所述的问题。
因此,在我们得到要删除的行索引列表后,我们将对列表进行排序。这会将列表设置为 { 2, 3, 7, 9 }。此时我们可以简单地从列表底部开始删除行,或者在下面的示例中我们将简单地反转列表,使其变为 { 9, 7, 3, 2 }。然后我们将有一个 ints
的有序列表,从高到低排列。现在 for
循环遍历列表应该不会混淆行索引。
为了提供帮助,我建议您创建一个方法,该方法接受一个打开的工作表和 return我们要删除的“未排序”行索引列表。为了简化事情,所有代码所做的就是添加重复行的行索引。遍历下面的代码,我们首先遍历工作表中的所有行。如果我们到达已被标记为重复的行,那么我们将跳过该行。
如果该行未标记为重复,则代码将启动另一个 for
循环,从下一行开始到最后一行结束。同样,如果我们到达已经标记为重复的行,那么我们将跳过该行。一旦代码遍历了所有行,我们只需 return 要删除的行索引列表。
private List<int> GetDuplicateRowsToDelete(ExcelWorksheet worksheet) {
List<int> rowsToDelete = new List<int>();
string a, b, c;
for (int i = 1; i <= worksheet.Dimension.End.Row; i++) {
if (!rowsToDelete.Contains(i)) {
a = worksheet.Cells[i, 1].Value.ToString();
b = worksheet.Cells[i, 2].Value.ToString();
c = worksheet.Cells[i, 3].Value.ToString();
for (int j = i + 1; j <= worksheet.Dimension.End.Row; j++) {
if (!rowsToDelete.Contains(j)) {
if (worksheet.Cells[j, 1].Value.ToString().Equals(a) &&
worksheet.Cells[j, 2].Value.ToString().Equals(b) &&
worksheet.Cells[j, 3].Value.ToString().Equals(c)) {
rowsToDelete.Add(j);
}
}
}
}
}
return rowsToDelete;
}
最后我们可以利用这个方法获取要删除的索引,然后我们将列表排序和反转,然后从下往上删除行。像……
private void button1_Click(object sender, EventArgs e) {
FileInfo newFile = new FileInfo(@"D:\Test\Excel_Test\RemoveDup1.xlsx");
using (ExcelPackage pck = new ExcelPackage(newFile)) {
using (ExcelWorksheet worksheet = pck.Workbook.Worksheets[0]) {
List<int> rowsToDel = GetDuplicateRowsToDelete(worksheet);
rowsToDel.Sort();
rowsToDel.Reverse();
foreach (int rowIndex in rowsToDel) {
worksheet.DeleteRow(rowIndex);
}
pck.Save();
}
}
MessageBox.Show("Removed duplicates complete");
}
我希望这是有道理的并且有所帮助。