Длина строки с умлаутами, поступающей из файловой системы.

Обновление Перефразировал мой вопрос:

У меня есть небольшой скрипт, который создает сводку для каждого каталога в данной папке:

def processDir(dir)
  title = "Project #{dir}"
<<EOF
#{title}
#{'-' * title.length}
...    


EOF
end

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

phone
-----

Propadeutikum
-------------

Propädeutikum
--------------
             ^ extra dash!

Поэтому я ищу способ вычислить точную длину моей строки.

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

1.9.3-p448 :012 > "Propädeutikum".length
 => 13 
1.9.3-p448 :013 > "Propädeutikum".length
 => 14 

person Besi    schedule 19.02.2014    source источник


Ответы (4)


Строка Ruby до сих пор поддерживает только символы ASCII. Таким образом, вы можете использовать гем - unicode для этого, когда у вас будут не-ascii символы . Посмотрите также здесь — width.

require "unicode"

s1 = "Propädeutikum"
s2 = "Propadeutikum"
Unicode::width(s1) # => 13
Unicode::width(s2) # => 13

Прочтите это сообщение Re: how to capitalize nonascii characters ?

Hi,

Да, пока используйте гем юникода. Операции со строками над символами, отличными от ASCII, — одна из тем предстоящего Ruby 2.2.

         matz.
person Arup Rakshit    schedule 19.02.2014
comment
@Besi Так и должно быть, подождите до 2.2, как сказал matz. :-) - person Arup Rakshit; 20.02.2014
comment
@ArupRakshit +1 за информацию, я думаю, вы должны баллотироваться на выборах SO :) вы много делаете для рубинового тега на SO, спасибо за ваши усилия - person bjhaid; 20.02.2014

В Юникоде некоторые символы, такие как ä, могут быть представлены двумя способами. Они могут состоять из одной кодовой точки, например, U+00E4 в случае ä, или могут состоять из «базового» символа, за которым сразу следует комбинированный символ, например a, за которым следует U+0308 (КОМБИНИРОВАНИЕ ДИАЭРЕЗИСА). В последнем случае комбинированный символ состоит из двух кодовых точек, а метод Ruby String#length возвращает только общее количество кодовых точек, поэтому вы можете получить разные значения для длин строк, которые кажутся одинаковыми.

s1 = "ä"        # single codepoint
s2 = "a"        # 'base' letter
s3 = "a\u0308"  # base letter + combining character

[s1, s2, s3].each do |s|
  puts "Letter:     #{s}"
  puts "Bytes:      #{s.bytes}"
  puts "Codepoints: #{s.codepoints}"
  puts "Length:     #{s.length}"
  puts
end

Выход:

Letter:     ä
Bytes:      [195, 164]
Codepoints: [228]
Length:     1

Letter:     a
Bytes:      [97]
Codepoints: [97]
Length:     1

Letter:     ä
Bytes:      [97, 204, 136]
Codepoints: [97, 776]
Length:     2

(bytes — это кодировка символов UTF-8. В UTF-8 некоторые символы кодируются как несколько байтов — это отдельная проблема, связанная с объединением символов.)

Сам Ruby (в настоящее время) не очень поддерживает решение таких проблем с юникодом, поэтому вам нужно использовать внешнюю библиотеку, такую ​​​​как UnicodeUtils. Идея length может стать довольно неясной, когда речь идет о разных языках (что считается «одним символом». Вы можете использовать метод display_width, который, вероятно, даст то, что вы хотите для латинских скриптов. Другая возможность - использовать нормализованная форма, обеспечивающая одинаковое представление всех символов либо все разложены на объединяющие символы или все (у которых они есть) с использованием одного символа:

require 'unicode_utils'

combined = "a\u0308"
single = "ä"

# nfc - normalized form composed - use a single code point if possible
puts UnicodeUtils.nfc(combined).length # => 1
puts UnicodeUtils.nfc(single).length   # => 1

# nfd - normalized form decomposed - always use combining characters
puts UnicodeUtils.nfd(combined).length # => 2
puts UnicodeUtils.nfd(single).length   # => 2
person matt    schedule 19.02.2014
comment
Хорошо, это очень интересно. На самом деле на моей клавиатуре я могу нажать клавишу ¨, а затем a, чтобы получить ä это. Я не уверен, что это приводит к другому персонажу, но это та же концепция. - person Besi; 20.02.2014
comment
@Besi Вы правы, это та же идея, но отдельно от реальных используемых символов. На моей машине (Mac) я могу нажать alt + u, а затем a, чтобы получить ä, но результатом будет версия с одной кодовой точкой. - person matt; 20.02.2014
comment
@Besi глядя на ваш отредактированный вопрос, display_width (или просто width из драгоценного камня unicode), вероятно, то, что вам нужно в этом случае. - person matt; 20.02.2014

Аналогичен Мэтту, но может быть немного более эффективным.

"Propädeutikum".each_char.size
# => 13

t = Time.now
500000.times{
"Propädeutikum".each_char.size
}
puts Time.now - t
# => 0.364056992

t = Time.now
500000.times{
"Propädeutikum".chars.count
}
puts Time.now - t
# => 1.462392185
person sawa    schedule 19.02.2014
comment
Та же проблема, что и с версией Мэтта. - person Besi; 20.02.2014
comment
@Besi Это твоя проблема, а не наша. - person sawa; 20.02.2014
comment
ОК, +1 к статистике вашего времени (хотя в моем случае это не проблема, но полезно знать). Я получаю строки из имен файлов, так что, вероятно, это связано с файловой системой. И, конечно, это моя проблема, поэтому я тот, кто задает вопрос :-) - person Besi; 20.02.2014

Может быть, у вас есть проблема с эквивалентностью Unicode и составными символами?

См. следующий пример. Оба текста выглядят одинаково, но кодируются по-разному:

#encoding: utf-8
text = "Myl\u00E8ne.png" #"Mylène.png"
text2 = "Myle\u0300ne.png" #"Mylène.png"

puts text   #Mylène.png
puts text2  #Mylène.png

puts text.size   #10
puts text2.size  #11

puts text.chars.count #10
puts text2.chars.count #11

Еще немного подробностей в мом ответе для кодирования странных символов.

Вы можете это проверить, если сравните кодовые точки ваших текстов с text.codepoints.to_a. В моем примере я получаю:

p text.codepoints.to_a   #[77, 121, 108, 232, 110, 101, 46, 112, 110, 103]
p text2.codepoints.to_a  #[77, 121, 108, 101, 768, 110, 101, 46, 112, 110, 103]
person knut    schedule 19.02.2014
comment
Думаю ты прав. Unicode::width(string) решил проблему сейчас. - person Besi; 20.02.2014