LINQ non-linear order by string length - c#

I'm trying to get a list of string ordered such that the longest are on either end of the list and the shortest are in the middle. For example:
A
BB
CCC
DDDD
EEEEE
FFFFFF
would get sorted as:
FFFFFF
DDDD
BB
A
CCC
EEEEE
EDIT: To clarify, I was specifically looking for a LINQ implementation to achieve the desired results because I wasn't sure how/if it was possible to do using LINQ.

You could create two ordered groups, then order the first group descending(already done) and the second group ascending:
var strings = new List<string> {
"A",
"BB",
"CCC",
"DDDD",
"EEEEE",
"FFFFFF"};
var two = strings.OrderByDescending(str => str.Length)
.Select((str, index) => new { str, index })
.GroupBy(x => x.index % 2)
.ToList(); // two groups, ToList to prevent double execution in following query
List<string> ordered = two.First()
.Concat(two.Last().OrderBy(x => x.str.Length))
.Select(x => x.str)
.ToList();
Result:
[0] "FFFFFF" string
[1] "DDDD" string
[2] "BB" string
[3] "A" string
[4] "CCC" string
[5] "EEEEE" string

Don't ask how and why... ^^
list.Sort(); // In case the list is not already sorted.
var length = list.Count;
var result = Enumerable.Range(0, length)
.Select(i => length - 1 - 2 * i)
.Select(i => list[Math.Abs(i - (i >> 31))])
.ToList();
Okay, before I forget how it works, here you go.
A list with 6 items for example has to be reordered to this; the longest string is at index 5, the shortest one at index 0 of the presorted list.
5 3 1 0 2 4
We start with Enumerable.Range(0, length) yielding
0 1 2 3 4 5
then we apply i => length - 1 - 2 * i yielding
5 3 1 -1 -3 -5
and we have the non-negative part correct. Now note that i >> 31 is an arithmetic left shift and will copy the sign bit into all bits. Therefore non-negative numbers yield 0 while negative numbers yield -1. That in turn means subtracting i >> 31 will not change non-negative numbers but add 1 to negative numbers yielding
5 3 1 0 -2 -4
and now we finally apply Math.Abs() and get
5 3 1 0 2 4
which is the desired result. It works similarly for lists of odd length.

Just another option, which I find more readable and easy to follow:
You have an ordered list:
var strings = new List<string> {
"A",
"BB",
"CCC",
"DDDD",
"EEEEE",
"FFFFFF"};
Create a new list and simply alternate where you add items::
var new_list = new List<string>(); // This will hold your results
bool start = true; // Insert at head or tail
foreach (var s in strings)
{
if (start)
new_list.Insert(0,s);
else
new_list.Add(s);
start = !start; // Flip the insert location
}
Sweet and simple :)
As for Daniel Bruckner comment, if you care about which strings comes first, you could also change the start condition to:
// This will make sure the longest strings is first
bool start= strings.Count()%2 == 1;

Related

sorting number based on the similar starting digits

I have a var allList which contains a list of all the accounts in my tables , I want to order the account ids based on the parent number which the first three digits or four digits, if string have similar starting digits can be next to that,
I have the following data as the output per now
101
202
303
404
10111
10122
20211
20222
303211
101112
101222
10111221
42215
10111223
3035422525
1011122121
I want the output to be like this
101
10111
10122
101112
101222
10111221
10111223
1011122121
202
20211
2022222
2023221
303
303211
3035422525
404
42215
I have tried this code, the accountid is string,can we use thenby or trimstrat
allList = allList.OrderBy(x => x.accountId);
It seems that you want something like this:
allList = allList
.OrderBy(item => item.Length <= 3 ? item : item.Substring(0, 3))
.ThenBy(item => item.Length)
.ThenBy(item => item)
.ToList();
Here we sort
By first 3 digits ("first three digits or four digits"): 101 < 404
On tie, by Length: 10122 < 101112
On tie, by items lexicographically: 10111221 < 10111223

Shuffle List of strings, but not too much

I have a list of strings in C# such as:
List<string> myList;
Lets say I populate it by adding 20 strings, starting from "1", up to "20".
myList.Add("1"); // And so on...
How can I, in the most efficient and elegant way, randomly shuffle this list of strings while restricting how far each item of the list can end up from it's original index to, say, 4 .
Example of what I want:
I want the order:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
For example, to be shuffled to the following:
2 5 4 3 6 1 8 7 10 9 12 11 15 14 13 16 18 15 20 19
One (straightforward) way of doing it would be to split the list into four parts and shuffle the parts separately. But I am still not sure how efficient this would be.
Efficiency in my case means not doing something that is overcomplicated or just stupid.
The following Linq will create a new index where the new index is limited to distance places away from original index
List<string> list = Enumerable.Range(1, 20).Select(i => i.ToString()).ToList();
Random rng = new Random();
int distance = 4;
List<string> newList = list
.Select((s, i) =>
new {OrigIndex = i, NewIndex = i + rng.Next(-distance, distance+1), Val = s})
.OrderBy(a => a.NewIndex).ThenBy(a=>a.OrigIndex)
.Select(a => a.Val)
.ToList();
You can use the following code:
List<int> myList = Enumerable.Range(1, 20).ToList(); // Feed the list from 1 to 20
int numberByGroups = 4;
List<int> result = new List<int>();
for (int skip = 0; skip < myList.Count; skip = skip + numberByGroups)
{
result.AddRange(myList.Skip(skip) // skip the already used numbers
.Take(numberByGroups) // create a group
.OrderBy(a => Guid.NewGuid()) // "Shuffle"
.ToList());
}
Console.WriteLine(String.Join(", ", result));
Which will shuffle by groups of numberByGroups

How to get the deltas of a list collection

I have a list.
1 2 3 4 5 6 7
I wish to return a list of differences (deltas) between consecutive element.
1 1 1 1 1 1
How can I do this?
I am sure there must be a simple "collections" way of doing this - but I cannot find it.
You can use Enumerable.Skip and the overload of Enumerable.Select which projects the index:
List<int> deltaList = list.Skip(1) // skip first, irrelevant
.Select((num, index) => num - list[index]) // index 0 is second number in list
.ToList();
The trick is that Skip(1) does not only skip the first number (which is desired) but also changes the indices in Select. The first number's index will be 0 but it'll refer to the second number in the list (due to Skip(1)). Therefore num - list[index] subtracts the current with the previous number.
var result = list.Zip(list.Skip(1), (x, y) => y - x);

Enumerable.Repeat with Union and Take does not return 'Take' values

Very simple problem. I have a list of values which I want to pad out with empty values such that I always have X number of items returned.
List<int> list = new List<int>() { 10, 20, 30 };
IEnumerable<int> values = list
.OrderByDescending( i => i )
.Union( Enumerable.Repeat( 0 , 5 ) );
foreach (var item in values.Take(5))
Console.Write( item + " ");
I would expect an output like "30 20 10 0 0 " But surprisingly I only get "30 20 10 0".
foreach (var i in Enumerable.Repeat( 0, 5 ).Take(3))
Console.Write( i + " " );
This code will return "0 0 0 ". Likewise "list.Take(3)" returns "30 20 10 ".
The reason is that Enumerable.Union removes duplicates. So you may want to use Enumerable.Concat instead which dsoesn't remove duplicates.
Another option i would prefer is to use Enumerable.ElementAtOrdefault to select the elements from an index created by Enumerable.Range where the second argument is the desired count:
IEnumerable<int> values = Enumerable.Range(0, 5)
.Select(i => list.ElementAtOrDefault(i))
.OrderByDescending(i => i);
The notion of union, as a set-theory operation, has an implicit "distinct". If you don't want that: use Concat instead of Union.

How can I ignore / skip 'n' elements while sorting string in a lexicographical order

I have the following c# code that sorts a string in a lexicographical (alphabetical) order.
string str = "ACGGACGAACT";
IEnumerable<string> sortedSubstrings =
Enumerable.Range(0, str.Length)
.Select(i => str.Substring(i))
.OrderBy(s => s);
Result:
0 AACT
1 ACGAACT
2 ACGGACGAACT
3 ACT
4 CGAACT
5 CGGACGAACT
6 CT
7 GAACT
8 GACGAACT
9 GACGAACT
10 T
However I want to enhance this sort by skipping the 3rd and the 4th character during the lexicographical sort process
In this case the lexicographical sort will be different to the one above.
result:
0 AA[CT
1 AC[T
2 AC[GG]ACGAACT
3 AC[GA]ACT
4 CG[GA]CGAACT
5 CG[AA]CT
6 CT
7 GA[CG]AACT
8 GA[AC]T
9 GG[AC]GAACT
10 T
how can I achieve this?
This can be done by tweaking the lambda passed to OrderBy. Something like this should do it:
var sortedSubstrings =
Enumerable.Range(0, str.Length)
.Select(i => str.Substring(i))
.OrderBy(s => s.Length < 3 ? s : s.Remove(2, Math.Min(s.Length - 2, 2)));
Edit: Corrected off-by-one error.
You can change the lambda passed to OrderBy to one which will remove the 3rd and 4th symbols from the string.
var sorted = source.OrderBy(s => new string(s.Where((ch, n) => n != 2 && n != 3).ToArray()));

Categories