Mangrio
Mangrio

Reputation: 1020

How to rank the list of random value without changing their index in C#

Problem Statement: I need to generate a numeric values list randomly, and then rank the each value on the basis of their value;

example:

0.945787, 0.352410, 0.2547545, 0.821542, 0.914242, 0.125412, 0.009124, 0.6521001

now the sorted list will look like this,

0.945787, 0.914242, 0.821542, 0.6521001, 0.352410, 0.2547545, 0.125412, 0.009124

When list is generated list order should remain same but every element/value have its rank depending on value (max value has max rank)with it as below mentioned class structured.

What I did:

I have generated random list of numeric values between 0 and 1.

   public class RandomList
    {
        public double RandomValue { get; set; }
        public int Rank { get; set; }
    }

here I am generating the values;

List<RandomList> randomList = new List<RandomList>();
for (int i = 0; i < 5; i++)
   {
     RandomList random = new RandomList();
     Random rand = new Random();
     random.RandomValue = rand.NextDouble();
     randomList.Add(random);
   }

Then I copied the same list another list and sorted that list with descending order.

List<RandomList> randomListCopy = randomList;
randomListCopy = randomListCopy.OrderByDescending(v => v.RandomValue).ToList();

Now I am thinking to loop through the above list and each time match value from sorted list and check if the values match then get the index sorted list and add as the rank against that value. But this does not looks to perfect or ideal solution to this.

Upvotes: 0

Views: 221

Answers (3)

Harald Coppoolse
Harald Coppoolse

Reputation: 30464

Your class RandomList does not represent a List at all. The identifier is confusing.

class RankedValue
{
    public double Value { get; set; }
    public int Rank { get; set; }
}

So you have a sequence of doubles, and you want to convert them to a sequence of RankedValues, such that RankedValue with Rank 0 has the largest Value, element with Rank 1 has the largest but one Value, etc.

For example:

input: 4, 9, 2, 5, 8

result:

Rank Value
 3     4
 0     9
 4     2
 2     5
 1     8

So 9 is the largest, it has Rank 0; 8 is the largest but one, therefore it has rank 1. Next comes 5 with rank 2, etc

You didn't specify what you wanted if you have two equal Values. Let's assume that you don't care which of them comes first.

// convert the source to a sequence where every element has the OriginalIndex
// and the double value
var result = source.Select( (value, index) => new
{
    OriginalIndex = index,
    Value = value,
})

So the original sequence is now

OriginalIndex Value
    0           4
    1           9
    2           2
    3           5
    4           8

Now sort the valuesWithOriginalIndex by descending value:

.OrderbyDescending(valueWithOriginalIndex => valueWithOriginalIndex.Value);

Result:

OriginalIndex Value
    1           9
    4           8
    3           5
    0           4
    2           2

Now add a sorted Index:

.Select( (valueWithOriginalIndex, sortedIndex) => new
{
    Value = valueWithOriginalIndex.Value,
    OriginalIndex = valueWithOriginalIndex.Index,
    SortedIndex = sortedIndex
})

Well you know the drill by know:

SortedIndex OriginalIndex Value
    0           1           9
    1           4           8
    2           3           5
    3           0           4
    4           2           2

Sort by OriginalIndex:

.OrderBy(item => item.OridingalIndex)

SortedIndex OriginalIndex Value
    3           0           4
    0           1           9
    4           2           2
    2           3           5
    1           4           8

Almost there: only put the items in a RankedValue:

.Select(item => new RankedValue
{
    Value = item.Value,
    Rank = item.SortedIndex,
});

Final result:

Rank Value
 3     4
 0     9
 4     2
 2     5
 1     8

Put everything in one extension method. See extension methods demystified

IEnumerable<RankedValue> ToRankedValues(this IEnumerable<double> source)
{
    return source.Select( (value, index) => new
    {
        OriginalIndex = index,
        Value = value,
    })
    .OrderbyDescending(valueWithOriginalIndex => valueWithOriginalIndex.Value);
    .Select( (valueWithOriginalIndex, sortedIndex) => new
    {
        Value = valueWithOriginalIndex.Value,
        OriginalIndex = valueWithOriginalIndex.Index,
        SortedIndex = sortedIndex
    })
    .OrderBy(item => item.OridingalIndex)
    .Select(item => new RankedValue
    {
        Value = item.Value,
        Rank = item.SortedIndex,
    });
}

Note: if you don't care about the order of the RankedValues in your end result, you only want to fill in the Rank, then you don't have to do the second sort.

Usage:

List<double> myOriginalListOfDoubles = ...
IEnumerable<RankedValue> rankedValues = myOriginalListOfDoubles.ToRankedValues();

The nice thing is that you have extended IEnumerable, so you could intertwine it with other LINQ methods.

Using your original List of RandomList items:

List<RandomList> randomList = ...
var rankedValues = randomList
    .Select(randomList => randomList.RandomValue)
    .Where(randomValue => 0 < randomValue && randomValue < 1)
    .ToRankedValues()
    .Take(5)
    .ToList();

Upvotes: 1

Johnathan Barclay
Johnathan Barclay

Reputation: 20363

First of all, you haven't copied randomList; randomListCopy references the exact same list as randomList, so randomListCopy.OrderByDecending will update both varibles.

List<T> has a copy constructor:

List<RandomList> randomListCopy = new List<RandomList>(randomList);

Or you can use LINQ:

List<RandomList> randomListCopy = randomList.ToList();

Second, it will increase complexity if you make each RandomList object responsible for maintaining its own "rank"; it's current position within a list should identify it's rank.

Keep it simple:

var randomList = new List<double>();
for (int i = 0; i < 5; i++)
{
     randomList.Add(rand.NextDouble());
}

var ranked = random.OrderBy(x => x); // or random.OrderByDescending(x => x);

// Check the rank of the first item
int firstRank = ranked.IndexOf(randomList[0]);

Upvotes: 1

Captain Kenpachi
Captain Kenpachi

Reputation: 7215

You really have two options here

  1. make a copy of your list, sort it, then update the rank of each item based on the index of the item in the sorted list.
  2. use only the one list, but for each value in the list, compute the rank based on how many values are smaller than the current value.

foreach (var item in randomlist) { item.rank = randomlist.Count(x => x.val < item.val);//0-based rank. }

Upvotes: 1

Related Questions