001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.plugin.table;
017
018import java.util.Arrays;
019
020import org.opengion.fukurou.util.StringUtil;
021import org.opengion.hayabusa.db.AbstractTableFilter;
022import org.opengion.hayabusa.db.DBTableModel;
023
024/**
025 * TableFilter_NORMALIZE は、TableFilter インターフェースを継承した、DBTableModel 処理用の
026 * 実装クラスです。
027 * 指定の数値データ(横並び)を、最小値「0」~最大値「1」に正規化(スケーリング)します。
028 * 値 = (値 - 最小値) / (最大値 - 最小値) を計算します。
029 *
030 * 正規化するカラムは、VAL_CLMS で指定します。これは、数値カラムで、CSV形式で指定します。
031 *
032 * カラムの値が null または、ゼロ文字列の場合は、計算から除外します。
033 *
034 * パラメータは、tableFilterタグの keys, vals にそれぞれ記述するか、BODY 部にCSS形式で記述します。
035 * 【パラメータ】
036 *  {
037 *       VAL_CLMS   : 正規化(スケーリング)を行うカラム列
038 *       FORMAT     : 数値のフォーマット (初期値:%.3f ・・・ 小数第3位以下を、四捨五入する)
039 *  }
040 *
041 * @og.formSample
042 * ●形式:
043 *      ① <og:tableFilter classId="NORMALIZE" selectedAll="true"
044 *                       keys="VAL_CLMS"
045 *                       vals='"USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY"' />
046 *
047 *      ② <og:tableFilter classId="NORMALIZE"  selectedAll="true" >
048 *               {
049 *                   VAL_CLMS  : USED_TIME,CNT_ACCESS,CNT_READ,TM_TOTAL_QUERY ;
050 *               }
051 *         </og:tableFilter>
052 *
053 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
054 *
055 * @version  8.4 (2023/03/10)
056 * @author   Kazuhiko Hasegawa
057 * @since    JDK1.11,
058 */
059public class TableFilter_NORMALIZE extends AbstractTableFilter {
060        /** このプログラムのVERSION文字列を設定します。 {@value} */
061        private static final String VERSION = "8.4.1.0 (2023/02/10)" ;
062
063        private DBTableModel    table   ;
064
065        /**
066         * デフォルトコンストラクター
067         */
068        public TableFilter_NORMALIZE() {
069                super();
070                initSet( "VAL_CLMS"     , "正規化(スケーリング)を行うカラム列" );
071                initSet( "FORMAT"       , "数値のフォーマット (初期値:%.3f ・・・ 小数代3位以下を、四捨五入する)"    );
072        }
073
074        /**
075         * DBTableModel処理を実行します。
076         *
077         * @og.rev 8.4.1.0 (2023/02/10) 新規作成
078         *
079         * @return 処理結果のDBTableModel
080         */
081        public DBTableModel execute() {
082                table = getDBTableModel();
083
084                final int ROW_CNT = table.getRowCount();
085                if( ROW_CNT == 0 ) { return table; }                    // 0 は、処理なし
086
087                final String[] valClms = StringUtil.csv2Array( getValue( "VAL_CLMS" ) );
088                // 最大、最小判定を行うカラム番号を求めます。
089                final int[] clmNos = getTableColumnNo( valClms );
090
091                final String    fmt             = getValue( "FORMAT" );
092                final String    format  = fmt == null || fmt.isEmpty() ? "%.3f" : fmt ;         // 初期値が、"%.3f"
093
094                final Normalization normal = new Normalization( clmNos , format );
095
096                // 1回目は最小値、最大値を求める処理
097                for( int row=0; row<ROW_CNT; row++ ) {
098                        final String[] vals = table.getValues( row );
099                        normal.check( vals );
100                }
101
102                // 2回目は(値 - 最小値) / (最大値 - 最小値) を計算します。
103                for( int row=0; row<ROW_CNT; row++ ) {
104                        final String[] vals = table.getValues( row );
105                        normal.chenge( vals );                          // vals の配列の中身を直接書き換えています。
106                }
107
108                return table;
109        }
110
111        /**
112         * 実際の間引き処理を行うクラス。
113         *
114         * 最大、最少を求めて、正規化処理部分だけを分離しました。
115         *
116         * @og.rev 8.4.1.0 (2023/02/10) 新規作成
117         */
118        private static final class Normalization {
119                private final int[]                     clmNos ;                // 数値カラムのカラム番号
120                private final int                       clmSize;                // 数値カラムの個数
121                private final double[]          minVals;                // 最小値
122                private final double[]          maxVals;                // 最大値
123                private final String            format;                 // 文字列化する時の数値フォーマット
124
125                /**
126                 * 正規化処理を行うクラスのコンストラクター
127                 *
128                 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
129                 *
130                 * @param       clmNos  最大最小処理を行うカラム番号配列
131                 * @param       format  初期値が、"%.3f"
132                 */
133                public Normalization( final int[] clmNos , final String format ) {
134                        this.clmNos     = clmNos;
135                        this.clmSize    = clmNos.length;                // 値部分のみの配列番号
136                        this.format             = format;
137
138                        minVals = new double[clmSize];
139                        maxVals = new double[clmSize];
140
141                        Arrays.fill( minVals,Double.POSITIVE_INFINITY );        // 最小値の初期値は、正の無限大
142                        Arrays.fill( maxVals,Double.NEGATIVE_INFINITY );        // 最大値の初期値は、負の無限大
143                }
144
145                /**
146                 * 正規化処理のための最大最小のデータを求めます。
147                 *
148                 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
149                 *
150                 * @param       vals    チェックする行配列データ
151                 */
152                public void check( final String[] vals ) {
153                        for( int i=0; i<clmSize; i++ ) {
154                                final String val = vals[clmNos[i]];
155                                if( !StringUtil.isNull( val ) ) {                                               // データが null の場合は、置換が発生しません。
156                                        final double dval = Double.parseDouble( val );
157                                        if( minVals[i] > dval ) { minVals[i] = dval; }          // 最小値の入れ替え
158                                        if( maxVals[i] < dval ) { maxVals[i] = dval; }          // 最大値の入れ替え
159                                }
160                        }
161                }
162
163                /**
164                 * 正規化処理のための最大最小のデータ変換を行います。
165                 *
166                 * @og.rev 8.4.1.0 (2023/02/10) 新規作成
167                 *
168                 * @param       vals    変換する行配列データ
169                 */
170                public void chenge( final String[] vals ) {
171                        for( int i=0; i<clmSize; i++ ) {
172                                final String val = vals[clmNos[i]];
173                                if( !StringUtil.isNull( val ) ) {                                               // データが null の場合は、置換が発生しません。
174                                        final double dval = Double.parseDouble( val );
175                                        vals[clmNos[i]] = String.format( format,( dval - minVals[i] ) / (maxVals[i] - minVals[i]) );    // 正規化(Normalization)
176                                }
177                        }
178                }
179        }
180}