web-dev-qa-db-ja.com

UTF-8文字列の配列を並べ替える方法は?

私は現在、PHPでUTF-8でエンコードされた文字列を含む配列をソートする方法についての手がかりを持っていません。配列はLDAPサーバーから取得されるため、データベースを介した並べ替え(問題はありません)は解決策ではありません。以下は私のWindows開発マシンでは機能しません(これは少なくとも可能な解決策であると思いますが):

_$array=array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich');
$oldLocal=setlocale(LC_COLLATE, "0");
var_dump(setlocale(LC_COLLATE, 'German_Germany.65001'));
usort($array, 'strcoll');
var_dump(setlocale(LC_COLLATE, $oldLocal));
var_dump($array);
_

出力は次のとおりです。

_string(20) "German_Germany.65001"
string(1) "C"
array(6) {
  [0]=>
  string(6) "Birnen"
  [1]=>
  string(9) "Ungetiere"
  [2]=>
  string(6) "Äpfel"
  [3]=>
  string(5) "Apfel"
  [4]=>
  string(9) "Ungetüme"
  [5]=>
  string(11) "Österreich"
}
_

これはまったくナンセンスです。 setlocale()のコードページとして1252を使用すると、別の出力が得られますが、それでも明らかに間違った出力が得られます。

_string(19) "German_Germany.1252"
string(1) "C"
array(6) {
  [0]=>
  string(11) "Österreich"
  [1]=>
  string(6) "Äpfel"
  [2]=>
  string(5) "Apfel"
  [3]=>
  string(6) "Birnen"
  [4]=>
  string(9) "Ungetüme"
  [5]=>
  string(9) "Ungetiere"
}
_

ロケールを認識したUTF-8文字列で配列を並べ替える方法はありますか?

これはWindowsの問題ではPHPのようです。ロケールとして使用される_de_DE.utf8_の同じスニペットがLinuxマシンで機能するためです。それにもかかわらず、このWindowsのソリューション-特定の問題はニースです...

25
Stefan Gehrig

最終的に、この問題は、明らかなPHPバグが発見されたため、ΤΖΩΤΖΙΟΥによって提案されたように、再コード化された文字列(UTF-8→Windows-1252またはISO-8859-1)を使用せずに簡単な方法で解決することはできません。問題を要約するために、65001 Windows-UTF-8-codepageを使用する場合の問題がstrcoll()関数であることを明確に示す次のコードスニペットを作成しました。

function traceStrColl($a, $b) {
    $outValue=strcoll($a, $b);
    echo "$a $b $outValue\r\n";
    return $outValue;
}

$locale=(defined('PHP_OS') && stristr(PHP_OS, 'win')) ? 'German_Germany.65001' : 'de_DE.utf8';

$string="ABCDEFGHIJKLMNOPQRSTUVWXYZÄÖÜabcdefghijklmnopqrstuvwxyzäöüß";
$array=array();
for ($i=0; $i<mb_strlen($string, 'UTF-8'); $i++) {
    $array[]=mb_substr($string, $i, 1, 'UTF-8');
}
$oldLocale=setlocale(LC_COLLATE, "0");
var_dump(setlocale(LC_COLLATE, $locale));
usort($array, 'traceStrColl');
setlocale(LC_COLLATE, $oldLocale);
var_dump($array);

結果は次のとおりです。

string(20) "German_Germany.65001"
a B 2147483647
[...]
array(59) {
  [0]=>
  string(1) "c"
  [1]=>
  string(1) "B"
  [2]=>
  string(1) "s"
  [3]=>
  string(1) "C"
  [4]=>
  string(1) "k"
  [5]=>
  string(1) "D"
  [6]=>
  string(2) "ä"
  [7]=>
  string(1) "E"
  [8]=>
  string(1) "g"
  [...]

同じスニペットがLinuxマシンで問題なく動作し、次の出力が生成されます。

string(10) "de_DE.utf8"
a B -1
[...]
array(59) {
  [0]=>
  string(1) "a"
  [1]=>
  string(1) "A"
  [2]=>
  string(2) "ä"
  [3]=>
  string(2) "Ä"
  [4]=>
  string(1) "b"
  [5]=>
  string(1) "B"
  [6]=>
  string(1) "c"
  [7]=>
  string(1) "C"
  [...]

このスニペットは、Windows-1252(ISO-8859-1)でエンコードされた文字列を使用する場合にも機能します(もちろん、mb_ *エンコードとロケールを変更する必要があります)。

bugs.php.net にバグレポートを提出しました: バグ#46165 strcoll()はWindowsのUTF-8文字列では機能しません 。同じ問題が発生した場合は、バグレポートページのPHPチームにフィードバックを送信できます(他の2つの、おそらく関連するバグはbogus-このバグはbogus;-)ではないと思います。

あなた方全員に感謝します。

5
Stefan Gehrig
$a = array( 'Кръстев', 'Делян1', 'делян1', 'Делян2', 'делян3', 'кръстев' );
$col = new \Collator('bg_BG');
$col->asort( $a );
var_dump( $a );

プリント:

array
  2 => string 'делян1' (length=11)
  1 => string 'Делян1' (length=11)
  3 => string 'Делян2' (length=11)
  4 => string 'делян3' (length=11)
  5 => string 'кръстев' (length=14)
  0 => string 'Кръстев' (length=14)

Collatorクラスは PECL intl extension で定義されています。 PHP 5.3ソースで配布されますが、一部のビルドでは無効になる場合があります。たとえば、Debianではパッケージphp5-intlに含まれています。

Collator::compareusortに役立ちます。

28
Delian Krustev

この問題に関する更新:

この問題に関する議論により、PHPバグ strcoll() および/または setlocale() 、これは明らかにそうではありません。問題は、Windows CRT実装の制限です setlocale() (PHPs setlocale()はCRT呼び出しの単なる薄いラッパーです)。以下は MSDNページ "setlocale、_wsetlocale" の引用です。

使用可能な言語、国/地域コード、およびコードページのセットには、Win32 NLS APIでサポートされているものがすべて含まれます。ただし、UTF-7やUTF-7などの1文字あたり2バイト以上を必要とするコードページは除きます。 UTF-8。 UTF-7やUTF-8のようなコードページを提供すると、setlocaleは失敗し、NULLを返します。setlocaleでサポートされている言語および国/地域コードのセットは、Language and Country /にリストされています。リージョン文字列。

したがって、文字列がマルチバイトエンコードされている場合、WindowsではPHP内でロケール対応の文字列操作を使用することはできません。

8
Stefan Gehrig

これは非常に複雑です issue UTF-8でエンコードされたデータには任意のUnicode文字(つまり、ロケールごとに異なる照合を行う多くの8ビットエンコードの文字)を含めることができるためです。

おそらく、UTF-8データをUnicodeに変換し(PHP unicode関数、申し訳ありません))、それらを NFDまたはNFKD に正規化してから、コードでソートした場合ポイントは、あなたにとって意味のある照合を与えるかもしれません(つまり、「Ä」の前の「A」)。

私が提供したリンクを確認してください。

編集:入力データが明確であると述べているので(すべて「windows-1252」コードページにあると思います)、次の変換を行う必要があります:UTF-8→Unicode→Windows-1252、Windows-1252エンコードされたデータは、「CP1252」ロケールを選択してソートを実行します。

3
tzot

私はドイツ語の「ウムラウト」でも同じ問題に直面しています。いくつかの調査の後、これは私のために働きました:

$laender =array("Österreich", "Schweiz", "England", "France", "Ägypten");  
$laender = array_map("utf8_decode", $laender);  
setlocale(LC_ALL,"de_DE@euro", "de_DE", "deu_deu");  
sort($laender, SORT_LOCALE_STRING);  
$laender = array_map("utf8_encode", $laender);  
print_r($laender);

結果:

アレイ

[0] =>Ägypten
[1] =>イングランド
[2] =>フランス
[3] =>オーストリア
[4] => Schweiz

0

I この次のヘルパー関数が見つかりました 文字列のすべての文字をASCIIここで非常に役立つ文字に変換します。

_function _all_letters_to_ASCII($string) {
  return strtr(utf8_decode($string), 
    utf8_decode('ŠŒŽšœžŸ¥µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýÿ'),
    'SOZsozYYuAAAAAAACEEEEIIIIDNOOOOOOUUUUYsaaaaaaaceeeeiiiionoooooouuuuyy');
}
_

その後、単純なarray_multisort()が必要なものを提供します。

_$array = array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich');
$reference_array = $array;

foreach ($reference_array as $key => &$value) {
  $value = _all_letters_to_ASCII($value);
}
var_dump($reference_array);

array_multisort($reference_array, $array);
var_dump($array);
_

もちろん、ヘルパー関数をより高度なニーズに合わせることができます。しかし、今のところ、それはかなり良さそうです。

_array(6) {
  [0]=> string(6) "Birnen"
  [1]=> string(5) "Apfel"
  [2]=> string(8) "Ungetume"
  [3]=> string(5) "Apfel"
  [4]=> string(9) "Ungetiere"
  [5]=> string(10) "Osterreich"
}

array(6) {
  [0]=> string(5) "Apfel"
  [1]=> string(6) "Äpfel"
  [2]=> string(6) "Birnen"
  [3]=> string(11) "Österreich"
  [4]=> string(9) "Ungetiere"
  [5]=> string(9) "Ungetüme"
}
_
0
leymannx