Группировка строк по начальной букве с культурной осведомленностью - PullRequest
3 голосов
/ 01 апреля 2011

Я пытаюсь сгруппировать список строк по их начальной букве. Допустим, это список:

azaroth 
älgkebab 
orgel 
ölkorv

Когда список отсортирован в соответствии с sv-SE, это порядок сортировки:

azaroth 
orgel 
älgkebab 
ölkorv

Что означает, что группировка по начальной букве будет

A
  azaroth
O
  orgel
Ä
  älgkebab
Ö 
  ölkorv

Это имеет смысл, и это также, как вы найдете его в телефонной книге в стране, где используется sv-SE.

Когда список отсортирован в соответствии с en-US, это порядок сортировки:

älgkebab 
azaroth 
ölkorv
orgel 

Теперь самое интересное. Это означает, что группировка по начальной букве будет

AÄ
  älgkebab
  azaroth
OÖ
  ölkorv
  orgel

Поскольку во всех практических целях «a» и «ä» рассматривались как одна и та же буква во время сортировки, так же как «o» и «ö», что означает, что они для этой цели одинаковые начальные . Это AFAIK, как вы найдете его в телефонной книге в стране, которая использует en-US.

Мой вопрос: как я могу достичь этой группировки программно, когда она варьируется в зависимости от культуры? Или, другими словами, как узнать, какие буквы рассматриваются как «одинаковые» при сортировке список в конкретной культуре?

Я не нашел способа сделать StringComparer возврат 0 для "a" против "ä", например.

У меня есть решение, которое, кажется, работает, которое делает это:

if (
    cultureInfo.CompareInfo.GetSortKey("a").KeyData[1] ==
    cultureInfo.CompareInfo.GetSortKey("ä").KeyData[1]
) // same initial (this will return false for sv-SE and true for en-US)

Проблема в том, что я понятия не имею, работает ли он для любой культуры или даже каков второй фрагмент данных в массиве KeyData SortKey на самом деле. Страница на MSDN довольно расплывчата и, вероятно, целенаправленно. Так что я бы предпочел, чтобы было более надежное решение.

Ответы [ 2 ]

1 голос
/ 01 апреля 2011

Когда вы сравниваете a и ä в sv-SE, результат равен -1, так что если два слова одинаковы, за исключением умлаута, они всегда сортируются одинаково. Но вы все равно можете выяснить, что они отсортированы одинаково, иначе: добавьте некоторый символ к одному из них и другому, по-разному отсортируйте к другому, и сравните их. Затем переключите добавленные символы и сравните снова. Если результат отличается, символы сортируются одинаково.

Пример:

sv-SE:
"a0" < "ä1"
"a1" < "ä0"
en-US:
"a0" < "ä1"
"a1" > "ä0"

Таким образом, в sv-SE, 'a' < 'ä', но в en-US 'a' == 'ä'. Ниже приведен класс, который группирует список строк в соответствии с этими правилами. Но это не работает должным образом для некоторых культур, потому что их порядок сортировки является более сложным. Например, на чешском языке, ch считается отдельной буквой, отсортированной после h. Я понятия не имею, как бы вы это исправить.

Кроме того, код использует 0 и 1 в качестве добавляемых символов. Если в некоторых культурах эти символы не влияют на сортировку, это не сработает.

class Grouper
{
    StringComparer m_comparer;

    public Grouper(StringComparer comparer)
    {
        m_comparer = comparer;
    }

    public List<Tuple<string, List<string>>> Group(IEnumerable<string> strings)
    {
        List<Tuple<string, List<string>>> result =
            new List<Tuple<string, List<string>>>();

        var sorted = strings.OrderBy(s => s, m_comparer);

        string previous = null;

        List<char> currentGroupName = null;
        List<string> currentGroup = null;

        foreach (var s in sorted)
        {
            char sInitial = ToUpper(s[0]);
            if (currentGroup == null || !AreEqual(s[0], previous[0]))
            {
                if (currentGroup != null)
                    result.Add(Tuple.Create(
                        SortGroupName(currentGroupName),
                        currentGroup));
                currentGroupName = new List<char> { sInitial };
                currentGroup = new List<string> { s };
            }
            else
            {
                if (!currentGroupName.Contains(sInitial))
                    currentGroupName.Add(sInitial);
                currentGroup.Add(s);
            }

            previous = s;
        }

        if (currentGroup != null)
            result.Add(Tuple.Create(SortGroupName(currentGroupName), currentGroup));

        return result;
    }

    string SortGroupName(List<char> chars)
    {
        return new string(chars.OrderBy(c => c.ToString(), m_comparer).ToArray());
    }

    bool AreEqual(char c1, char c2)
    {
        return Math.Sign(m_comparer.Compare(c1 + "0", c2 + "1")) ==
            -Math.Sign(m_comparer.Compare(c1 + "1", c2 + "0"));
    }

    char ToUpper(char c)
    {
        return c.ToString().ToUpper()[0];
    }
}

Кроме того, этот класс далек от качества производства, например, он не обрабатывает null s или пустые строки.

0 голосов
/ 01 апреля 2011

Вероятно, это внутренняя реализация, похожая на константу.Само значение не имеет значения, только как оно сравнивается с другими связанными значениями.

Это похоже на (например) значение EOF в C. Хотя GCC определяет его как -1, фактическое значение МОЖЕТ изменятьсяи поэтому код конечного разработчика должен только сравнивать значение, а не оценивать его.

...