ReverseString, вопрос интервью с С#

У меня был вопрос с интервью, который попросил меня написать "отзыв" на фрагменте кода, написанном младшим программистом. Они намекнули, что может возникнуть проблема, и сказал, что он будет использоваться в основном на больших струнах.

public string ReverseString(string sz)
{
    string result = string.Empty;
    for(int i = sz.Length-1; i>=0; i--)
    {
      result += sz[i]
    }
    return result;
}

Я не мог это заметить. Я не видел никаких проблем. Оглядываясь назад, я мог бы сказать, что пользователь должен изменить размер, но похоже, что у С# нет изменения размера (я парень С++).

В итоге я написал такие вещи, как использование итератора, если это возможно, [x] в контейнерах не может быть произвольным доступом, поэтому он может быть медленным. и разные вещи. Но я определенно сказал, что мне никогда не приходилось оптимизировать код С#, поэтому мое мышление, возможно, не подвело меня в интервью.

Я хотел знать, в чем проблема с этим кодом, вы его видите?

-edit -

Я изменил это на wiki, потому что может быть несколько правильных ответов. Также я так рад, что я прямо сказал, что мне никогда не приходилось оптимизировать программу на С# и упомянул о других вещах. К сожалению. Я всегда думал, что у С# не было проблем с производительностью с этими вещами. упс.

Ответ 1

Несколько комментариев по ответам, приведенным до сих пор:

  • Каждый из них (до сих пор!) будет терпеть неудачу на суррогатных парах и комбинировать символы. О, радости Юникода. Реверсирование строки - это не то же самое, что реверсирование последовательности символов.
  • Мне нравится Марковская оптимизация для нулевых, пустых и одиночных символов. В частности, это не только быстро получает правильный ответ, но также обрабатывает нуль (что ни один из других ответов не делает)
  • Я изначально думал, что ToCharArray, за которым следует Array.Reverse, будет самым быстрым, но создаст одну "мусорную" копию.
  • Решение StringBuilder создает одну строку (не массив char) и обрабатывает ее до тех пор, пока вы не назовете ToString. Там нет дополнительного копирования... но там гораздо больше работы, поддерживающей длины и т.д.

Какое наиболее эффективное решение? Ну, мне нужно было бы сравнить это, чтобы иметь хоть какую-то идею, но даже так, чтобы не рассказать всю историю. Вы используете это в ситуации с высоким давлением памяти, где лишний мусор - настоящая боль? Насколько быстро ваша память и ваш процессор и т.д.?

Как всегда, читаемость обычно короля - и она не намного лучше, чем ответ Марка на этом фронте. В частности, нет места для ошибки "один за другим", тогда как я должен был бы подумать о том, чтобы подтвердить другие ответы. Мне не нравится думать. Мне больно, поэтому я стараюсь не делать этого очень часто. Использование встроенного Array.Reverse звучит намного лучше для меня. (Хорошо, так что он все еще терпит неудачу в суррогатах и ​​т.д., Но эй...)

Ответ 2

Самое главное? Это сосать производительность - нужно создать лоты строк (по одному на символ). Самый простой способ - это что-то вроде:

public static string Reverse(string sz) // ideal for an extension method
{
    if (string.IsNullOrEmpty(sz) || sz.Length == 1) return sz;
    char[] chars = sz.ToCharArray();
    Array.Reverse(chars);
    return new string(chars);
}

Ответ 3

Проблема в том, что строковые конкатенации дороги, потому что строки неизменны в С#. Приведенный пример создаст новую строку на один символ дольше каждой итерации, что очень неэффективно. Чтобы этого избежать, вы должны использовать класс StringBuilder, например:

public string ReverseString(string sz)
{
    var builder = new StringBuilder(sz.Length);
    for(int i = sz.Length-1; i>=0; i--)
    {
      builder.Append(sz[i]);
    }
    return builder.ToString();
}

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

Вы заметите, что я предоставил StringBuilder первоначальную емкость, которую вы часто не видите. Поскольку вы знаете длину результата для начала, это устраняет ненужные выделения памяти.

Что обычно происходит, он выделяет объем памяти StringBuilder (по умолчанию 16 символов). Как только контент пытается превысить эту емкость, он удваивает (я думаю) свою собственную способность и продолжает. Это намного лучше, чем распределение памяти каждый раз, как это происходит с обычными строками, но если вы также можете избежать этого, это еще лучше.

Ответ 4

Поскольку строки неизменяемы, каждый оператор += создаст новую строку, скопировав строку на последнем шаге вместе с единственным символом, чтобы сформировать новую строку. Эффективно это будет O (n 2) алгоритм вместо O (n).

Более быстрый способ был бы (O (n)):

// pseudocode:
static string ReverseString(string input) {
    char[] buf = new char[input.Length];
    for(int i = 0; i < buf.Length; ++i)
       buf[i] = input[input.Length - i - 1];
    return new string(buf);
}

Ответ 5

Вместо этого вы можете сделать это в .NET 3.5:

    public static string Reverse(this string s)
    {
        return new String((s.ToCharArray().Reverse()).ToArray());
    }

Ответ 6

Лучший способ справиться с этим - это использовать StringBuilder, так как он не является неизменным, вы не получите ужасное поведение генерации объектов, которое вы получите выше. В .net все строки являются неизменяемыми, что означает, что оператор + = будет создавать новый объект каждый раз, когда он попадает. StringBuilder использует внутренний буфер, поэтому разворот может быть выполнен в буфере без дополнительных распределений объектов.

Ответ 7

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

Ответ 8

Я предпочитаю что-то вроде этого:

using System;
using System.Text;
namespace SpringTest3
{
    static class Extentions
    {
        static private StringBuilder ReverseStringImpl(string s, int pos, StringBuilder sb)
        {
            return (s.Length <= --pos || pos < 0) ? sb : ReverseStringImpl(s, pos, sb.Append(s[pos]));
        }

        static public string Reverse(this string s)
        {
            return ReverseStringImpl(s, s.Length, new StringBuilder()).ToString();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("abc".Reverse());
        }
    }
}

Ответ 9

x - это строка, которую нужно отменить.

        Stack<char> stack = new Stack<char>(x);

        string s = new string(stack.ToArray());

Ответ 10

Этот метод сокращает количество итераций пополам. Вместо того, чтобы начинать с конца, он начинается с начала и свопирует символы до тех пор, пока он не достигнет центра. Пришлось преобразовать строку в массив char, потому что указатель на строке не имеет сеттера.

    public string Reverse(String value)
    {
        if (String.IsNullOrEmpty(value)) throw new ArgumentNullException("value");

        char[] array = value.ToCharArray();

        for (int i = 0; i < value.Length / 2; i++)
        {
            char temp = array[i];
            array[i] = array[(array.Length - 1) - i];
            array[(array.Length - 1) - i] = temp;
        }

        return new string(array);
    }

Ответ 11

Некромантия.
Как публичная служба, так вы фактически ПРАВИЛЬНО меняете строку
(изменение строки NOT равно изменению последовательности символов)

public static class Test
{

    private static System.Collections.Generic.List<string> GraphemeClusters(string s)
    {
        System.Collections.Generic.List<string> ls = new System.Collections.Generic.List<string>();

        System.Globalization.TextElementEnumerator enumerator = System.Globalization.StringInfo.GetTextElementEnumerator(s);
        while (enumerator.MoveNext())
        {
            ls.Add((string)enumerator.Current);
        }

        return ls;
    }


    // this 
    private static string ReverseGraphemeClusters(string s)
    {
        if(string.IsNullOrEmpty(s) || s.Length == 1)
             return s;

        System.Collections.Generic.List<string> ls = GraphemeClusters(s);
        ls.Reverse();

        return string.Join("", ls.ToArray());
    }

    public static void TestMe()
    {
        string s = "Les Mise\u0301rables";
        // s = "noël";
        string r = ReverseGraphemeClusters(s);

        // This would be wrong:
        // char[] a = s.ToCharArray();
        // System.Array.Reverse(a);
        // string r = new string(a);

        System.Console.WriteLine(r);
    }
}

См: https://vimeo.com/7403673

Кстати, в Голанге правильный способ:

package main

import (
  "unicode"
  "regexp"
)

func main() {
    str := "\u0308" + "a\u0308" + "o\u0308" + "u\u0308"
    println("u\u0308" + "o\u0308" + "a\u0308" + "\u0308" == ReverseGrapheme(str))
    println("u\u0308" + "o\u0308" + "a\u0308" + "\u0308" == ReverseGrapheme2(str))
}

func ReverseGrapheme(str string) string {

  buf := []rune("")
  checked := false
  index := 0
  ret := "" 

    for _, c := range str {

        if !unicode.Is(unicode.M, c) {

            if len(buf) > 0 {
                ret = string(buf) + ret
            }

            buf = buf[:0]
            buf = append(buf, c)

            if checked == false {
                checked = true
            }

        } else if checked == false {
            ret = string(append([]rune(""), c)) + ret
        } else {
            buf = append(buf, c)
        }

        index += 1
    }

    return string(buf) + ret
}

func ReverseGrapheme2(str string) string {
    re := regexp.MustCompile("\\PM\\pM*|.")
    slice := re.FindAllString(str, -1)
    length := len(slice)
    ret := ""

    for i := 0; i < length; i += 1 {
        ret += slice[length-1-i]
    }

    return ret
}

И неправильным способом является это (ToCharArray.Reverse):

func Reverse(s string) string {
    runes := []rune(s)
    for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
        runes[i], runes[j] = runes[j], runes[i]
    }
    return string(runes)
}

Обратите внимание, что вам нужно знать разницу между
- персонаж и глиф
- байт (8 бит) и код/​​руна (32 бит)
- код и GraphemeCluster [32+ бит] (ака Grapheme/Glyph)

Ссылка:

Характер - это перегруженный термин, чем может означать много вещей.

Кодовая точка - это атомная единица информации. Текст представляет собой последовательность кодовые точки. Каждая кодовая точка - это число, которое задается значением Стандарт Unicode.

Графема - это последовательность из одной или нескольких кодовых точек, которые отображаются как единый графический блок, который читатель распознает как единый элемент системы письма. Например, и a, и ä являются графемы, но они могут состоять из множества кодовых точек (например, ä может быть две кодовые точки, одна для базового символа a, а затем одна для диарез; но есть также альтернативная, устаревшая, единая кодовая точка представляя эту графему). Некоторые кодовые точки никогда не являются частью каких-либо графем (например, без столяра с нулевой шириной или направленными переопределениями).

Глиф - это изображение, обычно хранящееся в шрифте (который представляет собой коллекцию глифов), используемых для представления графемов или их частей. Шрифты могут составлять несколько глифов в одно представление, например, если приведенный выше ä представляет собой единую кодовую точку, шрифт может выбрать, чтобы сделать это как два отдельных, пространственно наложенных глифа. Для OTF шрифт GSUB и Таблицы GPOS содержат информацию о замещении и позиционировании, чтобы сделать эта работа. Шрифт может содержать несколько альтернативных глифов для одного и того же графем тоже.

Ответ 12

 static string reverseString(string text)
    {
        Char[] a = text.ToCharArray();
        string b = "";
        for (int q = a.Count() - 1; q >= 0; q--)
        {
            b = b + a[q].ToString();
        }
        return b;
    }