I am looking for an efficient way to sort the data in a 2D array. The array can have many rows and columns but, in this example, I will just limit it to 6 rows and 5 columns. The data is strings as some are words. I only include one word below but in the real data there are a few columns of words. I realise if we sort, we should treat the data as numbers?
string[,] WeatherDataArray = new string[6,5];
The data is a set of weather data that is read every day and logged. This data goes through many parts of their system which I cannot change and it arrives to me in a way that it needs sorting. An example layout could be:
Day number, temperature, rainfall, wind, cloud
The matrix of data could look like this
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
They now want the data sorted so it will have temperature in descending order and day number in ascending order. The result would be
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
The array is stored and later they can extract it to a table and do lots of analysis on it. The extraction side is not changing so I cannot sort the data in the table, I have to create the data in the correct format to match the existing rules they have.
I could parse each row of the array and sort them but this seems a very long-handed method. There must be a quicker more efficient way to sort this 2D array by two columns? I think I could send it to a function and get returned the sorted array like:
private string[,] SortData(string[,] Data)
{
//In here we do the sorting
}
Any ideas please?
I agree with the other answer that it's probably best to parse each row of the data into an instance of a class that encapsulates the data, creating a new 1D array or list from that data. Then you'd sort that 1D collection and convert it back into a 2D array.
However another approach is to write an IComparer class that you can use to compare two rows in a 2D array like so:
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);
}
}
Note that this includes a reference to the 2D array to be sorted, and parses the elements for sorting as necessary.
Then you need to create a 1D array of indices, which is what you are actually going to sort. (You can't sort a 2D array, but you CAN sort a 1D array of indices that reference the rows of the 2D array.)
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;
}
Then you can call SortData to sort the data:
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();
}
}
Output:
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,
Note that this code does not contain any error checking - it assumes there are no nulls in the data, and that all the parsed data is in fact parsable. You might want to add appropriate error handling.
Try it on .NET Fiddle: https://dotnetfiddle.net/mwXyMs
I would suggest parsing the data into objects that can be sorted by conventional methods. Like using LINQ:
myObjects.OrderBy(obj => obj.Property1)
.ThenBy(obj=> obj.Property2);
Treating data as a table of strings will just make processing more difficult, since at every step you would need to parse values, handle potential errors since a string may be empty or contain an invalid value etc. It is a much better design to do all this parsing and error handling once when the data is read, and convert it to text-form again when writing it to disk or handing it over to the next system.
If this is a legacy system with lots of parts that handle the data in text-form I would still argue to parse the data first, and do it in a separate module so it can be reused. This should allow the other parts to be rewritten part by part to use the object format.
If this is completely infeasible you either need to convert the data to a jagged array, i.e. string[][]. Or write your own sorting that can swap rows in a multidimensional array.
I had fun trying to make something better than the accepted answer, and I think I did.
Reasons it is better:
Which columns it uses to sort and whether in an ascending or descending order is not hardcoded, but passed in as parameters. In the post, I understood that they might change their mind in the future as to how to sort the data.
It supports sorting by columns that do not contain numbers, for if they ever want to sort by the name columns.
In my testing, for large data, it is much faster and allocates less memory.
Reasons it is faster:
It never parses the same index of Data twice. It caches the numbers.
When copying, it uses Span.CopyTo instead of indeces.
It doesn't create a new Data array, it sorts the rows in place. This also means it won't copy the rows that are already in the correct spots.
Here's the usage:
DataSorter.SortDataWithSortAguments(array, (1, false), (0, true));
And here's the code:
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: I should not have spent so much time working on this considering my responsibilities, but it was fun.
Related
I'm new to c# and want to process strings according to the following pattern:
var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
/*
- if string contains > 5 characters --> Split
- check, which is the longest array from the split
- use the longest split to be an array 2D
*/
// expected result
var new_data = new List<object[]> {
new object[] { "ABCDE", 80, "TestM", "PQRST" },
new object[] { "FGHIJ", " ", "ain", "UVWXY" },
new object[] { "KLMNO", " ", " ", " " }
}
You will have to constrain your List<object> to a List<string>, since you cannot assure a valid conversion back to the original type, once you split it.
var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
List<string> stringData = data.Select(o => o.ToString()).ToList();
const int maxCharacters = 5;
int nrOfEntries = data.Count;
List<string[]> result = new List<string[]>();
while (true)
{
bool finished = true;
string[] newRow = new string[nrOfEntries];
for (int i = 0; i < nrOfEntries; i++)
{
string currentString = stringData[i];
if (string.IsNullOrEmpty(currentString))
{
newRow[i] = " ";
continue;
}
int length = currentString.Length;
int charactersToTake = Math.Min(length, maxCharacters);
int charactersRemaining = length - charactersToTake;
newRow[i] = currentString.Substring(0, charactersToTake);
switch (charactersRemaining)
{
case 0:
stringData[i] = null;
break;
default:
stringData[i] = currentString.Substring(charactersToTake, charactersRemaining);
finished = false;
break;
}
}
result.Add(newRow);
if(finished)
break;
}
You could use List<object[]> result, but that list will only contain strings (and will only be useful as such) since there is no way you can convert back arbitrary objects, as stated before.
I would use Linq to solve the problem. (Be sure you have using System.Linq; at the top of your code file!)
First of all, we define a function to break down an object into several strings with length 5 or less or the object itself, if it is not a string.
object[] BreakDownObject(object o)
=> BreakDownObjectToEnumerable(o).ToArray();
IEnmuerable<object> BreakDownObjectToEnumerable(object o)
{
// If object is string, thant yield return every part
// with 5 characters (or less than 5, if necessary,
// for the last one)
if(o is string s)
{
for(int i = 0; i < s.Length; i += maxStringLength)
{
yield return s.Substring(i, Math.Min(s.Length - i, maxStringLength));
}
}
// object is not a string, don't break it up
else
{
yield return o;
}
}
Wie use Substring in Combination with Math.Min. If length - index is smaller than 5, than we use this instead for the substring.
If we use this function on all items of the list we get an array of arrays of object. This array could be interpreted as "columns", because the first index gives us the columns, and the second index the subsequent broken down strings.
var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
object[][] columns = data.Select(BreakDownObject).ToArray();
Now we want to transpose the array, so rows first. We write a function, that takes an index and our array of arrays and returns the row with that index. (Again I use Linq-IEnumerable for easier creation of the array):
object[] GetRowAtIndex(int index, object[][] columns)
=> GetRowAtIndexAsEnumerable(index, columns).ToArray();
IEnumerable<object> GetRowAtIndexAsEnumerable(int index, object[][] columns)
{
foreach(var column in columns)
{
// Each column has different length,
// if index is less than length, we
// return the item at that index
if(index < column.Length)
{
yield return column[index];
}
// If index is greater or equal length
// we return a string with a single space
// instead.
else
{
yield return " ";
}
}
}
This function also fills up missing items in the columns with a one-space string.
Last but not least, we iterate through the rows, until no column has items left:
List<object[]> GetAllRows(object[][] columns)
=> GetAllRowsAsEnumerable(columns);
Enumerable<object[]> GetAllRowsAsEnumerable(object[][] columns)
{
int index = 0;
while(true)
{
// Check if any column has items left
if(!columns.Any(column => index < column.Length))
{
// No column with items left, left the loop!
yield break;
}
// return the row at index
yield return GetRowAtIndex(index, columns);
// Increase index
++index;
}
}
Put it together as one function:
List<object[]> BreakDownData(List<object> data)
{
object[][] columns = data.Select(BreakDownObject).ToArray();
return GetAllRows(columns);
}
After that, your code would be:
var data = new List<object> { "ABCDEFGHIJKLMNO", 80, "TestMain", "PQRSTUVWXY" };
var new_data = BreakDownData(data);
I need someone to create a method that will get me all the cell values in an excel spreadsheet into a 2D array.
I'm making it using ribbons in C# to work with Excel but i just can't get it to work.
private string[,] GetSpreadsheetData ()
{
try
{
Excel.Application exApp =
Globals.TSExcelAddIn.Application as Excel.Application;
Excel.Worksheet ExWorksheet = exApp.ActiveSheet as Excel.Worksheet;
Excel.Range xlRange = ExWorksheet.get_Range("A1","F188000");
object[,] values = (object[,])xlRange.Value2;
string[,] tsReqs = new string[xlRange.Rows.Count, 7];
char[] alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray();
for (int i = 0; i < tsReqs.GetLength(0); i++)
{
for (int z = 0; z < tsReqs.GetLength(1); z++)
{
if(values[i+1,z+1] != null)
tsReqs[i, z] = values[i + 1, z + 1].ToString();
}
}
return tsReqs;
}
catch
{
MessageBox.Show
("Excel has encountered an error. \nSaving work and exitting");
return null;
}
}
Also if anyone has a more efficient way of doing this I would greatly appreciate it.
Excel.Range xlRange = ExWorksheet.get_Range("A1","F188000");
Reads all the way till F188000 cell from A1, I just want it to keep reading until it reaches a row with absolutely no data.
- Caught: "Index was outside the bounds of the array." (System.IndexOutOfRangeException) Exception Message = "Index was outside the bounds of the array.", Exception Type = "System.IndexOutOfRangeException"
You could consider using ExWorksheet.UsedRange instead of ExWorksheet.get_Range("A1","F188000")
EDIT: Also, I think if you use the .Text field of a Range, the value is automatically casted to a string, so no need to use .Value2 here
Excel.Range rng = ExWorksheet.UsedRange;
int rowCount = rng.Rows.Count;
int colCount = rng.Columns.Count;
string[,] tsReqs = new string[rowCount, colCount];
for (int i = 1; i <= rowCount; i++)
{
for (int j = 1; j <= colCount; j++)
{
string str = rng.Cells[i, j].Text;
tsReqs[i - 1, j - 1] = str;
}
}
Your arrays are different sizes. Your values come from A1:F188000 which is 6 columns, but you're looping through the columns of tsReqs which is 7 columns. It looks like you're trying to account for the fact that values will be a 1-based array but aren't accounting for that properly.
Change the declaration of tsReqs to:
string[,] tsReqs = new string[xlRange.Rows.Count, 6];
and you should be fine.
Try this
private static void printExcelValues(Worksheet xSheet)
{
Range xRng =xSheet.Cells.SpecialCells(
XlCellType.xlCellTypeConstants);
var arr = new string[xRng.Rows.Count,xRng.Columns.Count];
foreach (Range item in xRng)
{
arr[item.Row-1,item.Column-1]=item.Value.ToString();
}
}
You can use the | to specify more cell types like XlCellType.xlCellTypeFormulas or any other type you'd like.
I had to do something very similar. Reading an excel file takes a while... I took a different route but I got the job done with instant results.
Firstly I gave each column its own array. I saved each column from the spreadsheet as a CSV(Comma delimited) file. After opening that file with notePad I was able to copy the 942 values separated with a comma to the initialization of the array
int[] Column1 = {300, 305, 310, ..., 5000};
[0] [1] [2] ... [941]
Now... If you do this for each Column, the position will resemble the 'row' of each 'column' of your 'spreadsheet' .
This odd method worked PERFECT for me, as I needed to compare user input sizes to the values in the "Width" column in the spreadsheet to get information regarding each respective size found in the spreadsheet.
NOTE: If your cells contain strings and not integers like mine, you can use enumeration method:
enum column1 { thing_1, thing_2, ..., thing_n,}
[0] [1] [n]
Here's my code If you want to have a look:
(This is NOT the code converting a spreadsheet into some arrays --that is only a few lines of code, depending on your amount of columns-- this is the whole LookUp method I wrote)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PalisadeWorld
{
//struct to store all the positions of each matched panel size in an array 'Identities'
struct ID
{
public int[] Identities;
public ID(int[] widths, int rows)
{
//Column1 from spreadsheet
int[] allWidths = { 300, 305, 310, 315, 320, 325, 330, ..., 5000 };
int i,j;
int[] Ids = new int[rows];
for (i = 0; i < rows; i++)
{
for (j = 0; j < 941; j++)
{
if (widths[i] == allWidths[j])
{
Ids[i] = j;
break;
}
}
}
this.Identities = Ids;
}
public override string ToString()
{
string data = String.Format("{0}", this.Identities);
return data;
}
}
class LookUpSheet
{
//retrieve user input from another class
public int[] lookUp_Widths {get; set;}
public int lookUp_Rows { get; set; }
//Method returning desired values from Column2
public int[] GetNumPales1()
{
//column2
int[] all_numPales = { 2, 2, 2, 2, 2, 2, 2, 2, 2, ..."goes on till [941]"...};
int[] numPales = new int[lookUp_Rows];
ID select = new ID(lookUp_Widths, lookUp_Rows);
for (int i = 0; i < lookUp_Rows; i++)
{
numPales[i] = all_numPales[select.Identities[i]];
}
return numPales;
}
//Method returning desired values from Column3
public int[] GetBlocks1()
{
//column3
int[] all_blocks = { 56, 59, 61, 64, 66, 69, 71, 74, "goes on till [941]"...};
int[] blocks = new int[lookUp_Rows];
ID select = new ID(lookUp_Widths, lookUp_Rows);
for (int i = 0; i < lookUp_Rows; i++)
{
blocks[i] = all_blocks[select.Identities[i]];
}
return blocks;
}
...
Goes on through each column of my spreadsheet
Really hope this helps someone. Cheers
I am using C# asp.net in my project. I use 2d array in that. Named roomno. When I try to remove one row in that. So I Convert the array into list.
static string[,] roomno = new string[100, 14];
List<string>[,] lst = new List<string>[100, 14];
lst = roomno.Cast<string>[,]().ToList();
Error 1 Invalid expression term 'string' in this line...
if i try below code,
lst = roomno.Cast<string>().ToList();
I got
Error 3 Cannot implicitly convert type 'System.Collections.Generic.List<string>' to 'System.Collections.Generic.List<string>[*,*]'
lst = roomno.Cast().ToList();
What is the Mistake in my code?.
After that, I plan to remove the row in list, lst.RemoveAt(array_qty);
This:
List<string>[,] lst = new List<string>[100, 14];
is declaring a 2-D array of List<string> values.
This:
roomno.Cast<string>[,]().ToList();
... simply doesn't make sense due to the position of the [,] between the type argument and the () for the method invocation. If you changed it to:
roomno.Cast<string[,]>().ToList();
then it would be creating a List<string[,]> but it's still not the same as a List<string>[,].
Additionally, roomno is just a 2-D array of strings - which is actually a single sequence of strings as far as LINQ is concerned - so why are you trying to convert it into an essentially 3-dimensional type?
It's not clear what you're trying to do or why you're trying to do it, but hopefully this at least helps to explain why it's not working...
To be honest, I would try to avoid mixing 2-D arrays and lists within the same type. Would having another custom type help?
EDIT: LINQ isn't going to be much use with a 2-D array. It's designed for single sequences really. I suspect you'll need to do it "manually" - here's a short but complete program as an example:
using System;
class Program
{
static void Main(string[] args)
{
string[,] values = {
{"x", "y", "z"},
{"a", "b", "c"},
{"0", "1", "2"}
};
values = RemoveRow(values, 1);
for (int row = 0; row < values.GetLength(0); row++)
{
for (int column = 0; column < values.GetLength(1); column++)
{
Console.Write(values[row, column]);
}
Console.WriteLine();
}
}
private static string[,] RemoveRow(string[,] array, int row)
{
int rowCount = array.GetLength(0);
int columnCount = array.GetLength(1);
string[,] ret = new string[rowCount - 1, columnCount];
Array.Copy(array, 0, ret, 0, row * columnCount);
Array.Copy(array, (row + 1) * columnCount,
ret, row * columnCount, (rowCount - row - 1) * columnCount);
return ret;
}
}
If you need to use 2-dimensional List, try List<List<string>>(). But conversation to array in that case could be: list.Select(x => x.ToArray()).ToArray() and that is string[][] but not string[,]
This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Delete row of 2D string array in C#
i have a 2d string array, I want to delete a specified row from the array.
string[] a = new string[] { "a", "b" }; //dummy string array
int deleteIndex = 1; //we want to "delete" element in position 1 of string
a = a.ToList().Where(i => !a.ElementAt(deleteIndex).Equals(i)).ToArray();
dirty but gives the expected result (foreach through the array to test it)
EDIT missed the "2d array" detail, here is the right code for the job
string[][] a = new string[][] {
new string[] { "a", "b" } /*1st row*/,
new string[] { "c", "d" } /*2nd row*/,
new string[] { "e", "f" } /*3rd row*/
};
int rowToRemove = 1; //we want to get rid of row {"c","d"}
//a = a.ToList().Where(i => !i.Equals(a.ElementAt(rowToRemove))).ToArray(); //a now has 2 rows, 1st and 3rd only.
a = a.Where((el, i) => i != rowToRemove).ToArray(); // even better way to do it maybe
code updated
As has been said above you cant remove from an array.
If you are going to need to remove rows quite often maybe change from using a 2d array to a list containing an array of string. This way you can make use of the remove methods that list implements.
Ok so I said you can't "delete" them. That's still true. You'll have to create a new array instance with enough space for the items you want to keep and copy them over.
If this is a jagged array, using LINQ here could simplify this.
string[][] arr2d =
{
new[] { "foo" },
new[] { "bar", "baz" },
new[] { "qux" },
};
// to remove the second row (index 1)
int rowToRemove = 1;
string[][] newArr2d = arr2d
.Where((arr, index) => index != rowToRemove)
.ToArray();
// to remove multiple rows (by index)
HashSet<int> rowsToRemove = new HashSet<int> { 0, 2 };
string[][] newArr2d = arr2d
.Where((arr, index) => !rowsToRemove.Contains(index))
.ToArray();
You could use other LINQ methods to remove ranges of rows easier (e.g., Skip(), Take(), TakeWhile(), etc.).
If this is a true two-dimensional (or other multi-dimensional) array, you won't be able to use LINQ here and will have to do it by hand and it gets more involved. This still applies to the jagged array as well.
string[,] arr2d =
{
{ "foo", null },
{ "bar", "baz" },
{ "qux", null },
};
// to remove the second row (index 1)
int rowToRemove = 1;
int rowsToKeep = arr2d.GetLength(0) - 1;
string[,] newArr2d = new string[rowsToKeep, arr2d.GetLength(1)];
int currentRow = 0;
for (int i = 0; i < arr2d.GetLength(0); i++)
{
if (i != rowToRemove)
{
for (int j = 0; j < arr2d.GetLength(1); j++)
{
newArr2d[currentRow, j] = arr2d[i, j];
}
currentRow++;
}
}
// to remove multiple rows (by index)
HashSet<int> rowsToRemove = new HashSet<int> { 0, 2 };
int rowsToKeep = arr2d.GetLength(0) - rowsToRemove.Count;
string[,] newArr2d = new string[rowsToKeep, arr2d.GetLength(1)];
int currentRow = 0;
for (int i = 0; i < arr2d.GetLength(0); i++)
{
if (!rowsToRemove.Contains(i))
{
for (int j = 0; j < arr2d.GetLength(1); j++)
{
newArr2d[currentRow, j] = arr2d[i, j];
}
currentRow++;
}
}
Instead of array you can use List or ArrayList class. Using it you can dynamically add element and remove based on your requirement. Array is fixed in size, which can not be manipulated dynamically.
The best way is to work with a List<Type>! The items are ordered in the way the are added to the list and each of them can be deleted.
Like this:
var items = new List<string>;
items.Add("One");
items.Add("Two");
items.RemoveAt(1);
I have been stumped on this one for a while. I want to take a List and order the list such that the Products with the largest Price end up in the middle of the list. And I also want to do the opposite, i.e. make sure that the items with the largest price end up on the outer boundaries of the list.
Imagine a data structure like this.. 1,2,3,4,5,6,7,8,9,10
In the first scenario I need to get back 1,3,5,7,9,10,8,6,4,2
In the second scenario I need to get back 10,8,6,4,2,1,3,5,7,9
The list may have upwards of 250 items, the numbers will not be evenly distributed, and they will not be sequential, and I wanted to minimize copying. The numbers will be contained in Product objects, and not simple primitive integers.
Is there a simple solution that I am not seeing?
Any thoughts.
So for those of you wondering what I am up to, I am ordering items based on calculated font size. Here is the code that I went with...
The Implementation...
private void Reorder()
{
var tempList = new LinkedList<DisplayTag>();
bool even = true;
foreach (var tag in this) {
if (even)
tempList.AddLast(tag);
else
tempList.AddFirst(tag);
even = !even;
}
this.Clear();
this.AddRange(tempList);
}
The Test...
[TestCase(DisplayTagOrder.SmallestToLargest, Result=new[]{10,14,18,22,26,30})]
[TestCase(DisplayTagOrder.LargestToSmallest, Result=new[]{30,26,22,18,14,10})]
[TestCase(DisplayTagOrder.LargestInTheMiddle, Result = new[] { 10, 18, 26, 30, 22, 14 })]
[TestCase(DisplayTagOrder.LargestOnTheEnds, Result = new[] { 30, 22, 14, 10, 18, 26 })]
public int[] CalculateFontSize_Orders_Tags_Appropriately(DisplayTagOrder sortOrder)
{
list.CloudOrder = sortOrder;
list.CalculateFontSize();
var result = (from displayTag in list select displayTag.FontSize).ToArray();
return result;
}
The Usage...
public void CalculateFontSize()
{
GetMaximumRange();
GetMinimunRange();
CalculateDelta();
this.ForEach((displayTag) => CalculateFontSize(displayTag));
OrderByFontSize();
}
private void OrderByFontSize()
{
switch (CloudOrder) {
case DisplayTagOrder.SmallestToLargest:
this.Sort((arg1, arg2) => arg1.FontSize.CompareTo(arg2.FontSize));
break;
case DisplayTagOrder.LargestToSmallest:
this.Sort(new LargestFirstComparer());
break;
case DisplayTagOrder.LargestInTheMiddle:
this.Sort(new LargestFirstComparer());
Reorder();
break;
case DisplayTagOrder.LargestOnTheEnds:
this.Sort();
Reorder();
break;
}
}
The appropriate data structure is a LinkedList because it allows you to efficiently add to either end:
LinkedList<int> result = new LinkedList<int>();
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Array.Sort(array);
bool odd = true;
foreach (var x in array)
{
if (odd)
result.AddLast(x);
else
result.AddFirst(x);
odd = !odd;
}
foreach (int item in result)
Console.Write("{0} ", item);
No extra copying steps, no reversing steps, ... just a small overhead per node for storage.
C# Iterator version
(Very simple code to satisfy all conditions.)
One function to rule them all! Doesn't use intermediate storage collection (see yield keyword). Orders the large numbers either to the middle, or to the sides depending on the argument. It's implemented as a C# iterator
// Pass forward sorted array for large middle numbers,
// or reverse sorted array for large side numbers.
//
public static IEnumerable<long> CurveOrder(long[] nums) {
if (nums == null || nums.Length == 0)
yield break; // Nothing to do.
// Move forward every two.
for (int i = 0; i < nums.Length; i+=2)
yield return nums[i];
// Move backward every other two. Note: Length%2 makes sure we're on the correct offset.
for (int i = nums.Length-1 - nums.Length%2; i >= 0; i-=2)
yield return nums[i];
}
Example Usage
For example with array long[] nums = { 1,2,3,4,5,6,7,8,9,10,11 };
Start with forward sort order, to bump high numbers into the middle.
Array.Sort(nums); //forward sort
// Array argument will be: { 1,2,3,4,5,6,7,8,9,10,11 };
long[] arrLargeMiddle = CurveOrder(nums).ToArray();
Produces: 1 3 5 7 9 11 10 8 6 4 2
Or, Start with reverse sort order, to push high numbers to sides.
Array.Reverse(nums); //reverse sort
// Array argument will be: { 11,10,9,8,7,6,5,4,3,2,1 };
long[] arrLargeSides = CurveOrder(nums).ToArray();
Produces: 11 9 7 5 3 1 2 4 6 8 10
Significant namespaces are:
using System;
using System.Collections.Generic;
using System.Linq;
Note: The iterator leaves the decision up to the caller about whether or not to use intermediate storage. The caller might simply be issuing a foreach loop over the results instead.
Extension Method Option
Optionally change the static method header to use the this modifier public static IEnumerable<long> CurveOrder(this long[] nums) { and put it inside a static class in your namespace;
Then call the order method directly on any long[ ] array instance like so:
Array.Reverse(nums); //reverse sort
// Array argument will be: { 11,10,9,8,7,6,5,4,3,2,1 };
long[] arrLargeSides = nums.CurveOrder().ToArray();
Just some (unneeded) syntactic sugar to mix things up a bit for fun. This can be applied to any answers to your question that take an array argument.
I might go for something like this
static T[] SortFromMiddleOut<T, U>(IList<T> list, Func<T, U> orderSelector, bool largestInside) where U : IComparable<U>
{
T[] sortedArray = new T[list.Count];
bool add = false;
int index = (list.Count / 2);
int iterations = 0;
IOrderedEnumerable<T> orderedList;
if (largestInside)
orderedList = list.OrderByDescending(orderSelector);
else
orderedList = list.OrderBy(orderSelector);
foreach (T item in orderedList)
{
sortedArray[index] = item;
if (add)
index += ++iterations;
else
index -= ++iterations;
add = !add;
}
return sortedArray;
}
Sample invocations:
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] sortedArray = SortFromMiddleOut(array, i => i, false);
foreach (int item in sortedArray)
Console.Write("{0} ", item);
Console.Write("\n");
sortedArray = SortFromMiddleOut(array, i => i, true);
foreach (int item in sortedArray)
Console.Write("{0} ", item);
With it being generic, it could be a list of Foo and the order selector could be f => f.Name or whatever you want to throw at it.
The fastest (but not the clearest) solution is probably to simply calculate the new index for each element:
Array.Sort(array);
int length = array.Length;
int middle = length / 2;
int[] result2 = new int[length];
for (int i = 0; i < array.Length; i++)
{
result2[middle + (1 - 2 * (i % 2)) * ((i + 1) / 2)] = array[i];
}
Something like this?
public IEnumerable<int> SortToMiddle(IEnumerable<int> input)
{
var sorted = new List<int>(input);
sorted.Sort();
var firstHalf = new List<int>();
var secondHalf = new List<int>();
var sendToFirst = true;
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current);
}
else
{
secondHalf.Add(current);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
return firstHalf.Concat(secondHalf);
}
For your specific (general) case (assuming unique keys):
public static IEnumerable<T> SortToMiddle<T, TU>(IEnumerable<T> input, Func<T, TU> getSortKey)
{
var sorted = new List<TU>(input.Select(getSortKey));
sorted.Sort();
var firstHalf = new List<TU>();
var secondHalf = new List<TU>();
var sendToFirst = true;
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current);
}
else
{
secondHalf.Add(current);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
sorted = new List<TU>(firstHalf.Concat(secondHalf));
//This assumes the sort keys are unique - if not, the implementation
//needs to use a SortedList<TU, T>
return sorted.Select(s => input.First(t => s.Equals(getSortKey(t))));
}
And assuming non-unique keys:
public static IEnumerable<T> SortToMiddle<T, TU>(IEnumerable<T> input, Func<T, TU> getSortKey)
{
var sendToFirst = true;
var sorted = new SortedList<TU, T>(input.ToDictionary(getSortKey, t => t));
var firstHalf = new SortedList<TU, T>();
var secondHalf = new SortedList<TU, T>();
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current.Key, current.Value);
}
else
{
secondHalf.Add(current.Key, current.Value);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
return(firstHalf.Concat(secondHalf)).Select(kvp => kvp.Value);
}
Simplest solution - order the list descending, create two new lists, into the first place every odd-indexed item, into the other every even indexed item. Reverse the first list then append the second to the first.
Okay, I'm not going to question your sanity here since I'm sure you wouldn't be asking the question if there weren't a good reason :-)
Here's how I'd approach it. Create a sorted list, then simply create another list by processing the keys in order, alternately inserting before and appending, something like:
sortedlist = list.sort (descending)
biginmiddle = new list()
state = append
foreach item in sortedlist:
if state == append:
biginmiddle.append (item)
state = prepend
else:
biginmiddle.insert (0, item)
state = append
This will give you a list where the big items are in the middle. Other items will fan out from the middle (in alternating directions) as needed:
1, 3, 5, 7, 9, 10, 8, 6, 4, 2
To get a list where the larger elements are at the ends, just replace the initial sort with an ascending one.
The sorted and final lists can just be pointers to the actual items (since you state they're not simple integers) - this will minimise both extra storage requirements and copying.
Maybe its not the best solution, but here's a nifty way...
Let Product[] parr be your array.
Disclaimer It's java, my C# is rusty.
Untested code, but you get the idea.
int plen = parr.length
int [] indices = new int[plen];
for(int i = 0; i < (plen/2); i ++)
indices[i] = 2*i + 1; // Line1
for(int i = (plen/2); i < plen; i++)
indices[i] = 2*(plen-i); // Line2
for(int i = 0; i < plen; i++)
{
if(i != indices[i])
swap(parr[i], parr[indices[i]]);
}
The second case, Something like this?
int plen = parr.length
int [] indices = new int[plen];
for(int i = 0; i <= (plen/2); i ++)
indices[i] = (plen^1) - 2*i;
for(int i = 0; i < (plen/2); i++)
indices[i+(plen/2)+1] = 2*i + 1;
for(int i = 0; i < plen; i++)
{
if(i != indices[i])
swap(parr[i], parr[indices[i]]);
}