Как вы переворачиваете строку в JavaScript? - PullRequest
393 голосов
/ 06 июня 2009

Как вы переворачиваете строку на месте (или на месте) в JavaScript при передаче функции с оператором return? Все без использования встроенных функций? .reverse(), .charAt() и т. Д.

Ответы [ 44 ]

691 голосов
/ 06 июня 2009

Пока вы имеете дело с простыми символами ASCII и готовы использовать встроенные функции, это будет работать:

function reverse(s){
    return s.split("").reverse().join("");
}

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

393 голосов
/ 27 мая 2013

Следующий метод (или аналогичный) обычно используется для обращения строки в JavaScript:

// Don’t use this!
var naiveReverse = function(string) {
    return string.split('').reverse().join('');
}

На самом деле, все ответы, опубликованные до сих пор, являются вариацией этой схемы. Однако есть некоторые проблемы с этим решением. Например:

naiveReverse('foo ? bar');
// → 'rab �� oof'
// Where did the `?` symbol go? Whoops!

Если вам интересно, почему это происходит, читайте о внутренней кодировке JavaScript . (TL; DR: ? является астральным символом, и JavaScript представляет его как две отдельные единицы кода.)

Но есть еще:

// To see which symbols are being used here, check:
// http://mothereff.in/js-escapes#1ma%C3%B1ana%20man%CC%83ana
naiveReverse('mañana mañana');
// → 'anãnam anañam'
// Wait, so now the tilde is applied to the `a` instead of the `n`? WAT.

Хорошей строкой для тестирования реализаций обратной строки является следующее :

'foo ? bar mañana mañana'

Почему? Поскольку он содержит астральный символ (?) (которые представлены суррогатными парами в JavaScript ) и знак объединения ( в последнем mañana фактически состоит из двух символов: U + 006E ЛАТИНСКОЕ МАЛЕНЬКОЕ ПИСЬМО N и U + 0303 КОМБИНИРОВАННАЯ ТИЛЬДА).

Порядок, в котором появляются суррогатные пары, изменить нельзя, иначе астральный символ больше не будет отображаться в «перевернутой» строке. Вот почему вы видели эти �� отметки в выводе для предыдущего примера.

Комбинированные метки всегда применяются к предыдущему символу, поэтому вы должны рассматривать оба основных символа (U + 006E LATIN SMALL LETTER N) как комбинирующие метки (U + 0303 COMBINING TILDE) в целом. Изменение их порядка приведет к объединению метки объединения с другим символом в строке. Вот почему выходной пример имел вместо ñ.

Надеюсь, это объясняет, почему все опубликованные до сих пор ответы неверны .


Чтобы ответить на ваш первоначальный вопрос - как [правильно] перевернуть строку в JavaScript - я написал небольшую библиотеку JavaScript, которая способна переворачивать строки с поддержкой Юникода. У него нет ни одной из проблем, которые я только что упомянул. Библиотека называется Esrever ; его код находится на GitHub, и он работает практически в любой среде JavaScript. Он поставляется с утилитой shell / binary, так что вы можете легко перезаписывать строки с вашего терминала, если хотите.

var input = 'foo ? bar mañana mañana';
esrever.reverse(input);
// → 'anañam anañam rab ? oof'

Что касается части «на месте», см. Другие ответы.

88 голосов
/ 06 июня 2009
String.prototype.reverse=function(){return this.split("").reverse().join("");}

или

String.prototype.reverse = function() {
    var s = "";
    var i = this.length;
    while (i>0) {
        s += this.substring(i-1,i);
        i--;
    }
    return s;
}
51 голосов
/ 28 октября 2014

Детальный анализ и десять различных способов перевернуть строку и подробности их исполнения.

http://eddmann.com/posts/ten-ways-to-reverse-a-string-in-javascript/

Выполнение этих реализаций:

Наиболее эффективные реализации для браузера

  • Chrome 15 - реализации 1 и 6
  • Firefox 7 - Реализация 6
  • IE 9 - Реализация 4
  • Opera 12 - Реализация 9

Вот эти реализации:

Реализация 1:

function reverse(s) {
  var o = '';
  for (var i = s.length - 1; i >= 0; i--)
    o += s[i];
  return o;
}

Реализация 2:

function reverse(s) {
  var o = [];
  for (var i = s.length - 1, j = 0; i >= 0; i--, j++)
    o[j] = s[i];
  return o.join('');
}

Реализация 3:

function reverse(s) {
  var o = [];
  for (var i = 0, len = s.length; i <= len; i++)
    o.push(s.charAt(len - i));
  return o.join('');
}

Реализация 4:

function reverse(s) {
  return s.split('').reverse().join('');
}

Реализация 5:

function reverse(s) {
  var i = s.length,
      o = '';
  while (i > 0) {
    o += s.substring(i - 1, i);
    i--;
  }
  return o;
}

Реализация 6:

function reverse(s) {
  for (var i = s.length - 1, o = ''; i >= 0; o += s[i--]) { }
  return o;
}

Реализация 7:

function reverse(s) {
  return (s === '') ? '' : reverse(s.substr(1)) + s.charAt(0);
}

Реализация 8:

function reverse(s) {
  function rev(s, len, o) {
    return (len === 0) ? o : rev(s, --len, (o += s[len]));
  };
  return rev(s, s.length, '');
}

Реализация 9:

function reverse(s) {
  s = s.split('');
  var len = s.length,
      halfIndex = Math.floor(len / 2) - 1,
      tmp;


     for (var i = 0; i <= halfIndex; i++) {
        tmp = s[len - i - 1];
        s[len - i - 1] = s[i];
        s[i] = tmp;
      }
      return s.join('');
    }

Реализация 10

function reverse(s) {
  if (s.length < 2)
    return s;
  var halfIndex = Math.ceil(s.length / 2);
  return reverse(s.substr(halfIndex)) +
         reverse(s.substr(0, halfIndex));
}
51 голосов
/ 14 июля 2010

Целое «перевернуть строку на месте» - это устаревший вопрос для интервьюеров, программистов C, и люди, которые брали у них интервью (возможно, для мести?), Спросят. К сожалению, это часть «На месте», которая больше не работает, потому что строки практически в любом управляемом языке (JS, C # и т. Д.) Используют неизменяемые строки, что разрушает всю идею перемещения строки без выделения новой памяти.

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

Лично я действительно ненавижу такие вопросы на собеседовании, но, к сожалению, я уверен, что мы будем их видеть в течение многих лет.

36 голосов
/ 10 апреля 2016

Сначала используйте Array.from(), чтобы превратить строку в массив, затем Array.prototype.reverse(), чтобы обратить массив, а затем Array.prototype.join(), чтобы вернуть обратно строку.

const reverse = str => Array.from(str).reverse().join('');
24 голосов
/ 24 декабря 2015

В ECMAScript 6 вы можете перевернуть строку еще быстрее, не используя .split('') метод разбиения, с помощью оператора , например:

var str = [...'racecar'].reverse().join('');
19 голосов
/ 07 марта 2012

Похоже, я опоздал на вечеринку на 3 года ...

К сожалению, вы не можете, как было указано. См. Являются ли строки JavaScript неизменяемыми? Нужен ли мне «строитель строк» ​​в JavaScript?

Следующая лучшая вещь, которую вы можете сделать, - это создать «представление» или «обертку», которая принимает строку и переопределяет любые части используемого вами строкового API, но притворяется, что строка обратная. Например:

var identity = function(x){return x};

function LazyString(s) {
    this.original = s;

    this.length = s.length;
    this.start = 0; this.stop = this.length; this.dir = 1; // "virtual" slicing
    // (dir=-1 if reversed)

    this._caseTransform = identity;
}

// syntactic sugar to create new object:
function S(s) {
    return new LazyString(s);
}

//We now implement a `"...".reversed` which toggles a flag which will change our math:

(function(){ // begin anonymous scope
    var x = LazyString.prototype;

    // Addition to the String API
    x.reversed = function() {
        var s = new LazyString(this.original);

        s.start = this.stop - this.dir;
        s.stop = this.start - this.dir;
        s.dir = -1*this.dir;
        s.length = this.length;

        s._caseTransform = this._caseTransform;
        return s;
    }

//We also override string coercion for some extra versatility (not really necessary):

    // OVERRIDE STRING COERCION
    //   - for string concatenation e.g. "abc"+reversed("abc")
    x.toString = function() {
        if (typeof this._realized == 'undefined') {  // cached, to avoid recalculation
            this._realized = this.dir==1 ?
                this.original.slice(this.start,this.stop) : 
                this.original.slice(this.stop+1,this.start+1).split("").reverse().join("");

            this._realized = this._caseTransform.call(this._realized, this._realized);
        }
        return this._realized;
    }

//Now we reimplement the String API by doing some math:

    // String API:

    // Do some math to figure out which character we really want

    x.charAt = function(i) {
        return this.slice(i, i+1).toString();
    }
    x.charCodeAt = function(i) {
        return this.slice(i, i+1).toString().charCodeAt(0);
    }

// Slicing functions:

    x.slice = function(start,stop) {
        // lazy chaining version of https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/slice

        if (stop===undefined)
            stop = this.length;

        var relativeStart = start<0 ? this.length+start : start;
        var relativeStop = stop<0 ? this.length+stop : stop;

        if (relativeStart >= this.length)
            relativeStart = this.length;
        if (relativeStart < 0)
            relativeStart = 0;

        if (relativeStop > this.length)
            relativeStop = this.length;
        if (relativeStop < 0)
            relativeStop = 0;

        if (relativeStop < relativeStart)
            relativeStop = relativeStart;

        var s = new LazyString(this.original);
        s.length = relativeStop - relativeStart;
        s.start = this.start + this.dir*relativeStart;
        s.stop = s.start + this.dir*s.length;
        s.dir = this.dir;

        //console.log([this.start,this.stop,this.dir,this.length], [s.start,s.stop,s.dir,s.length])

        s._caseTransform = this._caseTransform;
        return s;
    }
    x.substring = function() {
        // ...
    }
    x.substr = function() {
        // ...
    }

//Miscellaneous functions:

    // Iterative search

    x.indexOf = function(value) {
        for(var i=0; i<this.length; i++)
            if (value==this.charAt(i))
                return i;
        return -1;
    }
    x.lastIndexOf = function() {
        for(var i=this.length-1; i>=0; i--)
            if (value==this.charAt(i))
                return i;
        return -1;
    }

    // The following functions are too complicated to reimplement easily.
    // Instead just realize the slice and do it the usual non-in-place way.

    x.match = function() {
        var s = this.toString();
        return s.apply(s, arguments);
    }
    x.replace = function() {
        var s = this.toString();
        return s.apply(s, arguments);
    }
    x.search = function() {
        var s = this.toString();
        return s.apply(s, arguments);
    }
    x.split = function() {
        var s = this.toString();
        return s.apply(s, arguments);
    }

// Case transforms:

    x.toLowerCase = function() {
        var s = new LazyString(this.original);
        s._caseTransform = ''.toLowerCase;

        s.start=this.start; s.stop=this.stop; s.dir=this.dir; s.length=this.length;

        return s;
    }
    x.toUpperCase = function() {
        var s = new LazyString(this.original);
        s._caseTransform = ''.toUpperCase;

        s.start=this.start; s.stop=this.stop; s.dir=this.dir; s.length=this.length;

        return s;
    }

})() // end anonymous scope

Демо-версия:

> r = S('abcABC')
LazyString
  original: "abcABC"
  __proto__: LazyString

> r.charAt(1);       // doesn't reverse string!!! (good if very long)
"B"

> r.toLowerCase()    // must reverse string, so does so
"cbacba"

> r.toUpperCase()    // string already reversed: no extra work
"CBACBA"

> r + '-demo-' + r   // natural coercion, string already reversed: no extra work
"CBAcba-demo-CBAcba"

Кикер - с помощью чистой математики выполняется следующее: посещение каждого персонажа только один раз и только при необходимости:

> 'demo: ' + S('0123456789abcdef').slice(3).reversed().slice(1,-1).toUpperCase()
"demo: EDCBA987654"

> S('0123456789ABCDEF').slice(3).reversed().slice(1,-1).toLowerCase().charAt(3)
"b"

Это дает значительную экономию при применении к очень большой строке, если вы берете только ее относительно небольшой кусочек.

Стоит ли это того (по сравнению с копией, как в большинстве языков программирования), во многом зависит от вашего варианта использования и от того, насколько эффективно вы переопределяете строковый API. Например, если все, что вам нужно, - это манипулировать индексом строки или взять небольшие slice с или substr с, это сэкономит вам пространство и время. Однако если вы планируете печатать большие перевернутые срезы или подстроки, экономия может быть небольшой, даже хуже, чем при создании полной копии. Ваша «перевернутая» строка также не будет иметь тип string, хотя вы могли бы подделать это с помощью прототипирования.

Приведенная выше демонстрационная реализация создает новый объект типа ReversedString. Он является прототипом и, следовательно, довольно эффективным, с почти минимальной работой и минимальными затратами пространства (определения прототипа являются общими). Это ленивая реализация, включающая отложенную нарезку. Всякий раз, когда вы выполняете такую ​​функцию, как .slice или .reversed, она выполняет индексную математику. Наконец, когда вы извлекаете данные (неявно вызывая .toString() или .charCodeAt(...) или что-то в этом роде), они применяют их «умным» образом, касаясь как можно меньшего количества данных.

Примечание: приведенный выше строковый API является примером и может быть реализован не полностью. Вы также можете использовать только 1-2 функции, которые вам нужны.

10 голосов
/ 10 февраля 2015

Во время интервью меня попросили перевернуть строку без использования переменных или нативных методов. Это моя любимая реализация:

function reverseString(str) {
    return str === '' ? '' : reverseString(str.slice(1)) + str[0];
}
8 голосов
/ 08 марта 2019

Есть много способов перевернуть строку в JavaScript. Я записываю три способа, которые предпочитаю.

Подход 1 с использованием обратной функции:

function reverse(str) {
  return str.split('').reverse().join('');
}

Подход 2, проходящий по символам:

function reverse(str) {
  let reversed = '';

  for (let character of str) {
    reversed = character + reversed;
  }

  return reversed;
}

Подход 3 с использованием функции уменьшения:

function reverse(str) {
  return str.split('').reduce((rev, char) => char + rev, '');
}

Надеюсь, это поможет:)

...