Функция для вычисления длины строки в Google Таблицах, когда строка содержит определенные символы UTF-8

Вопрос или проблема

Эта функция возвращает 6, но ожидается, что она вернет 4:

=LEN("🥎A🥎B")

Какую функцию я должен использовать, чтобы получить “реальное количество символов”, ожидая, что один символ в кодировке UTF-8 считается за 1 символ?

Я не смог найти такой встроенной функции, но результат LEN() “правильный” в том смысле, что он спрашивает ваш браузер, какая длина строки, и возвращает 6, потому что ответ браузера на "🥎A🥎B".length также 6.

Хотя Google Таблицы работают с Unicode, они фактически не работают с UTF-8 – они работают со строками UTF-16, которые предоставляет JavaScript, и поскольку 🥎 (U+1F94E) имеет кодовую точку выше U+FFFF, он не помещается в один 16-битный кодовый элемент, а должен храниться как суррогатная пара (U+D83E U+DD4E). Поэтому строка из четырех символов хранится не как четыре руны UTF-8, а как шесть 16-битных кодовых единиц UTF-16:

U+D83E U+DD4E U+0041 U+D83E U+DD4E U+0042

К сожалению, 16-битные кодовые единицы именно те, которые учитываются методом String.length в JavaScript (что является неприятным особенностью, которую несколько “ранних адоптеров Unicode” языков программирования сохранили для обратной совместимости при переходе мира от строго 16-битного UCS-2 к UTF-16). Если вы выполните "🥎A🥎B".length в любой консоли JS, он также вернет 6.

(Существует другая функция LENB(), которая действительно кодирует строку в UTF-8 и возвращает ее длину в байтах, но это не очень помогает.)

Если вы можете использовать Apps Script, то можно написать пользовательскую функцию, которая это делает (хотя не с использованием примера из MDN, так как Apps Script работает на более старой версии JavaScript, которая не поддерживает строки как итерируемые). Например:

function LENU(str) {
  var length = 0, i, c;
  for (i = 0; i < str.length; i++) {
    c = str.charCodeAt(i);
    if (c < 0xDC00 || c > 0xDFFF) {
      /* Считаем все, кроме низких суррогатов, так как они всегда
       * (по крайней мере, в действительном UTF-16) следуют за высоким суррогатом, который
       * уже был учтен ранее. */
      length++;
    }
  }
  return length;
}

(Примечание: Проверяйте диапазон кодов символов и не вводите случайно 0xDB00, как я только что сделал.)

Это должно быть использовано как =LENU(...) из таблицы, хотя это немного медленнее из-за вызова удаленной службы Apps.

Как упомянуто в другом ответе, "🥎" представлен в UTF-16 как суррогатная пара и, следовательно, учитывается как два символа.

Тот ответ показал, как создать пользовательскую функцию LENU с помощью сценария приложений, которая правильно вычисляет длину с использованием пользовательской функции Google Apps Script.

Если вы не можете или не хотите использовать Google Apps Script, тот же подход можно использовать с помощью встроенных формул Google Таблиц:

=REDUCE(
  0,
  SPLIT(REGEXREPLACE("🥎A🥎B",," ")," "),
  LAMBDA(a,c,a+IF(OR(UNICODE(c)<HEX2DEC("DC00"),UNICODE(c)>HEX2DEC("DFFF")),1,0))
)

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

Это можно превратить в именованную функцию. Например, можно создать функцию с именем “LENU” с аргументом-заполнителем “value” и следующим определением формулы:

=IF(LEN(value)=0,0,REDUCE(
  0,
  SPLIT(REGEXREPLACE(value,," ")," "),
  LAMBDA(a,c,a+IF(OR(UNICODE(c)<HEX2DEC("DC00"),UNICODE(c)>HEX2DEC("DFFF")),1,0))
))

Эту именованную функцию можно использовать так же, как встроенную функцию:

=LENU("🥎A🥎B")

Ответ или решение

Как рассчитать длину строки с учетом UTF-8 символов в Google Sheets?

Если вы столкнулись с проблемой, когда функция LEN() в Google Sheets возвращает неверное количество символов для строки, содержащей определенные UTF-8 символы, такие как “🥎A🥎B”, это может быть связано с тем, что Google Sheets работает с UTF-16 кодировкой. Это приводит к тому, что некоторые символы (например, смайлы) представлены в виде пары суррогатных символов, что искажает результат.

Почему LEN() возвращает неверное значение?

При анализе строки 🥎A🥎B, функция LEN() возвращает 6, несмотря на то, что ожидается 4. Это объясняется тем, что символ "🥎" (U+1F94E) в кодировке UTF-16 представлен как две 16-битные единицы: U+D83E и U+DD4E. Таким образом, два символа "🥎" в строке фактически занимают 4 единицы UTF-16.

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

Использование Google Apps Script

Если вы хотите решить эту проблему с помощью Google Apps Script, вы можете создать собственную функцию под названием LENU, которая будет правильно обрабатывать символы. Ниже представлен код, который делает именно это:

function LENU(str) {
  var length = 0, i, c;
  for (i = 0; i < str.length; i++) {
    c = str.charCodeAt(i);
    if (c < 0xDC00 || c > 0xDFFF) {
      length++;
    }
  }
  return length;
}

Эта функция перебирает каждый символ строки и увеличивает счетчик, если символ не является низким суррогатным символом.

Альтернативное решение без использования Apps Script

Если вы не хотите использовать Google Apps Script, существует возможность решить эту задачу с помощью встроенных формул Google Sheets. Ниже представлена формула, которая может быть использована для подсчета реальной длины строки:

=REDUCE(
  0,
  SPLIT(REGEXREPLACE("🥎A🥎B",," ")," "),
  LAMBDA(a,c,a+IF(OR(UNICODE(c)<HEX2DEC("DC00"),UNICODE(c)>HEX2DEC("DFFF")),1,0))
)

Эта формула:

  1. Разбивает строку на отдельные символы.
  2. Подсчитывает только те символы, которые не относятся к нижним суррогатам.

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

Именованная функция для удобства

Вы также можете создать именованную функцию в Google Sheets, чтобы использовать ее более удобно. Например, вы можете определить функцию LENU следующим образом:

=IF(LEN(value)=0,0,REDUCE(
  0,
  SPLIT(REGEXREPLACE(value,," ")," "),
  LAMBDA(a,c,a+IF(OR(UNICODE(c)<HEX2DEC("DC00"),UNICODE(c)>HEX2DEC("DFFF")),1,0))
))

Теперь вы сможете использовать её как любую встроенную функцию:

=LENU("🥎A🥎B")

Заключение

Общеизвестно, что работа с строками, содержащими сложные символы, может вызывать путаницу, и использование стандартной функции LEN() в Google Sheets может не обеспечивать нужных результатов. HTML и Apps Script предоставляют гибкие возможности для создания точного и эффективного анализа строк. Выбор подходящего решения зависит от ваших потребностей и навыков.

Оцените материал
Добавить комментарий

Капча загружается...