我正在尋找一種有效的方法來對二維陣列中的資料進行排序。該陣列可以有很多行和列,但在本例中,我將其限制為 6 行和 5 列。資料是字串,因為有些是單詞。我只在下面包含了一個單詞,但在真實資料中有幾列單詞。我意識到如果我們排序,我們應該將資料視為數字?
string[,] WeatherDataArray = new string[6,5];
資料是一組每天讀取并記錄的天氣資料。這些資料經過他們系統的許多部分,我無法更改,并且以需要排序的方式到達我這里。一個示例布局可能是:
Day number, temperature, rainfall, wind, cloud
資料矩陣可能如下所示
3,20,0,12,cumulus
1,20,0,11,none
23,15,0,8,none
4,12,0,1,cirrus
12,20,0,12,cumulus
9,15,2,11,none
他們現在希望對資料進行排序,以便溫度按降序排列,天數按升序排列。結果將是
1,20,0,11,none
3,20,0,12,cumulus
12,20,0,12,cumulus
9,15,2,11,none
23,15,0,0,none
4,12,0,1,cirrus
該陣列被存盤起來,然后他們可以將其提取到一個表中并對其進行大量分析。提取端沒有改變,因此我無法對表中的資料進行排序,我必須以正確的格式創建資料以匹配它們擁有的現有規則。
我可以決議陣列的每一行并對它們進行排序,但這似乎是一種非常耗時的方法。必須有一種更快更有效的方法來按兩列對這個二維陣列進行排序?我想我可以將它發送到一個函式并回傳排序后的陣列,例如:
private string[,] SortData(string[,] Data)
{
//In here we do the sorting
}
請問有什么想法嗎?
uj5u.com熱心網友回復:
我同意另一個答案,即最好將每一行資料決議為一個封裝資料的類的實體,從該資料創建一個新的一維陣列或串列。然后,您對該一維集合進行排序并將其轉換回二維陣列。
然而,另一種方法是撰寫一個IComparer類,您可以使用它來比較二維陣列中的兩行,如下所示:
public sealed class WeatherComparer: IComparer
{
readonly string[,] _data;
public WeatherComparer(string[,] data)
{
_data = data;
}
public int Compare(object? x, object? y)
{
int row1 = (int)x;
int row2 = (int)y;
double temperature1 = double.Parse(_data[row1, 1]);
double temperature2 = double.Parse(_data[row2, 1]);
if (temperature1 < temperature2)
return 1;
if (temperature2 < temperature1)
return -1;
int day1 = int.Parse(_data[row1,0]);
int day2 = int.Parse(_data[row2,0]);
return day1.CompareTo(day2);
}
}
請注意,這包括對要排序的二維陣列的參考,并根據需要決議元素以進行排序。
然后您需要創建一個一維索引陣列,這就是您實際要排序的內容。(您不能對二維陣列進行排序,但可以對參考二維陣列行的索引的一維陣列進行排序。)
public static string[,] SortData(string[,] data)
{
int[] indexer = Enumerable.Range(0, data.GetLength(0)).ToArray();
var comparer = new WeatherComparer(data);
Array.Sort(indexer, comparer);
string[,] result = new string[data.GetLength(0), data.GetLength(1)];
for (int row = 0; row < indexer.Length; row)
{
int dest = indexer[row];
for (int col = 0; col < data.GetLength(1); col)
result[dest, col] = data[row, col];
}
return result;
}
然后您可以呼叫SortData對資料進行排序:
public static void Main()
{
string[,] weatherDataArray = new string[6, 5]
{
{ "3", "20", "0", "12", "cumulus" },
{ "1", "20", "0", "11", "none" },
{ "23", "15", "0", "8", "none" },
{ "4", "12", "0", "1", "cirrus" },
{ "12", "20", "0", "12", "cumulus" },
{ "9", "15", "2", "11", "none" }
};
var sortedWeatherData = SortData(weatherDataArray);
for (int i = 0; i < sortedWeatherData.GetLength(0); i)
{
for (int j = 0; j < sortedWeatherData.GetLength(1); j)
Console.Write(sortedWeatherData[i,j] ", ");
Console.WriteLine();
}
}
輸出:
1, 20, 0, 11, none,
3, 20, 0, 12, cumulus,
12, 20, 0, 12, cumulus,
9, 15, 2, 11, none,
23, 15, 0, 8, none,
4, 12, 0, 1, cirrus,
請注意,此代碼不包含任何錯誤檢查 - 它假定資料中沒有空值,并且所有已決議的資料實際上都是可決議的。您可能想要添加適當的錯誤處理。
在 .NET Fiddle 上試用:https ://dotnetfiddle.net/mwXyMs
uj5u.com熱心網友回復:
我建議將資料決議為可以通過常規方法排序的物件。就像使用 LINQ:
myObjects.OrderBy(obj => obj.Property1)
.ThenBy(obj=> obj.Property2);
將資料視為字串表只會使處理更加困難,因為在每一步都需要決議值,處理潛在錯誤,因為字串可能為空或包含無效值等。這是一個更好的設計這種決議和錯誤處理在讀取資料時進行一次,并在將其寫入磁盤或將其移交給下一個系統時再次將其轉換為文本形式。
如果這是一個遺留系統,有很多部分以文本形式處理資料,我仍然會主張先決議資料,然后在單獨的模塊中進行分析,以便可以重用。這應該允許其他部分被部分重寫以使用物件格式。
如果這完全不可行,您要么需要將資料轉換為鋸齒狀陣列,即string[][]. 或者撰寫自己的排序,可以交換多維陣列中的行。
uj5u.com熱心網友回復:
我很高興嘗試做出比公認答案更好的東西,我想我做到了。
更好的理由:
- 它用于排序的列以及是升序還是降序不是硬編碼的,而是作為引數傳入的。在帖子中,我了解到他們將來可能會改變對如何對資料進行排序的想法。
- 它支持按不包含數字的列進行排序,因為如果他們想按名稱列排序。
- 在我的測驗中,對于大資料,它更快并且分配更少的記憶體。
速度更快的原因:
- 它從不兩次決議相同的 Data 索引。它快取數字。
- 復制時,它使用
Span.CopyTo而不是 indeces。 - 它不會創建新的 Data 陣列,而是對行進行適當的排序。這也意味著它不會復制已經在正確位置的行。
這是用法:
DataSorter.SortDataWithSortAguments(array, (1, false), (0, true));
這是代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace YourNamespace;
public static class DataSorter
{
public static void SortDataWithSortAguments(string[,] Data, params (int columnIndex, bool ascending)[] sortingParams)
{
if (sortingParams.Length == 0)
{
return;
// maybe throw an exception instead? depends on what you want
}
if (sortingParams.Length > 1)
{
var duplicateColumns =
from sortingParam in sortingParams
group false by sortingParam.columnIndex
into sortingGroup
where sortingGroup.Skip(1).Any()
select sortingGroup.Key;
var duplicateColumnsArray = duplicateColumns.ToArray();
if (duplicateColumnsArray.Length > 0)
{
throw new ArgumentException($"Cannot sort by the same column twice. Duplicate columns are: {string.Join(", ", duplicateColumnsArray)}");
}
}
for (int i = 0; i < sortingParams.Length; i )
{
int col = sortingParams[i].columnIndex;
if (col < 0 || col >= Data.GetLength(1))
{
throw new ArgumentOutOfRangeException($"Column index {col} is not within range 0 to {Data.GetLength(1)}");
}
}
int[] linearRowIndeces = new int[Data.GetLength(0)];
for (int i = 0; i < linearRowIndeces.Length; i )
{
linearRowIndeces[i] = i;
}
Span<int> sortedRows = SortIndecesByParams(Data, sortingParams, linearRowIndeces);
SortDataRowsByIndecesInPlace(Data, sortedRows);
}
private static float[]? GetColumnAsNumbersOrNull(string[,] Data, int columnIndex)
{
if (!float.TryParse(Data[0, columnIndex], out float firstNumber))
{
return null;
}
// if the first row of the given column is a number, assume all rows of the column should be numbers as well
float[] column = new float[Data.GetLength(0)];
column[0] = firstNumber;
for (int row = 1; row < column.Length; row )
{
if (!float.TryParse(Data[row, columnIndex], out column[row]))
{
throw new ArgumentException(
$"Rows 0 to {row - 1} of column {columnIndex} contained numbers, but row {row} doesn't");
}
}
return column;
}
private static Span<int> SortIndecesByParams(
string[,] Data,
ReadOnlySpan<(int columnIndex, bool ascending)> sortingParams,
IEnumerable<int> linearRowIndeces)
{
var (firstColumnIndex, firstAscending) = sortingParams[0];
var firstColumn = GetColumnAsNumbersOrNull(Data, firstColumnIndex);
IOrderedEnumerable<int> sortedRowIndeces = (firstColumn, firstAscending) switch
{
(null, true) => linearRowIndeces.OrderBy(row => Data[row, firstColumnIndex]),
(null, false) => linearRowIndeces.OrderByDescending(row => Data[row, firstColumnIndex]),
(not null, true) => linearRowIndeces.OrderBy(row => firstColumn[row]),
(not null, false) => linearRowIndeces.OrderByDescending(row => firstColumn[row])
};
for (int i = 1; i < sortingParams.Length; i )
{
var (columnIndex, ascending) = sortingParams[i];
var column = GetColumnAsNumbersOrNull(Data, columnIndex);
sortedRowIndeces = (column, ascending) switch
{
(null, true) => sortedRowIndeces.ThenBy(row => Data[row, columnIndex]),
(null, false) => sortedRowIndeces.ThenByDescending(row => Data[row, columnIndex]),
(not null, true) => sortedRowIndeces.ThenBy(row => column[row]),
(not null, false) => sortedRowIndeces.ThenByDescending(row => column[row])
};
}
return sortedRowIndeces.ToArray();
}
private static void SortDataRowsByIndecesInPlace(string[,] Data, Span<int> sortedRows)
{
Span<string> tempRow = new string[Data.GetLength(1)];
for (int i = 0; i < sortedRows.Length; i )
{
while (i != sortedRows[i])
{
Span<string> firstRow = MemoryMarshal.CreateSpan(ref Data[i, 0], tempRow.Length);
Span<string> secondRow = MemoryMarshal.CreateSpan(ref Data[sortedRows[i], 0], tempRow.Length);
firstRow.CopyTo(tempRow);
secondRow.CopyTo(firstRow);
tempRow.CopyTo(secondRow);
(sortedRows[i], sortedRows[sortedRows[i]]) = (sortedRows[sortedRows[i]], sortedRows[i]);
}
}
}
}
PS:考慮到我的責任,我不應該花這么多時間來做這件事,但這很有趣。
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/466413.html
上一篇:R按組之一重新排序ggplot
