简体   繁体   中英

Order by sum of digits of number

Example:

a = "56 65 74 100 99 68 86 180 90", ordered by numbers weights becomes: "100 180 90 56 65 74 68 86 99"

When two numbers have the same "weight", let us class them as if they were strings and not numbers: 100 is before 180 because its "weight" (1) is less than the one of 180 (9) and 180 is before 90 since, having the same "weight" (9) it comes before as a string.

All numbers in the list are positive numbers and the list can be empty.

My tests:

[TestMethod]
public void Test1()
{
     Assert.AreEqual("2000 103 123 4444 99",
         WeightSort.orderWeight("103 123 4444 99 2000"));
}

[TestMethod]
public void Test2()
{
    Assert.AreEqual("11 11 2000 10003 22 123 1234000 44444444 9999",
        WeightSort.orderWeight("2000 10003 1234000 44444444 9999 11 11 22 123"));
}

My class to calculate the order of the weights:

public class WeightSort
{
    public static string orderWeight(string strng)
    {
        List<int> list = strng.Split(' ').Select(Int32.Parse).OrderBy(i => i).ToList();
        List<int> SumofNums = new List<int>();
        List<string> SumandNums = new List<string>();
        List<string> SumandNums2 = new List<string>();
        List<string> Nums = new List<string>();

        foreach (var itm in list)
        {
            int num = (int)GetSumOfDigits(itm);
            SumofNums.Add(num);
            SumandNums.Add(itm + "," + num);
        }
        SumofNums = SumofNums.OrderBy(i => i).ToList();
        string txt = "";            
        foreach (var itm in SumofNums)
        {
            var item = itm.ToString();
            if (!Nums.Contains(item))
            {
                foreach (var itm2 in SumandNums)
                {
                    var itm3 = itm2.Split(',');
                    if (item == itm3[1])
                    {
                        SumandNums2.Add(itm2);
                        if (string.IsNullOrEmpty(txt))
                            txt = itm3[0];
                        else
                            txt = txt + " " + itm3[0];
                    }
                }
                Nums.Add(item);
            }            
        }

        return txt;
    }

    static long GetSumOfDigits(long n)
    {
        long num2 = 0;
        long num3 = n;
        long r = 0;
        while (num3 != 0)
        {
            r = num3 % 10;
            num3 = num3 / 10;
            num2 = num2 + r;
        }

        return num2;
    }
}

I can handle if there is only one but not duplicates. Please help me rewrite my class so it can handle the duplicates also..

Sum of digits:

string weights = "103 123 4444 99 2000";

1) 2000, digit sum = 2;
2) 103, digit sum = 4;
3) 123, digit sum = 6;
4) 4444, digit sum = 16;
5) 99, digit sum = 18;

the correct order is "2000 103 123 4444 99"

You can use Linq if sorting by weight means

  • by sum of digits
  • lexicographically ("as strings")

the implementation

  String a = "56 65 74 100 99 68 86 180 90";

  // 100 180 90 56 65 74 68 86 99
  String result = String.Join(" ", a
    .Split(' ')
    .OrderBy(item => item.Sum(ch => ch - '0')) // sum of digits
    .ThenBy(item => item));                    // lexicographic ("as string")

Try this:

var input = "103 123 4444 99 2000";
var sorted = input.Split(' ').OrderBy(s => s.Sum(c => c - '0')).ThenBy(s => s);
var result = string.Join(" ", sorted);

Addition: I realize now that Dmitry's answer had evolved into the same as mine before I posted mine.

New addition: If you find that s.Sum(c => c - '0') is like a hack, you can be using System.Globalization; and say s.Sum((Func<char, int>)CharUnicodeInfo.GetDecimalDigitValue) instead.


You can validate in the lambda. For example:

var sorted = input.Split(' ')
  .OrderBy(s => s.Sum(c => { if (c < '0' || c > '9') { throw new ArgumentOutOfRangeException("c", "Unexpected character."); } return c - '0'; }))
  .ThenBy(s => s);

You can also do this by creating a comparer which tells you whether a value is greater than or less than another value and can then be used. The code largely speaks for itself:

void Main()
{
    var strings = new List<string>("2000 10003 1234000 44444444 9999 11 11 22 123".Split(' '));
    strings.Sort(new MyComparer());
    Console.WriteLine(String.Join(" ", strings));
}

public class MyComparer : IComparer<string>
{
    public int Compare(string a, string b)
    {
        var aWeight = GetWeight(a);
        var bWeight = GetWeight(b);
        if (aWeight==bWeight)
        {
            return String.Compare(a,b);
        }
        else
        {
            return aWeight < bWeight ? -1 : 1;
        }

    }

    private int GetWeight(string number)
    {
        var weight = 0;
        foreach(var digit in number)
        {
            weight+=Int32.Parse(digit.ToString());
        }
        return weight;
    }
}

The key thing is the MyComparer class which defines a single public method that takes two values in. It gets the weights of the objects and if they are the same it falls back to string comparison.

This comparer can then be passed to a sort function such as that of List<T> to then do the sorting.

This is much lengthier but I thought it worth sharing as it is a little more reusable (eg if you do this in a lot of places in your code you can have your logic in a single class) and it can sometimes be a bit more readable.

I also note I am not a fan of ch - '0' as a way of getting the int value of a character since it is not always obvious at a glance what it does if you don't know the trick. Also in the event of non numeric characters it will still do things, just not necessarily anything sensible. Mine will just throw a good old fashioned exception that can be caught if you pass it any non-numeric data.

I found this solution pretty short and clear:

var orderedNumbers = "56 65 74 100 99 68 86 180 90".Split(' ')
    .OrderBy(GetWeight)
    .ThenBy(x => x);
var result = String.Join(" ", orderedNumbers);

This will first calculate the weight from any given number and sort by this value. If it´s equal the ThenBy -clause comes to the play and furtherly orders the result by performing a string-comparison (as the values returned by the first OrderBy is a list of strings).

With

int GetWeight(string number)
{
    return number.Sum(x => CharUnicodeInfo.GetDecimalDigitValue(x));
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM