26

私は現在、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"
}

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

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 文字列のロケールを認識して配列をソートする方法はありますか?

ロケールとして使用される同じスニペットがde_DE.utf8Linux マシンで動作するため、これは Windows 上の PHP の問題のように思われることに注意してください。それにもかかわらず、この Windows 固有の問題の解決策は素晴らしいでしょう...

4

8 に答える 8

31
$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::compareに役立ちusortます。

于 2012-03-06T00:30:05.273 に答える
8

この問題に関する更新:

この問題に関する議論により、strcoll()および/またはの PHP バグを発見できた可能性があることが明らかになりsetlocale()ましたが、明らかにそうではありません。問題はむしろ Windows CRT 実装の制限ですsetlocale()(PHPsetlocale()は CRT 呼び出しのシン ラッパーにすぎません)。以下は、MSDN ページ "setlocale, _wsetlocale"の引用です。

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

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

于 2008-12-08T09:54:40.097 に答える
6

Huppie によって発見された明らかな PHP のバグにより、ΤΖΩΤΖΙΟΥ によって提案されているように、最終的にこの問題は再コード化された文字列 (UTF-8 → Windows-1252 または ISO-8859-1) を使用せずに簡単な方法で解決することはできません。問題を要約するために、65001 Windows-UTF-8 コードページを使用する場合の問題が 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にバグ レポートを提出しました。同じ問題が発生した場合は、バグレポート ページで PHP チームにフィードバックを送信できます (他に 2 つのおそらく関連するバグが偽物として分類されています - 私はこのバグが偽物だとは思いません;-)。

皆さんのお陰で。

于 2008-09-24T07:42:28.867 に答える
4

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

おそらく、UTF-8データをUnicodeに変換し(PHPユニコード関数に精通していないため、申し訳ありません)、それらをNFDまたはNFKDに正規化してから、コードポイントで並べ替えると、意味のある照合が行われる可能性があります(つまり、「A」 「Ä」の前)。

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

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

于 2008-09-23T11:12:10.290 に答える
1

ここでは、文字列のすべての文字を 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"
}
于 2015-09-25T09:46:04.630 に答える
0

コードページ1252で例を使用すると、Windows開発マシンで完全に正常に機能しました。

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

...をちょきちょきと切る...

これはPHP5.2.6でした。ところで。


上記の例は間違っています。UTF-8の代わりにASCIIエンコーディングを使用しています。私はstrcoll()呼び出しをトレースし、見つけたものを調べました。

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

$array=array('Birnen', 'Äpfel', 'Ungetüme', 'Apfel', 'Ungetiere', 'Österreich');
setlocale(LC_COLLATE, 'German_Germany.65001');
usort($array, 'traceStrColl');
print_r($array);

与える:

UngetümeÄpfel2147483647
UngetümeBirnen2147483647
UngetümeApfel2147483647
UngetümeUngetiere2147483647
オーストリアUngetüme2147483647
ÄpfelUngetiere2147483647
ÄpfelBirnen2147483647
ApfelÄpfel2147483647
Ungetiere Birnen 2147483647

偽物であるとフラグが立てられたバグレポートをいくつか見つけました...あなたが持っている最善の策は、バグレポートを提出することですが...

于 2008-09-23T11:21:18.427 に答える
-1

照合は文字セットと一致する必要があります。データは UTF-8 でエンコードされているため、UTF-8 照合を使用する必要があります。プラットフォームによって名前が異なる可能性がありますが、おそらくde_DE.utf8.

UNIX システムでは、次のコマンドを使用して、現在インストールされているロケールのリストを取得できます。

locale -a
于 2008-09-23T14:40:05.080 に答える