Czy istnieje prosty sposób na tworzenie porządków w C #?

202

Czy istnieje prosty sposób w C # na tworzenie porządków dla liczby? Na przykład:

  • 1 zwraca 1. pozycję
  • 2 zwraca 2. miejsce
  • 3 zwraca 3. miejsce
  • ...itp

Czy można to zrobić za pomocą String.Format()czy dostępne są jakieś funkcje?

GateKiller
źródło

Odpowiedzi:

310

Ta strona zawiera pełną listę wszystkich niestandardowych reguł formatowania numerycznego:

http://msdn.microsoft.com/en-us/library/0c899ak8.aspx

Jak widać, w porządkach nie ma nic, więc nie można tego zrobić za pomocą String.Format. Jednak nie jest tak trudno napisać funkcję, aby to zrobić.

public static string AddOrdinal(int num)
{
    if( num <= 0 ) return num.ToString();

    switch(num % 100)
    {
        case 11:
        case 12:
        case 13:
            return num + "th";
    }

    switch(num % 10)
    {
        case 1:
            return num + "st";
        case 2:
            return num + "nd";
        case 3:
            return num + "rd";
        default:
            return num + "th";
    }
}

Aktualizacja: Techniczne porządki nie istnieją dla <= 0, więc zaktualizowałem powyższy kod. Usunięto także zbędne ToString()metody.

Należy również pamiętać, że nie jest to internacjonalizowane. Nie mam pojęcia, jak wyglądają porządki w innych językach.

samjudson
źródło
2
Assert.AreEqual („0”, AddOrdinal (0)); Zobacz wisegeek.com/what-is-an-ordinal-number.htm
si618
2
Zastosowanie metody rozszerzenia (lub jakkolwiek to się nazywa - patrz odpowiedź @ Stu) byłoby tutaj świetne. @ Si, dodanie tego warunku byłoby bardzo łatwe, gdyby było wymagane.
strager
12
Zapomniałem o „11, 12 13.”… powinno być pytanie do wywiadu. :-)
Holf
2
Tak, cóż, programiści są dziwni;)
samjudson
2
@IanWarburton Nie ma redundancji, ponieważ trafiony zostanie tylko jeden wyciąg zwrotny. Jeśli nie jesteś zadowolony z odpowiedzi, podaj swoją, wskazując nam „właściwy” sposób na zrobienie tego i dlaczego ma to znaczenie.
B2K
73

Pamiętaj o internacjonalizacji!

Rozwiązania tutaj działają tylko w języku angielskim. Sprawy stają się znacznie bardziej złożone, jeśli potrzebujesz obsługi innych języków.

Na przykład w języku hiszpańskim „1.” byłoby napisane jako „1.o”, „1.a”, „1.os” lub „1.as” w zależności od tego, czy liczysz coś męskiego, żeńskiego czy mnogiego !

Więc jeśli twoje oprogramowanie musi obsługiwać różne języki, staraj się unikać porządków.

roomaroo
źródło
7
@ Andomar: „Pierwszych 2 czytelników” => w języku włoskim (i hiszpańskim, jak sądzę) „pierwsze” jest tutaj w liczbie mnogiej. Masz więc liczbę pojedynczą rodzaju męskiego, liczbę pojedynczą rodzaju żeńskiego, liczbę mnogą rodzaju męskiego, liczbę mnogą rodzaju żeńskiego; może jakiś język ma także neutralny przypadek (odróżniający rzeczy od ludzi / zwierząt)
M.Turrini
2
To powiedziawszy, nie musisz unikać porządków porządkowych: dołącz je do lokalizacji, gdy poznasz całą sprawę, z którą możesz się zmierzyć, lub (spraw, aby twój klient) zaakceptował pewne ograniczenia.
M.Turrini
26
To wyjaśnia, dlaczego zespół .NET starał się nie dodawać go do formatów DateTime
Chris S
moment.js ma funkcję „porządkową” formatowania według ustawień regionalnych, więc wydaje się to wykonalne, szkoda też, że nie zrobiliby tego w .NET dla DateTime
Guillaume86,
5
Wszystko byłoby bardzo proste, gdybyście wszyscy użyli „.” znak dla rzędnych, tak jak robimy to w języku niemieckim)))) 1. 2. 3. 4. 5., itd. Chociaż algorytm byłby o wiele bardziej interesujący, gdyby zapisał liczbę i musiałby dołączyć fleksję w 4 przypadki gramatyczne z 3 różnymi artykułami, oprócz przypadków w liczbie pojedynczej i mnogiej z 12 różnych kombinacji. Pomyśl o tym, czy Rosjanie nie mają jeszcze 2 plus vocativ, a niektóre języki nordyckie mają 15, tak myślę. Bardzo chciałbym zobaczyć tę implementację w .NET.
Stefan Steiger
22

Moja wersja wersji Jesse'a Stu i wersji Samjudson :)

Uwzględniono test jednostkowy, aby wykazać, że zaakceptowana odpowiedź jest niepoprawna, gdy liczba <1

    /// <summary>
    /// Get the ordinal value of positive integers.
    /// </summary>
    /// <remarks>
    /// Only works for english-based cultures.
    /// Code from: http://stackoverflow.com/questions/20156/is-there-a-quick-way-to-create-ordinals-in-c/31066#31066
    /// With help: http://www.wisegeek.com/what-is-an-ordinal-number.htm
    /// </remarks>
    /// <param name="number">The number.</param>
    /// <returns>Ordinal value of positive integers, or <see cref="int.ToString"/> if less than 1.</returns>
    public static string Ordinal(this int number)
    {
        const string TH = "th";
        string s = number.ToString();

        // Negative and zero have no ordinal representation
        if (number < 1)
        {
            return s;
        }

        number %= 100;
        if ((number >= 11) && (number <= 13))
        {
            return s + TH;
        }

        switch (number % 10)
        {
            case 1: return s + "st";
            case 2: return s + "nd";
            case 3: return s + "rd";
            default: return s + TH;
        }
    }

    [Test]
    public void Ordinal_ReturnsExpectedResults()
    {
        Assert.AreEqual("-1", (1-2).Ordinal());
        Assert.AreEqual("0", 0.Ordinal());
        Assert.AreEqual("1st", 1.Ordinal());
        Assert.AreEqual("2nd", 2.Ordinal());
        Assert.AreEqual("3rd", 3.Ordinal());
        Assert.AreEqual("4th", 4.Ordinal());
        Assert.AreEqual("5th", 5.Ordinal());
        Assert.AreEqual("6th", 6.Ordinal());
        Assert.AreEqual("7th", 7.Ordinal());
        Assert.AreEqual("8th", 8.Ordinal());
        Assert.AreEqual("9th", 9.Ordinal());
        Assert.AreEqual("10th", 10.Ordinal());
        Assert.AreEqual("11th", 11.Ordinal());
        Assert.AreEqual("12th", 12.Ordinal());
        Assert.AreEqual("13th", 13.Ordinal());
        Assert.AreEqual("14th", 14.Ordinal());
        Assert.AreEqual("20th", 20.Ordinal());
        Assert.AreEqual("21st", 21.Ordinal());
        Assert.AreEqual("22nd", 22.Ordinal());
        Assert.AreEqual("23rd", 23.Ordinal());
        Assert.AreEqual("24th", 24.Ordinal());
        Assert.AreEqual("100th", 100.Ordinal());
        Assert.AreEqual("101st", 101.Ordinal());
        Assert.AreEqual("102nd", 102.Ordinal());
        Assert.AreEqual("103rd", 103.Ordinal());
        Assert.AreEqual("104th", 104.Ordinal());
        Assert.AreEqual("110th", 110.Ordinal());
        Assert.AreEqual("111th", 111.Ordinal());
        Assert.AreEqual("112th", 112.Ordinal());
        Assert.AreEqual("113th", 113.Ordinal());
        Assert.AreEqual("114th", 114.Ordinal());
        Assert.AreEqual("120th", 120.Ordinal());
        Assert.AreEqual("121st", 121.Ordinal());
        Assert.AreEqual("122nd", 122.Ordinal());
        Assert.AreEqual("123rd", 123.Ordinal());
        Assert.AreEqual("124th", 124.Ordinal());
    }
si618
źródło
15

Prosto, czysto, szybko

    private static string GetOrdinalSuffix(int num)
    {
        if (num.ToString().EndsWith("11")) return "th";
        if (num.ToString().EndsWith("12")) return "th";
        if (num.ToString().EndsWith("13")) return "th";
        if (num.ToString().EndsWith("1")) return "st";
        if (num.ToString().EndsWith("2")) return "nd";
        if (num.ToString().EndsWith("3")) return "rd";
        return "th";
    }

Lub jeszcze lepiej, jako metoda rozszerzenia

public static class IntegerExtensions
{
    public static string DisplayWithSuffix(this int num)
    {
        if (num.ToString().EndsWith("11")) return num.ToString() + "th";
        if (num.ToString().EndsWith("12")) return num.ToString() + "th";
        if (num.ToString().EndsWith("13")) return num.ToString() + "th";
        if (num.ToString().EndsWith("1")) return num.ToString() + "st";
        if (num.ToString().EndsWith("2")) return num.ToString() + "nd";
        if (num.ToString().EndsWith("3")) return num.ToString() + "rd";
        return num.ToString() + "th";
    }
}

Teraz możesz po prostu zadzwonić

int a = 1;
a.DisplayWithSuffix(); 

lub nawet tak bezpośredni jak

1.DisplayWithSuffix();
Shahzad Qureshi
źródło
14

Musisz rzucić własne. Z czubka głowy:

public static string Ordinal(this int number)
{
  var work = number.ToString();
  if ((number % 100) == 11 || (number % 100) == 12 || (number % 100) == 13)
    return work + "th";
  switch (number % 10)
  {
    case 1: work += "st"; break;
    case 2: work += "nd"; break;
    case 3: work += "rd"; break;
    default: work += "th"; break;
  }
  return work;
}

Możesz wtedy zrobić

Console.WriteLine(432.Ordinal());

Edytowano dla wyjątków 11.12.2013. Powiedziałem z góry :-)

Edycja dla 1011 - inni już to naprawili, po prostu upewnij się, że inni nie pobiorą tej niepoprawnej wersji.

Stu
źródło
12

Raczej podobały mi się elementy z rozwiązań Stu i samjudson i połączyłem je w coś, co moim zdaniem jest użyteczną kombinacją:

    public static string Ordinal(this int number)
    {
        const string TH = "th";
        var s = number.ToString();

        number %= 100;

        if ((number >= 11) && (number <= 13))
        {
            return s + TH;
        }

        switch (number % 10)
        {
            case 1:
                return s + "st";
            case 2:
                return s + "nd";
            case 3:
                return s + "rd";
            default:
                return s + TH;
        }
    }
Jesse C. Slicer
źródło
1
jakie jest uzasadnienie użycia stałej dla „th”?
nickf
ponieważ jest używany dwukrotnie w kodzie. Po prostu wykorzystując odwieczną mądrość, której nie powinieneś powtarzać :) W tym przypadku środowisko uruchomieniowe .NET powinno utworzyć tylko jedną kopię ciągu, a przy dwóch „th” w kodzie powstałyby dwa ciągi i przywoływane w pamięci.
Jesse C. Slicer
25
a także, jeśli wartość TH kiedykolwiek się zmieni, zostaniesz ustawiony.
Zaćmienie
7
@Jesse - Dostajesz moje +1, ale nie wierzę, że .NET obsługuje ciągi w ten sposób, patrz yoda.arachsys.com/csharp/strings.html#interning , moje czytanie to każde odniesienie do literału „th” odwoływałby się do tego samego bitu pamięci. Ale zgadzam się na DRY :)
si618
4
Wydaje mi się, że usunięcie tego typu duplikatów tylko utrudnia czytelność, stąd zamieszanie „Dlaczego TH?”. Nie sądzę, aby DRY należy interpretować jako „usuń wszelkie powielanie niezależnie od kosztów”.
SeeNoWeevil,
8

Chociaż nie przeprowadziłem jeszcze testu porównawczego, powinieneś być w stanie uzyskać lepszą wydajność, unikając wszystkich instrukcji warunkowych.

To jest Java, ale port do C # jest trywialny:

public class NumberUtil {
  final static String[] ORDINAL_SUFFIXES = {
    "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
  };

  public static String ordinalSuffix(int value) {
    int n = Math.abs(value);
    int lastTwoDigits = n % 100;
    int lastDigit = n % 10;
    int index = (lastTwoDigits >= 11 && lastTwoDigits <= 13) ? 0 : lastDigit;
    return ORDINAL_SUFFIXES[index];
  }

  public static String toOrdinal(int n) {
    return new StringBuffer().append(n).append(ordinalSuffix(n)).toString();
  }
}

Zwróć uwagę, że ograniczenie warunkowe i użycie wyszukiwania tablicowego powinno przyspieszyć wydajność, jeśli generujesz wiele rzędnych w ciasnej pętli. Przyznaję jednak, że nie jest to tak czytelne jak rozwiązanie instrukcji case.

Ryan McGeary
źródło
Przepraszam, że przetestowałem to w C #, twoja wersja nie jest szybsza niż rozwiązanie si618.
GY_,
sprawdź tę odpowiedź stackoverflow.com/a/58378465/2583579, aby zapoznać się z niektórymi
testami
3

Podobne do rozwiązania Ryana, ale nawet bardziej podstawowe, po prostu używam prostej tablicy i używam dnia, aby wyszukać poprawną liczbę porządkową:

private string[] ordinals = new string[] {"","st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th","st" };
DateTime D = DateTime.Now;
String date = "Today's day is: "+ D.Day.ToString() + ordinals[D.Day];

Nie miałem takiej potrzeby, ale zakładam, że możesz użyć wielowymiarowej tablicy, jeśli chcesz mieć obsługę wielu języków.

Z tego, co pamiętam z czasów Uni, ta metoda wymaga minimalnego wysiłku ze strony serwera.

shawad
źródło
2

Używam tej klasy rozszerzenia:

public static class Int32Extensions
{
    public static string ToOrdinal(this int i)
    {
        return (i + "th")
            .Replace("1th", "1st")
            .Replace("2th", "2nd")
            .Replace("3th", "3rd");
    }
}
Perry Tribolet
źródło
11, 12, 13
Kcoder
2

Żądano wersji „mniej redundancji” odpowiedzi samjudson ...

public static string AddOrdinal(int number)
{
    if (number <= 0) return number.ToString();

    string GetIndicator(int num)
    {
        switch (num % 100)
        {
            case 11:
            case 12:
            case 13:
                return "th";
        }

        switch (num % 10)
        {
            case 1:
                return "st";
            case 2:
                return "nd";
            case 3:
                return "rd";
            default:
                return "th";
        }
    }

    return number + GetIndicator(number);
}
Ian Warburton
źródło
2
Odsłoniłbym „GetIndicator” jako a public statici zmieniłem jego nazwę na bardziej mnemoniczną nazwę (tj. „OrdinalSuffix”). Dzwoniący może chcieć, aby część liczbowa była w różnych formatach (np. Przecinkami).
Tom
2
        private static string GetOrd(int num) => $"{num}{(!(Range(11, 3).Any(n => n == num % 100) ^ Range(1, 3).All(n => n != num % 10)) ? new[] { "ˢᵗ", "ⁿᵈ", "ʳᵈ" }[num % 10 - 1] : "ᵗʰ")}";

Jeśli ktoś szuka jednej wkładki: str

Ali Humayun
źródło
1
public static string OrdinalSuffix(int ordinal)
{
    //Because negatives won't work with modular division as expected:
    var abs = Math.Abs(ordinal); 

    var lastdigit = abs % 10; 

    return 
        //Catch 60% of cases (to infinity) in the first conditional:
        lastdigit > 3 || lastdigit == 0 || (abs % 100) - lastdigit == 10 ? "th" 
            : lastdigit == 1 ? "st" 
            : lastdigit == 2 ? "nd" 
            : "rd";
}
Faust
źródło
1

EDYCJA : Jak zauważa YM_Industries w komentarzu, odpowiedź samjudsona DZIAŁA dla liczb powyżej 1000, wydaje się, że komentarz Nickfa zniknął i nie pamiętam, jaki był problem. Pozostaw tę odpowiedź tutaj dla czasów porównania.

Okropnie wiele z nich nie działa dla liczb> 999, jak nick zauważył w komentarzu (EDYCJA: teraz brakuje).

Oto wersja oparta na zmodyfikowanej wersji zaakceptowanej odpowiedzi samjudsona , która spełnia.

public static String GetOrdinal(int i)
{
    String res = "";

    if (i > 0)
    {
        int j = (i - ((i / 100) * 100));

        if ((j == 11) || (j == 12) || (j == 13))
            res = "th";
        else
        {
            int k = i % 10;

            if (k == 1)
                res = "st";
            else if (k == 2)
                res = "nd";
            else if (k == 3)
                res = "rd";
            else
                res = "th";
        }
    }

    return i.ToString() + res;
}

Również Shahzad Qureshi „s odpowiedź za pomocą manipulacji ciąg działa dobrze, jednak ma spadku wydajności. Aby wygenerować wiele z nich, przykładowy program LINQPad sprawia, że ​​wersja łańcucha jest 6-7 razy wolniejsza niż ta liczba całkowita (chociaż trzeba by generować dużo, aby zauważyć).

Przykład LINQPad:

void Main()
{
    "Examples:".Dump();

    foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 10000013 })
        Stuff.GetOrdinal(i).Dump();

    String s;

    System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();

    for(int iter = 0; iter < 100000; iter++)
        foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
            s = Stuff.GetOrdinal(i);

    "Integer manipulation".Dump();
    sw.Elapsed.Dump();

    sw.Restart();

    for(int iter = 0; iter < 100000; iter++)
        foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
            s = (i.ToString() + Stuff.GetOrdinalSuffix(i));

    "String manipulation".Dump();
    sw.Elapsed.Dump();
}

public class Stuff
{
        // Use integer manipulation
        public static String GetOrdinal(int i)
        {
                String res = "";

                if (i > 0)
                {
                        int j = (i - ((i / 100) * 100));

                        if ((j == 11) || (j == 12) || (j == 13))
                                res = "th";
                        else
                        {
                                int k = i % 10;

                                if (k == 1)
                                        res = "st";
                                else if (k == 2)
                                        res = "nd";
                                else if (k == 3)
                                        res = "rd";
                                else
                                        res = "th";
                        }
                }

                return i.ToString() + res;
        }

        // Use string manipulation
        public static string GetOrdinalSuffix(int num)
        {
                if (num.ToString().EndsWith("11")) return "th";
                if (num.ToString().EndsWith("12")) return "th";
                if (num.ToString().EndsWith("13")) return "th";
                if (num.ToString().EndsWith("1")) return "st";
                if (num.ToString().EndsWith("2")) return "nd";
                if (num.ToString().EndsWith("3")) return "rd";
                return "th";
        }
}
Whelkaholism
źródło
Nie mogę znaleźć komentarza @ nickf, co jest nie tak z odpowiedzią samjudson? Wydaje mi się, że dobrze radzi sobie z liczbami powyżej 1000, a jednocześnie jest o wiele bardziej czytelny niż twój.
Joshua Walsh
1
To uczciwy komentarz, właśnie uruchomiłem zestaw testowy i nie mogę znaleźć żadnych problemów. Wydaje się, że nie było żadnych zmian w odpowiedzi Sama, więc mogę sobie tylko wyobrazić, że oszalałem. Zredagowałem swoją odpowiedź, aby to odzwierciedlić.
Whelkaholism
1
Haha, wszyscy mamy takie chwile, prawda? Spoglądamy wstecz na stary kod i mówimy „dlaczego, do diabła, to napisałem?”
Joshua Walsh
1

Na podstawie innych odpowiedzi:

public static string Ordinal(int n)
{   
    int     r = n % 100,     m = n % 10;

    return (r<4 || r>20) && (m>0 && m<4) ? n+"  stndrd".Substring(m*2,2) : n+"th";                                              
}
Dave Sumter
źródło
3
1. MIEJSCE: Najbardziej niepotrzebnie tajemnicza odpowiedź. „Niepotrzebnie”: korzyści z rozmiaru / wydajności kodu nie są warte kosztów czytelności. „Cryptic”: Konieczne jest znaczące tłumaczenie, aby zmapować się do wymagań „Layperson”.
Tom
0

FWIW, dla MS-SQL, to wyrażenie wykona zadanie. Zachowaj pierwszy WHEN ( WHEN num % 100 IN (11, 12, 13) THEN 'th') jako pierwszy na liście, ponieważ zależy to od wypróbowania go przed innymi.

CASE
  WHEN num % 100 IN (11, 12, 13) THEN 'th' -- must be tried first
  WHEN num % 10 = 1 THEN 'st'
  WHEN num % 10 = 2 THEN 'nd'
  WHEN num % 10 = 3 THEN 'rd'
  ELSE 'th'
END AS Ordinal

W przypadku programu Excel:

=MID("thstndrdth",MIN(9,2*RIGHT(A1)*(MOD(A1-11,100)>2)+1),2)

Wyrażenie (MOD(A1-11,100)>2)ma wartość PRAWDA (1) dla wszystkich liczb z wyjątkiem każdego kończącego się na 11,12,13(FAŁSZ = 0). Tak więc 2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1)kończy się jako 1 na 11.12.2013, w przeciwnym razie:
1 ocenia na 3
2 do 5,
3 do 7
innych: 9
- i wymagane 2 znaki są wybierane "thstndrdth"z tej pozycji.

Jeśli naprawdę chcesz przekonwertować to dość bezpośrednio na SQL, działało to dla mnie dla kilku wartości testowych:

DECLARE @n as int
SET @n=13
SELECT SubString(  'thstndrdth'
                 , (SELECT MIN(value) FROM
                     (SELECT 9 as value UNION
                      SELECT 1+ (2* (ABS(@n) % 10)  *  CASE WHEN ((ABS(@n)+89) % 100)>2 THEN 1 ELSE 0 END)
                     ) AS Mins
                   )
                 , 2
                )
AjV Jsy
źródło
0

Jest to implementacja darti może być modyfikowana zgodnie z językiem.

String getOrdinalSuffix(int num){
    if (num.toString().endsWith("11")) return "th";
    if (num.toString().endsWith("12")) return "th";
    if (num.toString().endsWith("13")) return "th";
    if (num.toString().endsWith("1")) return "st";
    if (num.toString().endsWith("2")) return "nd";
    if (num.toString().endsWith("3")) return "rd";
    return "th";
}
Phani Rithvij
źródło
0

Chociaż jest tu wiele dobrych odpowiedzi, myślę, że jest miejsce na inną, tym razem opartą na dopasowaniu wzorców, jeśli nie na niczym innym, to przynajmniej na dyskusyjną czytelność

    public static string Ordinals1(this int number)
    {
        switch (number)
        {
            case int p when p % 100 == 11:
            case int q when q % 100 == 12:
            case int r when r % 100 == 13:
                return $"{number}th";
            case int p when p % 10 == 1:
                return $"{number}st";
            case int p when p % 10 == 2:
                return $"{number}nd";
            case int p when p % 10 == 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

a co wyróżnia to rozwiązanie? nic poza faktem, że dodałem rozważania dotyczące wydajności dla różnych innych rozwiązań

szczerze mówiąc, wątpię, czy wydajność naprawdę ma znaczenie w tym konkretnym scenariuszu (który naprawdę potrzebuje porządków milionów liczb), ale przynajmniej przedstawia pewne porównania, które należy wziąć pod uwagę ...

1 milion pozycji w celach informacyjnych (twój przebieg może się różnić w zależności od specyfikacji maszyny)

z dopasowaniem wzorca i podziałami (ta odpowiedź)

~ 622 ms

z dopasowaniem wzorca i łańcuchami (ta odpowiedź)

~ 1967 ms

z dwoma przełącznikami i podziałami (zaakceptowana odpowiedź)

~ 637 ms

z jednym przełącznikiem i podziałami (inna odpowiedź)

~ 725 ms

void Main()
{
    var timer = new Stopwatch();
    var numbers = Enumerable.Range(1, 1000000).ToList();

    // 1
    timer.Reset();
    timer.Start();
    var results1 = numbers.Select(p => p.Ordinals1()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and divisions");

    // 2
    timer.Reset();
    timer.Start();
    var results2 = numbers.Select(p => p.Ordinals2()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and strings");

    // 3
    timer.Reset();
    timer.Start();
    var results3 = numbers.Select(p => p.Ordinals3()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with two switches and divisons");

    // 4
    timer.Reset();
    timer.Start();
    var results4 = numbers.Select(p => p.Ordinals4()).ToList();
    timer.Stop();
    timer.Elapsed.TotalMilliseconds.Dump("with one switche and divisons");
}

public static class Extensions
{
    public static string Ordinals1(this int number)
    {
        switch (number)
        {
            case int p when p % 100 == 11:
            case int q when q % 100 == 12:
            case int r when r % 100 == 13:
                return $"{number}th";
            case int p when p % 10 == 1:
                return $"{number}st";
            case int p when p % 10 == 2:
                return $"{number}nd";
            case int p when p % 10 == 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

    public static string Ordinals2(this int number)
    {
        var text = number.ToString();
        switch (text)
        {
            case string p when p.EndsWith("11"):
                return $"{number}th";
            case string p when p.EndsWith("12"):
                return $"{number}th";
            case string p when p.EndsWith("13"):
                return $"{number}th";
            case string p when p.EndsWith("1"):
                return $"{number}st";
            case string p when p.EndsWith("2"):
                return $"{number}nd";
            case string p when p.EndsWith("3"):
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

    public static string Ordinals3(this int number)
    {
        switch (number % 100)
        {
            case 11:
            case 12:
            case 13:
                return $"{number}th";
        }

        switch (number % 10)
        {
            case 1:
                return $"{number}st";
            case 2:
                return $"{number}nd";
            case 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }

    public static string Ordinals4(this int number)
    {
        var ones = number % 10;
        var tens = Math.Floor(number / 10f) % 10;
        if (tens == 1)
        {
            return $"{number}th";
        }

        switch (ones)
        {
            case 1:
                return $"{number}th";
            case 2:
                return $"{number}nd";
            case 3:
                return $"{number}rd";
            default:
                return $"{number}th";
        }
    }
}
Dan Dohotaru
źródło
0

Kolejny jednowierszowy, ale bez porównań tylko poprzez indeksowanie wyniku wyrażenia regularnego do tablicy.

public static string GetOrdinalSuffix(int input)
{
    return new []{"th", "st", "nd", "rd"}[Convert.ToInt32("0" + Regex.Match(input.ToString(), "(?<!1)[1-3]$").Value)];
}

Wersję PowerShell można dodatkowo skrócić:

function ord($num) { return ('th','st','nd','rd')[[int]($num -match '(?<!1)[1-3]$') * $matches[0]] }
Pluton
źródło
0

Kolejna 1 wkładka.

public static string Ordinal(this int n)
{    
 return n + (new [] {"st","nd","rd" }.ElementAtOrDefault((((n + 90) % 100 - 10) % 10 - 1)) ?? "th");
}
Echostorm
źródło
-2

Oto klasa DateTime Extension. Kopiuj, wklej i ciesz się

publiczna klasa statyczna DateTimeExtensions {

    public static string ToStringWithOrdinal(this DateTime d)
    {
        var result = "";
        bool bReturn = false;            

        switch (d.Day % 100)
        {
            case 11:
            case 12:
            case 13:
                result = d.ToString("dd'th' MMMM yyyy");
                bReturn = true;
                break;
        }

        if (!bReturn)
        {
            switch (d.Day % 10)
            {
                case 1:
                    result = d.ToString("dd'st' MMMM yyyy");
                    break;
                case 2:
                    result = d.ToString("dd'nd' MMMM yyyy");
                    break;
                case 3:
                    result = d.ToString("dd'rd' MMMM yyyy");
                    break;
                default:
                    result = d.ToString("dd'th' MMMM yyyy");
                    break;
            }

        }

        if (result.StartsWith("0")) result = result.Substring(1);
        return result;
    }
}

Wynik:

9 października 2014 r

Maulik Patel
źródło
Duplikujesz: a) ciąg formatu daty (X5) ib) całą resztę metody (gdy pojawi się prawdopodobny przypadek użycia (jeśli jeszcze go nie ma), że potrzebny jest sufiks porządkowy dla dnia innego niż miesiąc cele, a nawet Dzień miesiąca z innym ciągiem formatu daty). Skorzystaj z metody „OrdinalSuffix”, którą zasugerowałem, aby zostać ujawnionym przez Iana Warburtona w dniu 6 kwietnia 2017 o 16:32 ( stackoverflow.com/questions/20156/... ).
Tom
-3

Kolejna alternatywa, której użyłem na podstawie wszystkich innych sugestii, ale nie wymaga specjalnej obudowy:

    public static string DateSuffix(int day)
    {
        if (day == 11 | day == 12 | day == 13) return "th";
        Math.DivRem(day, 10, out day);
        switch (day)
        {
            case 1:
                return "st";
            case 2:
                return "nd";
            case 3:
                return "rd";
            default:
                return "th";
        }
    }
Rupert
źródło