challenge エレベータの制御(基本編)

エレベータを制御して、5階建てのビルの各階にいる人たちを、
「効率よく」1階のエントランスまで運んでください。

作成時の条件は次の通りです。
  1. 各階において、人が残っていることはわかるが、
     あと何人残っているかまではわからないものとする。
     # 人が残っているところは常に呼び出しのボタンが押された状態を
     # 仮定して問題ありません。
  2. 稼働しているエレベータは1機のみとする
  3. 降車するたびに、変化がわかるような出力をしてください。
  4. すべての人を運搬終わったら、最後に次の情報を出力してください。
     - 運搬を始めてからの経過時間(ラウンド数)
     - 一番長く放置されていた階の待ち時間(ラウンド数)
     - エレベータの移動距離合計数

エレベータの機能は次の通りです。
  - 搭載人数は最大3人までとする。
  - 移動には、1つの階につき「2」ラウンドかかる。
  - 人の乗降には、1回につき「5」ラウンドかかる。

※ 「ラウンド」は包括された時間だと思ってください。
   単純に「秒」と読み替えてもよいです


各階の人数は次の通りです。
5階  7人
4階 11人
3階  3人
2階  7人
1階  0人

冒頭では、「効率よく」なんて曖昧な表現をしようしましたが、
何について効率よくしたのか、設計についての見解を
コメントしていただけるとうれしいです。

たとえば、
下記のサンプル出力では、各階の待ち時間を最小とすることで
効率よいとしました。(利用者の視点)
ぱっと思いつくところでは、ほかにも2-3種類あるとおもいます。

さて、あなたならどう書く?(笑
// 自分で作ったやつは250L位になってしまいましたorz

INIT --------------------
 [5]th Floor: [ 7] / last_round:[   0]
 [4]th Floor: [11] / last_round:[   0]
 [3]th Floor: [ 3] / last_round:[   0]
 [2]th Floor: [ 7] / last_round:[   0]
 [1]th Floor: [ 0] / last_round:[   0] 

IN(5) [7] → [4] OUT
IN(4) [11] → [8] OUT
IN(3) [3] → [0] OUT
IN(2) [7] → [4] OUT
IN(5) [4] → [1] OUT
IN(4) [8] → [5] OUT
IN(2) [4] → [1] OUT
IN(5) [1] → [0] IN(4) [5] → [3] OUT
IN(2) [1] → [0] OUT
IN(4) [3] → [0] OUT

END --------------------
 [5]th Floor: [ 0] / last_round:[ 174]
 [4]th Floor: [ 0] / last_round:[ 229]
 [3]th Floor: [ 0] / last_round:[  58]
 [2]th Floor: [ 0] / last_round:[ 213]
 [1]th Floor: [ 0] / last_round:[   0] 
       経過時間:[257]
   最大待ち時間:[100]
 移動距離合計数:[52]
長いからとサンプルコードはしょってしまったのでいろいろと不都合がでてしまいました。

last_round は僕のデバックの都合上のものなので出力に関しては気にしないでください。
ごめんなさい。
お題を間違えてCのまま投稿してしまったので
言語設定せずに投稿します。

gcc -Wall -std=c99 doukaku225.c

# ところで、これ投稿したの一昨日くらいなのですが、
  公開のカウントって下書きした段階ではじまってる?
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
#include <stdio.h>
#include <stdbool.h>
#include <assert.h>

#define CARRYING_LIMIT      (3)
#define CARRYING_MOVE_COST  (5)
#define ELEVATOR_MOVE_COST  (2)

typedef struct tagFloor
{
    bool called;            /*  */
    int  floor_Nth;         /*  */
    int  carry_remain;      /*  */
    long last_round;        /*  */
    struct tagFloor *down;  /*  */
    struct tagFloor *up;    /*  */
} FLOOR;

typedef struct tagElevatorBox
{
    int     carrying;       /*  */
    int     cool_time;      /*  */
    FLOOR * now_floor;      /*  */
    FLOOR * target_floor;   /*  */
} ELEVATOR;

static long round          = 0;
static long round_wait_max = 0;
static long move_total     = 0;

/* エレベータ内が搭載可能人数を返す */
int elevator_getCarryAvailed( const ELEVATOR *elevator )
{
    return (CARRYING_LIMIT - elevator->carrying);
}

/* 目的の階に到着していれば真 */
bool elevator_isTargetFloor( const ELEVATOR *elevator)
{
    if( elevator->now_floor == elevator->target_floor )
    {
        return true;
    }
    return false;
}

/* エレベータに乗車させる */
int elevator_carrying_in( ELEVATOR *elevator )
{
    int avail = 0;
    const int oldremain = elevator->now_floor->carry_remain;
    avail = elevator_getCarryAvailed(elevator);
    if(  avail > 0 )
    {
        if( elevator->now_floor->carry_remain > avail )
        {
            elevator->now_floor->carry_remain -= avail;
            elevator->carrying += avail;
        }
        else
        {
            elevator->carrying += elevator->now_floor->carry_remain;
            elevator->now_floor->called = false;
            elevator->now_floor->carry_remain = 0;
        }
        elevator->cool_time = CARRYING_MOVE_COST;
        printf("IN(%d) [%d] → [%d] "
                ,elevator->now_floor->floor_Nth,oldremain, elevator->now_floor->carry_remain);
    }
    return 0;
}

/* エレベータから降車させる */
int elevator_carrying_out( ELEVATOR *elevator )
{
    if( elevator->carrying > 0 )
    {
        elevator->carrying = 0;
        elevator->cool_time = CARRYING_MOVE_COST;
        printf("OUT ");
    }
    return 0;
}

/* エレベータを移動する */
int elevator_update(ELEVATOR *elevator)
{
    if( elevator->cool_time > 0 )
    {
        elevator->cool_time --;
        return 1;
    }
    if( elevator_isTargetFloor(elevator) == true )
    {
        return 0;
    }
    else
    {
        /* エレベータの移動 */
        move_total ++;
        assert( elevator->now_floor->floor_Nth != elevator->target_floor->floor_Nth );
        if( elevator->now_floor->floor_Nth < elevator->target_floor->floor_Nth )
        {
            elevator->now_floor = elevator->now_floor->up;
         }
        else
        {
            elevator->now_floor = elevator->now_floor->down;
        }
        elevator->cool_time = ELEVATOR_MOVE_COST;
    }
    return 0;
}

/* 次に停止するべき階を選択する */
int floor_select( FLOOR **next, const FLOOR *floor, const long round )
{
    long wait_max;
    FLOOR *cursor;

    *next = NULL; /* 最上階 */
    wait_max = -1;
    cursor = (FLOOR *)floor;
    while( true )
    {
        if( cursor->called == true && (round - cursor->last_round) > wait_max )
        {
            *next = cursor;
            wait_max = (round - cursor->last_round);
            if( (round - cursor->last_round) > round_wait_max )
            {
                round_wait_max = (round - cursor->last_round);
            }
        }
        if( cursor == cursor->down )
        {
            break;
        }
        cursor = cursor->down;
    }

    if( *next == NULL )
    {
        /* 1階ならばすべて運び終わっている */
        *next = cursor;
        return 1;
    }
    (*next)->last_round = round;
    return 0;
}

/*  */
int floor_print(const ELEVATOR* elevator, const FLOOR *floor)
{
    printf("--------------------\n");
    while( true )
    {
        printf(" [%d]th Floor: [%2d] / last_round:[%4ld] %c %c\n"
            , floor->floor_Nth, floor->carry_remain,floor->last_round
            , (floor==elevator->target_floor)?'=':' '
            , (floor==elevator->now_floor)?'*':' ');
        if( floor == floor->down )
        {
            break;
        }
        floor = floor->down;
    }
    return 0;
}

/*  */
int main(int argc, char *argv[])
{
    static FLOOR floor[] = 
            {
                {false, 1,  0, 0, &floor[0], &floor[1]}
                ,{true, 2,  7, 0, &floor[0], &floor[2]}
                ,{true, 3,  3, 0, &floor[1], &floor[3]}
                ,{true, 4, 11, 0, &floor[2], &floor[4]}
                ,{true, 5,  7, 0, &floor[3], &floor[4]}
            };
    static FLOOR *top_floor = &floor[4];
    static ELEVATOR schindler = { 0, 0, &floor[0], &floor[0] };

    FLOOR *next = NULL;

    /* 初期状態の出力*/
    printf("INIT ");
    floor_print(&schindler, top_floor);

    /* 運搬開始 */
    round = 0;
    round_wait_max = 0;
    move_total = 0;
    while( true )
    {
        round ++;
        elevator_update(&schindler);
        if( schindler.cool_time > 0 )
        {
            continue;
        }
        if( elevator_isTargetFloor(&schindler) == true )
        {
            /* 目的の階に到着 */
            if( schindler.now_floor->floor_Nth == 1 )
            {
                elevator_carrying_out(&schindler);
                /* 次の停止回を最上階から検索する */
                floor_select( &next, top_floor, round );
                if( next->floor_Nth == 1 )
                {
                    /* もうない */
                    break;
                }
                //floor_print(&schindler, top_floor);
                printf("\n");
             }
            else
            {
                elevator_carrying_in(&schindler);
                if( elevator_getCarryAvailed( &schindler ) > 0 )
                {
                    /* まだ搭載できるので移動のついでに追加搭載する */
                    floor_select( &next, schindler.now_floor, round );
                }
                else
                {
                    /* もう限界なので1階を目的地にする */
                    next = &floor[0];
                }
                //floor_print(&schindler, top_floor);
            } /* end of if(1階か) */
            /* 次の停止階を設定する */
            schindler.target_floor = next;
        } /* end of if(到着しているか) */
    } /* end of while( 残っているか )  */

    printf("\n\nEND ");
    floor_print(&schindler, top_floor);
    printf("%15s:[%ld]\n", "経過時間", round);
    printf("%15s:[%ld]\n", "最大待ち時間", round_wait_max);
    printf("%15s:[%ld]\n", "移動距離合計数", move_total);

    return 0;
}
/* EOF */

Posted feedbacks - Nested

Flatten Hidden
エレベータの初期位置は1Fと仮定するのでしょうか? あるいはランダムな位置なのでしょうか?

今回、エレベータの移動距離を出力するので 1Fスタートにしていただければと思います。

どっかで似た問題を見たなぁと思ってちょっと記憶をたどって探してみたら、一応見つかった。
東大の創造情報学の2008年2月の入試問題がよく似ている。
http://www.i.u-tokyo.ac.jp/edu/course/ci/admission.shtml
まさか東大の入試問題になっているとは思いませんでしたが、
プログラムの勉強をする上でわりとポピュラーなものです。

ほかにポピュラーなものとして
自動販売機の釣り銭勘定(基本ロジックの勉強によい)、
電卓(スタック操作の勉強にいい)等があるのですが、
エレベータの制御が抜けているのに気がついたので投稿しました。
ちなみに、これは本来並列処理や計算量の見積もり訓練の勉強にしようします。
GUIで作ると結構楽しいんですけどね^^;;

質問です。 END時のlast_roundの数値は何を表示していますか? 最大待ち時間 = 100 ということなので、各階の待ち時間ではないですよね。

ごめんなさい。これは僕のデバッグ用出力です。

最後に更新(乗車)したときのroundです。

あまり気にしないでください。
「下記のサンプル出力」の経過時間が手計算と合わなのですが、間違っていませんか?

[4]th Floor: [ 0] / last_round:[ 229]
を「4Fに最後に訪れたラウンドが229」と解釈すると、そのあとは、乗せる(5)、3F下降(6)、下ろす(5)だけのはずなのに総経過時間(257)まであと28もかかっています。

1Fからスタートして、4Fにいる3人を1Fに下ろすラウンド数は
2*(4-1) + 5 + 2*(4-1) + 5 = 22
であっていますか?
はい、バグってました。
# しかもそのまま投稿しちゃってる^^;

予定では、目的地についたとき、時間を更新するつもりだったのですが
ロジック的に、降車した段階で次の目的となる階を決定した段階でlast_roundを設定していました。

つまり、1Fから4Fまでの移動時間(6ラウンド)が空白になります。

論理的なもの、僕が想定しているものはsawat氏の考えているとおりです。
失礼しました。

Squeak Smalltalk で。

あまりよい方法を思いつかなかったので、よくあるエレベータのオーソドックスな動きのシミュレートだけしてみました。

待っている人がいる階では常に「↓」ボタンが押されていると仮定します。つまり、さらに上の階に呼ばれてそちらに向かっているエレベータを止めることはできません。

したがって、1階で人を降ろして空になった(あるいは初期状態で1階待機の)ボックスは、呼んでいる階のうち最上階に直行します。

停止階で人を乗せたボックスは1階に向かう途中、余裕があれば次の呼ばれている階に停まります。最大搭載人数に達した場合は途中で停止せず、1階に直行します。

老婆心ながら、このオーソドックスな動きだけをまずは基本編として出題されたほうが良かったのではないかな⋯と思いました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
| 各階待ち人数 階数 最大搭乗人数 ボックス内人数 ボックス停止階 
  ラウンド数 移動数 完了ラウンド数 放置ラウンド数 ボックス移動 状態出力 |

各階待ち人数 := #(0 7 3 11 7).
階数 := 各階待ち人数 size.
最大搭乗人数 := 3.
ボックス内人数 := 0.
ボックス停止階 := 1.
ラウンド数 := 0.
移動数 := 0.
完了ラウンド数 := Array new: 階数 withAll: 0.
放置ラウンド数 := Array new: 階数 withAll: 0.
放置ラウンド数 at: 1 put: -1.
World findATranscript: nil.

ボックス移動 := [:行き先階 |
    ラウンド数 := ラウンド数 + ((行き先階 - ボックス停止階) abs * 2) + 5.
    移動数 := 移動数 + (行き先階 - ボックス停止階) abs.
    (放置ラウンド数 at: 行き先階) = 0
        ifTrue: [放置ラウンド数 at: 行き先階 put: ラウンド数].
    行き先階 = 1 ifTrue: [ボックス停止階 := 1. ボックス内人数 := 0] ifFalse: [
        | 待ち人数 搭乗者数 残り人数 |
        待ち人数 := 各階待ち人数 at: 行き先階.
        搭乗者数 := 待ち人数 min: 最大搭乗人数 - ボックス内人数.
        残り人数 := 待ち人数 - 搭乗者数.
        ボックス停止階 = 1 ifTrue: [Transcript cr].
        ボックス停止階 := 行き先階.
        Transcript
            show: ボックス停止階; show: '階: '; show: 待ち人数; show: ' -> ';
            show: (各階待ち人数 at: ボックス停止階 put: 残り人数); show: '. '.
        "Transcript
            show: ' (ラウンド: ', ラウンド数 printString, ', 移動数: ', 移動数 printString, ')'."
        ボックス内人数 := ボックス内人数 + 搭乗者数.
        残り人数 = 0 ifTrue: [完了ラウンド数 at: ボックス停止階 put: ラウンド数]]].

状態出力 := [
    Transcript cr.
    5 to: 1 by: -1 do: [:n |
        Transcript cr; show: n; show: '階; ';
            show: (各階待ち人数 at: n);
            show: ' 人 / 完了ラウンド: ';
            show: (完了ラウンド数 at: n)].
    Transcript cr].

状態出力 value.

[   ボックス内人数 = 最大搭乗人数 ifTrue: [ボックス移動 value: 1] ifFalse: [
        | 呼び出し階 |
        呼び出し階 := 各階待ち人数 findLast: [:各階 | 各階 > 0].
        呼び出し階 = 0
            ifTrue: [ボックス移動 value: 1]
            ifFalse: [ボックス移動 value: 呼び出し階]].
    ボックス停止階 > 1 or: [各階待ち人数 anySatisfy: [:各階 | 各階 > 0]]
] whileTrue.

状態出力 value.

Transcript cr; show: '経過時間: '; show: ラウンド数.
"Transcript cr; show: '階別の放置時間一覧: '; show: 放置ラウンド数."
Transcript cr; show: '階別の最大放置時間: '; show: 放置ラウンド数 max.
Transcript cr; show: 'のべ移動階数: '; show: 移動数

"出力 => 
5階; 7 人 / 完了ラウンド: 0
4階; 11 人 / 完了ラウンド: 0
3階; 3 人 / 完了ラウンド: 0
2階; 7 人 / 完了ラウンド: 0
1階; 0 人 / 完了ラウンド: 0

5階: 7 -> 4. 
5階: 4 -> 1. 
5階: 1 -> 0. 4階: 11 -> 9. 
4階: 9 -> 6. 
4階: 6 -> 3. 
4階: 3 -> 0. 
3階: 3 -> 0. 
2階: 7 -> 4. 
2階: 4 -> 1. 
2階: 1 -> 0. 

5階; 0 人 / 完了ラウンド: 65
4階; 0 人 / 完了ラウンド: 138
3階; 0 人 / 完了ラウンド: 158
2階; 0 人 / 完了ラウンド: 202
1階; 0 人 / 完了ラウンド: 0

経過時間: 209
階別の最大放置時間: 174
のべ移動階数: 52 "
やっぱ、上から下へ運ぶだけのお題にしてしまえばよかったんですかねぇ。
移動距離だけ計算して。。。

このお題を投稿する時点でもなやんだのですよ。
実はこの基本すら飛ばして応用だけにしてしまうかとか。

今にして思えば、このお題の前後をお題にすればちょうどよかった気がしてきましたorz
Groovyで。

待ってる人の待ち時間を少なくなるよう勤めました。
(経過時間と移動距離合計数は無駄に増えます)
乗降を開始した時点で各階の待ち時間をリセットしています。

動きとしては以下のような感じです。
(1) 待っている人がいる最上階に移動
(2) 1人乗せる
(3) 1階降りて止まる
(4) 1人降ろす
(5) 1階にたどり着くまで、(2)~(4)を繰り返す
(6) 1階に到着したら、1人降ろす


出力結果
INIT --------------------
[5]th Floor: [ 7] / last_round:[0]
[4]th Floor: [11] / last_round:[0]
[3]th Floor: [ 3] / last_round:[0]
[2]th Floor: [ 7] / last_round:[0]
[1]th Floor: [ 0] / last_round:[0]
-------------------------
Elevator move from [1 → 5]
Person get on at [5]th Floor / Floor's member [7 → 6]
Elevator move from [5 → 4]
Person get off at [4]th Floor / Floor's member [11 → 12]
Person get on at [4]th Floor / Floor's member [12 → 11]
Elevator move from [4 → 3]
  ・
  ・(途中省略)
  ・
Elevator move from [2 → 1]
Person get off at [1]th Floor / Floor's member [26 → 27]
Elevator move from [1 → 2]
Person get on at [2]th Floor / Floor's member [1 → 0]
Elevator move from [2 → 1]
Person get off at [1]th Floor / Floor's member [27 → 28]
END --------------------
[5]th Floor: [ 0] / last_round:[51]
[4]th Floor: [ 0] / last_round:[46]
[3]th Floor: [ 0] / last_round:[46]
[2]th Floor: [ 0] / last_round:[46]
[1]th Floor: [28] / last_round:[0]
      経過時間:[1036]
  最大待ち時間:[51]
移動距離合計数:[148]
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
/** エレベータ */
class Elevator {
  static final int MOVE_COST       = 2 // 移動コスト
  static final int GETTING_ON_COST = 5 // 乗降コスト
  static final int MAX_PERSONS     = 3 // 最大人数
  def floors                           // フロア
  int currentFloor                     // 現在のフロア
  int persons                          // 乗っている人数
  int elapsedRound = 0                 // 経過ラウンド
  int moved        = 0                 // 総移動距離

  /** 階を移動する */
  def moveTo(toFloor) {
    int movin = Math.abs(toFloor - currentFloor)
    println("Elevator move from [${currentFloor + 1} → ${toFloor + 1}]")
    moved += movin
    currentFloor = toFloor
    elapseByMove(movin * MOVE_COST)
  }

  /** フロアからエレベータへ一人乗せる */
  def getOn() {
    if (persons < MAX_PERSONS && floors[currentFloor].persons) {
      persons++
      int before = floors[currentFloor].persons--
      elapseByGettingOn(GETTING_ON_COST)
      println("Person get on at [${currentFloor + 1}]th Floor / "
      + "Floor's member [${before} → ${floors[currentFloor].persons}]")
    }
  }

  /** エレベータからフロアへ一人降ろす */
  def getOff() {
    if (persons) {
      persons--
      int before = floors[currentFloor].persons++
      elapseByGettingOn(GETTING_ON_COST)
      println("Person get off at [${currentFloor + 1}]th Floor / "
      + "Floor's member [${before} → ${floors[currentFloor].persons}]")
    }
  }

  /** 移動によるラウンドの経過 */
  def elapseByMove(round) {
    floors.each{ it.elapse(round) }
    elapsedRound += round
  }
  
  /** 乗降によるラウンドの経過 */
  def elapseByGettingOn(round) {
    floors.eachWithIndex{ f, idx ->
      if (currentFloor == idx) {
        f.resetRound()
      } else {
        f.elapse(round)
      }
    }
    elapsedRound += round
  }
  
  /** フロア情報を出力 */
  def dispFloorsInfo(){
    (1..floors.size()).reverseEach{
      println("[${it}]th Floor: "
      + "[${(floors[it-1].persons as String).padLeft(2)}] /"
      + " last_round:[${floors[it-1].maxWaitRound}]")
    }
  }
  
  /** ラウンド情報を出力 */
  def dispRoundInfo() {
    println """\
      経過時間:[${elapsedRound}]
  最大待ち時間:[${floors*.maxWaitRound.max()}]
移動距離合計数:[${moved}]"""
  }

}

/** フロア */
class Floor {
  int persons          // 待っている人数
  int waitRound    = 0 // 待っているラウンド
  int maxWaitRound = 0 // 最大待ちラウンド
  boolean noRound      // ラウンド経過なし

  /** ラウンド経過 */
  def elapse(round) {
    if (persons && !noRound) {
      waitRound += round
      maxWaitRound = [maxWaitRound, waitRound].max()
    }
  }
  
  /** ラウンドリセット */
  def resetRound() {
    waitRound = 0
  }
}


def elev = new Elevator(
  floors:[
    new Floor(persons: 0, noRound:true), // 1階 (ラウンド経過なし)
    new Floor(persons: 7              ), // 2階
    new Floor(persons: 3              ), // 3階
    new Floor(persons:11              ), // 4階
    new Floor(persons: 7              )  // 5階
  ],
  currentFloor: 0,
  persons     : 0
)

// メイン処理
println 'INIT --------------------'
elev.dispFloorsInfo()
println '-------------------------'

// 1階以外で待っている人がいる間繰り返す
while(elev.floors.tail()*.persons.sum()) {
  // 待っている人がいる最上階を探す
  def nextList = []
  elev.floors.tail()*.persons.eachWithIndex{ item, idx ->
    nextList << [idx+1, item]
  }
  def topFloor = nextList.findAll{ it[1] != 0 }.last()[0]

  // 上から順に一人ずつスライドさせてくる
  for (i in topFloor..0) {
    elev.moveTo(i)
    switch (i) {
      case topFloor:                elev.getOn();  break
      case        0: elev.getOff();                break
      default      : elev.getOff(); elev.getOn();  break
    }
  }
}

println 'END --------------------'
elev.dispFloorsInfo()
elev.dispRoundInfo()
#8319と同じアプローチで。
主にプログラム作成の容易さについて効率化しました(笑)
総移動距離は最短になります。

階数表記は英国式(0Fが地階)です。
----実行結果----
4F[7->4] -> 0F. (Round: 26)
4F[4->1] -> 0F. (Round: 52)
4F[1->0] -> 3F[11->9] -> 0F. (Round: 83)
3F[9->6] -> 0F. (Round: 105)
3F[6->3] -> 0F. (Round: 127)
3F[3->0] -> 0F. (Round: 149)
2F[3->0] -> 0F. (Round: 167)
1F[7->4] -> 0F. (Round: 181)
1F[4->1] -> 0F. (Round: 195)
1F[1->0] -> 0F. (Round: 209)
Finish.
        Total time: 209
        Total move: 52
        Max wait:   174
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
function log(str, lineContinue) {
    // for Rhino
    java.lang.System.out.print(str);
    if (!lineContinue) java.lang.System.out.println();
}

function Elevator(capacity, t1, t2) {
    this.capacity = capacity;
    this.t1 = t1;
    this.t2 = t2;
    this.init([]);
}

Elevator.prototype = {
    simulate: function (data) {
        this.init(data);
        var c;
        while (c = this.callingFrom()) {
            this.upTo(c);
            while (this.floor != 0) this.down();
        }
        this.printResult();
    },
    printResult: function () {
        log("Finish.");
        log("\tTotal time: " + this.round);
        log("\tTotal move: " + this.move);
        log("\tMax wait:   " + Math.max.apply(null, this.waitTime));
    }, 
    init: function (data) {
        this.floor = 0; // 英国式
        this.count = 0;
        this.round = 0;
        this.move = 0;
        this.waitTime = [];
        for(var i=0,n=data.length+1;i<n;i++) this.waitTime[i]=0;
        this.data = [0].concat(data);
    },
    upTo: function (f) {
        this.round += this.t1 * (f - this.floor);
        this.move += (f - this.floor);
        this.aliveAt(f);
    },
    down: function () {
        this.round += this.t1;
        this.move++;
        this.aliveAt(this.floor-1);
    },
    aliveAt: function (f) {
        this.floor = f;
        if (this.floor == 0) {
            this.round += this.t2;
            this.count = 0;
            log("0F. (Round: " + this.round + ")");
        } else {
            var n = Math.min(this.capacity - this.count, this.data[f]);
            if (n != 0) {
                this.round += this.t2;
                this.data[f] -= n;
                this.count += n;
                if(this.waitTime[f]==0) this.waitTime[f] = this.round;
                log(f + "F[" + (this.data[f] + n) + "->" + this.data[f] + "] -> ", true);
            }
        }
    },
    callingFrom: function () {
        for (var f = this.data.length - 1; f > 0; f--)
            if (this.data[f] > 0) return f;
        return 0;
    }
}

if (arguments.length > 0) {
    new Elevator(arguments.shift()*1, arguments.shift()*1, arguments.shift()*1).simulate(arguments);
} else {
    new Elevator(3, 2, 5).simulate([7, 3, 11, 7]);
}

ども、raynstardです。 コメントにもありますが、ロジックを単純にするとエレベータの移動距離が最小限になります。

いってみれば、消費エネルギーの効率をよくしたということです。 つまり、エコですよ!(笑

そして、また、テストデータがあまかったorz 予定では、単純なロジックにすると移動距離がへるはずだったんだけどなぁ^^;;

とりあえず、サンプル通りのやつと、自分なりに最適化したのを1本づつ。 方針としては、トータルの待ち時間の最小化です。 各人が1階まで降りる時間*人数の合計が最小になるように、という方針です。

実際のところは、下の階層から優先的に運び出してしまって、上の階層は後回し、ということです。定員まで余裕があれば、更に上の階層へ寄り道、という手順になります。

サンプルプログラム(FindNext2)の手順だと、 Score =3464 Time =209 Run =52 ですが、この手順(FindNext1)ですと、 Score =2931 Time =222 Run =56 となり、若干エレベータの移動量(Run)及び所要時間(Time)が増えるものの、大幅に合計待ち時間(Score)が短縮されます。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
%!PS

% [[待ち人] 現在位置 乗員 定員 時刻 評価]   Check1  [...]
/Check1 {
    dup 0 get true exch { 0 eq and } forall % 待ち無し
    1 index 1 get 0 eq and                  % 地上階    
} bind def

% [[待ち人] 現在位置....] FindNext1 num
/FindNext1 {
    0 2 getinterval aload
    0 get length 1 sub 1 exch {
        2 copy get 0 gt {
            exch pop true exit
        } if
        pop
    } for
    true ne {
        0
    } if
} bind def

/FindNext2 {
    (==) = ppstack (==)=
    0 get
    dup length 1 sub -1 1
    {
        2 copy get 0 gt {
            exch pop true exit
        } if
        pop
    } for
    true ne {
        0
    } if
} bind def

/AddVal { % [] pos tt  AddVal  []
    2 index 2 index get add 2 index 3 1 roll put
} bind def

% [[待ち人] 現在位置 乗員 定員 時刻 評価]  /Next  Move1  [...]
/Move1 {
    exch
    (Move: ) print dup 1 get 1 add 3 string cvs print ( ==> ) print

    (==) = ppstack
    dup 2 2 getinterval aload pop lt {
        dup 3 -1 roll cvx exec
        1 index 1 get
        2 index 2 index 1 exch put
        sub abs
        2 copy 6 exch AddVal pop
        MoveTime mul 4 exch AddVal
    } {
        exch pop
        dup 1 get
        2 copy 6 exch AddVal pop
        MoveTime mul 4 exch AddVal
        dup 1 0 put
    } ifelse
    dup 1 get 1 add 3 string cvs =
    dup ===
} bind def
/RideOn {
    % [[] ... ] n RideOn [[] ...]
    1 index 0 get 2 index 1 get 2 index neg
    AddVal pop
    2 exch AddVal
} bind def
/IO1 {
    dup 1 get 0 eq {
        (Floor 1 =[Out]= ) print dup 2 get =
        dup 2 get 0 gt {
            4 IOTime AddVal
            dup 2 get exch dup 4 get 3 -1 roll mul
            5 exch AddVal
            dup 2 0 put
        }if
        dup ===
    } {
        (Floor ) print dup 1 get 1 add 3 string cvs print ( =[In]= ) print
        dup 1 get exch dup 0 get 3 -1 roll get
        exch dup 2 get exch dup 3 get 3 -1 roll sub
        3 copy 3 -1 roll lt {
            pop 3 -1 roll pop
        } {
            pop pop exch
        } ifelse
        dup 0 gt {
            dup =
            RideOn
            4 IOTime AddVal
        } {
            (noop) =
        } ifelse
    } ifelse
} bind def


/Run {
    exch
    {
        Check1 { exit } if
        1 index Move1
    IO1
    } loop
    (Score =) print dup 5 get =
    (Time  =) print dup 4 get =
    (Run   =) print dup 6 get =
    pop pop
} bind def

% ===== Test Code =====
/MoveTime 2 def
/IOTime 5 def
[[0 7 3 11 7] 0 0 3 0 0 0] /FindNext2 Run
[[0 7 3 11 7] 0 0 3 0 0 0] /FindNext1 Run

Haskellでやってみました。

全ての可能性を試して最小稼働時間で全員を1階に運ぶようにしています。一番単純なので…あと、一番長く放置されていた階の待ち時間まで手が回ってません。

出力はかなりいい加減です:

(209," +1 *3 -1 / +1 *3 -1 / +2 *3 -2 / +3 *3 -3 / +3 *3 -3 / +3 *3 -3 / +3 *2 +

1 *1 -4 / +4 *3 -4 / +4 *3 -4 / +1 *1 -1 /")

最初の数字はラウンド数、次の文字列はエレベーターのシークエンスで、+、-はそれぞれ相対移動階数、*はその階で乗った人の人数、/は1階で全員降りたしるしです…つまり、"+1 *3 -1 /"は(1階から)一つ上に上がって、3人乗って、ひとつ下に降りて、(一階で)全員おろしたということになります…

モナドコンビネータはパーサーだけじゃなくてこうゆう問題をやるのにも使えますね…

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
module Main where

import Data.Array.Unboxed (UArray, listArray, assocs, (//), (!))
import Control.Monad.State
import Data.Function (on)
import Data.List (minimumBy)
import Data.Ord (compare)

-- Data Types --
data Elevator = E {
    cPassenger :: Int,
    iFloor :: Int
    } deriving Show

data Wait = W {
    unW :: (UArray Int Int)
    } deriving Show

type Cost = (Int, String)

type ElevState = (Cost, (Elevator, Wait))

type ElevM = State (Elevator, Wait) Cost

-- Data --
initialWait = W $ listArray (1, 5) [0, 7, 3, 11, 7]
initialElev = E 0 1

initial = ((0, ""), (initialElev, initialWait))

-- Primitives --
isFull :: Elevator -> Bool
isFull (E p fl) = p == 3

comb :: Cost -> Cost -> Cost
comb (c1, st1) (c2, st2) = (c1 + c2, st1 ++ " " ++ st2)

waitingFloor :: Wait -> [Int]
waitingFloor = map (fst) . filter ((/=0).snd) . assocs . unW

-- (Quasi) Monadic Combinators --
combM :: Cost -> ElevM -> ElevM
combM c m = m >>= \c' -> return $ c `comb` c'

(<*>) :: ElevM -> ElevM -> ElevM
a1 <*> a2 = a1 >>= \c1 -> combM c1 a2

ifFull :: ElevM -> Cost -> ElevM
ifFull ifM c = State $ \i@(e, w) -> case (isFull e) of
    False     -> (c, i)
    otherwise -> runState (combM c ifM) i

ifFullM :: ElevM -> ElevM -> ElevM
ifFullM pM tM = pM >>= \c -> ifFull tM c

-- Monadic Primitive Elevator Operations --
pickupM :: ElevM
pickupM = State $ _pickup

_pickup (E p fl, W ar) = ((5, str), (E p' fl, W ar'))
    where
        ar' = ar//[(fl, cWaiting - cPickup)]
        p' = p + cPickup
        cWaiting = ar!fl
        cPickup = min cWaiting (3 - p)
        str = '*' : (show cPickup)
        
dropM :: ElevM
dropM = State $ \(E p fl, w) -> case (p) of
    0         -> ((0, ""),  (E 0 fl, w))
    otherwise -> ((5, "/"), (E 0 fl, w))

moveByM :: Int -> ElevM
moveByM d =  State $ \((E p fl), w) -> (c, (E p $ fl + d,  w))
    where
        c = (abs $ d * 2, cat d)
        cat  d
            | d == 0 = ""
            | d < 0 = show d
            | otherwise = '+' : (show d)

-- Composit Monadic Operations --
takeHomeM :: ElevM
takeHomeM = State $ \i@((E _ fl), _) -> (runState $ (moveByM $ 1 - fl) <*> dropM) i

goM :: Int -> ElevM
goM flTo = State $ \i@((E _ fl), _) -> (runState $ moveByM $ flTo - fl) i

turnM :: Int -> ElevM
turnM flTo = ((goM flTo) <*> pickupM) `ifFullM` takeHomeM

-- Permutation Traversers --
tryAll :: ElevState -> [ElevState]
tryAll (c, i@(_, w)) 
    | fls == [] = [runState (combM c takeHomeM) i]
    | otherwise = map (\fl -> runState (combM c $ turnM fl) i) fls >>= tryAll
    where
        fls = waitingFloor w

-- Main --
main = (print.fst . minimumBy (byCost) . tryAll) initial
    where
        byCost :: ElevState -> ElevState -> Ordering
        byCost = compare `on` (fst . fst)

Haskellでやったんですよー言語タグをつけるの忘れました…

1
2
3
module Main where

main = putStrLn "see my previous post"
#8319と同様の方針で。
経過時間が最小になっています(たぶん)。

[INIT]
1階:	7人
2階:	11人
3階:	3人
4階:	7人
5階:	0人
--------------------
5階 [7 => 4] => 1階
5階 [4 => 1] => 1階
5階 [1 => 0] => 4階 [11 => 9] => 1階
4階 [9 => 6] => 1階
4階 [6 => 3] => 1階
4階 [3 => 0] => 1階
3階 [3 => 0] => 1階
2階 [7 => 4] => 1階
2階 [4 => 1] => 1階
2階 [1 => 0] => 1階
--------------------
[END]
1階:	0人
2階:	0人
3階:	0人
4階:	0人
5階:	28人
--------------------
[RESULT]
経過時間:	209
最大待ち時間:	174
移動距離:	52
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
class Elevator
  CAPACITY = 3
  MOVE_COST = 2
  GETTING_ON_COST = 5

  attr_reader :current_floor, :number_of_people, :total_round, :travel_distance
  
  def initialize
    @current_floor = 0 # 現在の階
    @number_of_people = 0 # 乗っている人数
    @total_round = 0 # 現在の合計ラウンド
    @travel_distance = 0 # 現在の総移動距離
  end

  def move_to(floor)
    distance = (@current_floor - floor).abs
    @total_round += MOVE_COST * distance
    @travel_distance += distance
    @current_floor = floor
  end

  def get_on(n)
    raise if (@number_of_people + n) > CAPACITY
    @total_round += GETTING_ON_COST
    @number_of_people += n
  end

  def get_off(n)
    raise if @number_of_people < n
    @total_round += GETTING_ON_COST
    @number_of_people -= n
  end

  def room_for_getting_on
    CAPACITY - @number_of_people
  end
end


class Building

  def initialize(floors)
    @floors = floors # 各階の人数(階数は0から)
    @elevator = Elevator.new
    @first_visited_round = [] # 各階に初めて止まったときのラウンド
    @floors.size.times { @first_visited_round << nil }
    @first_visited_round[0] = 0
  end

  def get_on_to_evevator
    n = (@floors[@elevator.current_floor] <= @elevator.room_for_getting_on) ? @floors[@elevator.current_floor] : @elevator.room_for_getting_on
    @elevator.get_on(n)
    @floors[@elevator.current_floor] -= n
  end

  def get_off_from_elevator
    n = @elevator.number_of_people
    @elevator.get_off(n)
    @floors[@elevator.current_floor] += n
  end

  def finished?
    @floors[1..@floors.size].inject(0) { |s, a| s + a } == 0
  end

  def where_to_go
    floor = nil
    (@floors.size - 1).downto(0) { |i|
      if @floors[i] > 0
        floor = i
        break
      end
    }
    raise if floor.nil?
    floor
  end
  
  def simulate
    until finished?
      floor = where_to_go
      prev = @floors[floor]
      @elevator.move_to(floor)
      get_on_to_evevator
      print "#{floor + 1}階 [#{prev} => #{@floors[floor]}] => "
      @first_visited_round[floor] = @elevator.total_round if @first_visited_round[floor].nil?
      if (@elevator.number_of_people == Elevator::CAPACITY) || (@elevator.current_floor == 1)
        @elevator.move_to(0)
        get_off_from_elevator
        puts "1階"
      end
    end
  end

  def get_total_round
    @elevator.total_round
  end

  def get_travel_distance
    @elevator.travel_distance
  end

  def get_max_waiting_round
    @first_visited_round.max
  end

  def get_floors_status
    status = ''
    @floors.reverse.each_with_index { |n, i| status << "#{i + 1}階:\t#{n}\n" }
    status
  end
end

b = Building.new([0, 7, 3, 11, 7]) # 1階 0人, 2階 7人, 3階 3人, 4階 11人, 5階 7人
puts '[INIT]'
puts b.get_floors_status
puts '-' * 20
b.simulate
puts '-' * 20
puts '[END]'
puts b.get_floors_status
puts '-' * 20
puts '[RESULT]'
puts "経過時間:\t#{b.get_total_round}"
puts "最大待ち時間:\t#{b.get_max_waiting_round}"
puts "移動距離:\t#{b.get_travel_distance}"

Index

Feed

Other

Link

Pathtraq

loading...