LE Blog

Инженер с поэтической душой

16.05.2009 firtree_right Эволюция алгоритма замены в строке ActionScript

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

Задача

Один из участников попросил составить выражение для удаления из текста ссылок с определенным текстом внутри целиком. Например, в выражении:

var str:String = '<a href="somelink">_some text_</a> ';
str += 'More text! ';
str += '<a href="anotherlink">**remove me**</a> ';
str += '<a href="yetanotherlink"><s>another text</s></a>';

Нужно удалить целиком ссылку, содержащую фразу remove me.

Понятно, что первое приходящее в голову выражение /<a.+?remove me.*?</a>/ захватит две первые ссылки. И «жадность» не поможет, т.к. поиск осуществляется по порядку, и, найдя первый <a, выражение не остановится до самого remove me.

Решение номер один

Поскольку к концу недели отладки и пересмотра одного и того же кода голова моя не была готова что-то изобретать, я последовал пути, предлагавшемуся одним из ответивших, слегка его доделав:

var re0:RegExp = /<a[^>]+>[^a]*remove me.*?<\/a>/g;

trace(str.replace(re0, "!removed!"));

Недостаток его очевиден. Хотя для приведенного примера он работает, но всё-таки, может и отказать, если встретит a между > и remove me. Например:

str += '<a href="anotherlink">eh! ah? **remove me**</a> ';

Решение номер два (рекурсивное)

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

Оно использовало возможность подсовывать функцию в качестве аргумента. Вот оно:

var re1:RegExp = /<a(.+?remove me.*?<\/a>)/g;

var replacer1:Function = function():String {
  var s:String = arguments[1].toString();
  if (s.indexOf("<a") > 0) {
    return "<a" + s.replace(re1, replacer1);
  } else {
    return "!removed!";
  }
}

trace(str.replace(re1, replacer1));

Здесь речь идёт о том, чтобы в группе (см. скобки), следующей после <a проверять наличие ещё одного <a. И в случае его наличия запускать ту же процедуру замены, но уже на группе.

Довольный собой, я запостил своё решение в ruFlash и поехал домой. По дороге домой мозг окончательно расслабился, и я смог увидеть задачу в отрыве от способа думать, который я выбрал изначально. И мне пришло

Решение номер три

Зачем городить рекурсию, когда можно просто перебирать все ссылки и заменять (удалять) только те, что нужно?

var re2:RegExp = /<a[^>]+>(.+?)<\/a>/g;

var replacer2:Function = function():String {
   var s:String = arguments[1].toString();
  if (s.indexOf("remove me") > 0) {
    return "!removed!";
  } else {
    return arguments[0];
  }
}

trace(str.replace(re2, replacer2));

Это ли не чудесно?

Выводы

  1. Решайте задачи.
  2. Решив (или не решив), записывайте то, что получилось, покажите кому-нибудь. Это позволит выкинуть решение из головы.
  3. Если есть решение лучше, то оно придет на освободившееся место.