challenge 出力の一時停止と再開

起動すると、標準出力に1秒毎に'a'の1文字を出力し続けるプログラムで、 以下の条件を満たすものを「どう書く?」

  • 'q'キーが押されるとプログラムは終了する
  • 出力中に'p'キーが押されると一時停止する
  • 一時停止中に'p'キーが押されると出力を再開する

Posted feedbacks - Nested

Flatten Hidden

こんにちは。 このコードは環境に依存しています。標準ではありません。 でも、C/C++だとstdioもiosteamもブロッキングされちゃうのでこういうコード書くしか手がないと思われます。 抜け道募集。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
#include <stdio.h>
#include <conio.h>

int main(){
    bool Output=true;
    int ch=0;
    do{
        ch = 0;
        if(_kbhit()) ch  = _getch();//環境依存コード。
        if(ch == 'p') Output = !Output;
        if(Output) printf("A");
    }while(ch != 'q');

}

うーん。こんにちは。 えらそうなこと言っておきながら仕様満たしてなかったですね。 1秒毎に表示するようにしました。 ほかの方などがいわれてるの見て気づいたんですけど、あるぅぇえええ??って感じです。(汗

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
#include <time.h>
#include <conio.h>

int main(){
    bool IsOutput=true;
    int ch=0;
    int TimeCount=0,TimeOld=0;
    do{
        ch = 0;
        if(_kbhit()) ch  = _getch();//環境依存コード。
        if(ch == 'p') IsOutput = !IsOutput;
        if(IsOutput){
            TimeCount = time(NULL);
            if(TimeCount != TimeOld){
                printf("A");
                TimeOld = TimeCount;
            }
        }
    }while(ch != 'q');
    return 0;
}
ども、raynstardです。
入力がブロッキングされてしまうのは
入力モードを変更してあげないからですね。

UNIXでgetche()の作り方というのを調べるといろいろと出てくると思います。
ポイントはtermiosでカノニカルモードを解除してあげていることと
標準出力のバッファリングをなしにしているところでしょうか?
標準入力の方はおまけです。
// gcc -Wall -std=c99 doukaku179.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
#include <stdio.h>
#include <stdbool.h>
#include <termios.h>
#include <time.h>

static struct termios termios_save;
static bool isContinued = true;
static bool enableOutputScreen = true;

int initKeyInput(void)
{
    struct termios t;
    tcgetattr(0, &termios_save);
    tcgetattr(0, &t);
    t.c_lflag &= ~(ICANON|ECHO);
    t.c_cc[VMIN]  = 0;
    t.c_cc[VTIME] = 0;
    tcsetattr(0, TCSANOW, &t);
    setvbuf(stdin, NULL, _IONBF, 0);
    return 0;
}

int main(int argc, char *argv[])
{
    char key;
    ssize_t read_size;
    time_t old, now;

    initKeyInput();
    setvbuf(stdout, NULL, _IONBF, 0);

    old = 0;
    while( isContinued )
    {
        now = time(NULL);
        if( now != old )
        {
            old = now;
            if( enableOutputScreen == true )
            {
                printf("a");
            }
        }
        read_size = fread(&key, 1, sizeof(key), stdin);
        if( read_size > 0 )
        {
            switch( key )
            {
                case 'q':   /* quit */
                    isContinued = false;
                    break;
                case 'p':   /* pause */
                    enableOutputScreen = (enableOutputScreen != true);
                    break;
                default:
                    break;
            }
        }
    }
    tcsetattr(0, TCSANOW, &termios_save);
    printf("\n");
    return 0;
}
/* EOF */

Javaでは、STDINがバッファされてしまうため Enterが押下されるまで読み込めません。 そのため、下記のコードでは p + Enterと、q + EnterでSuspendとQuitします。

 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
public class Sample179 implements Runnable {

    private final Object lockObj_ = new Object();

    private boolean isSuspend_ = false;

    public boolean getSuspend() {
        synchronized (lockObj_) {
            return isSuspend_;
        }
    }
    public void changeSusupend() {
        synchronized (lockObj_) {
            isSuspend_ = !isSuspend_;
            if (isSuspend_) {
                lockObj_.notifyAll();
            }
        }
    }

    @Override
    public void run() {
        try {
            while (Thread.currentThread().isAlive()) {
                synchronized (lockObj_) {
                    while (getSuspend()) {
                        lockObj_.wait(1000);
                    }
                }
                System.out.print('a');
                Thread.sleep(1000);
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }


    public static void main(String[] args) {
        final Sample179 sample = new Sample179();
        Thread thread = new Thread(sample);
        thread.setDaemon(true);
        thread.start();
        try {
            while (true) {
                char c = (char) System.in.read();
                if (c == 'q') break;
                if (c == 'p') {
                    sample.changeSusupend();
                }
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

Squeak Smalltalk で。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
| process |
process := [
    World findATranscript: nil.
    [   Transcript show: $a.
        (Delay forSeconds: 1) wait
    ] repeat
] fork.

[   ActiveHand checkForMoreKeyboard ifNotNilDo: [:event |
        event keyCharacter = $q ifTrue: [^process terminate].
        event keyCharacter = $p ifTrue: [
            process isSuspended
                ifTrue: [process resume]
                ifFalse: [process suspend]]].
    Processor yield.
] repeat
適当に拡張性を考えて書いてみた。
 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
#include <stdio.h>
#include <conio.h>
#include <time.h>

#define STATE_RUN  0
#define STATE_WAIT 1
#define STATE_QUIT 2


int main(){
    time_t now,last;
    int c;
    int state=STATE_RUN;
    
    last=time(NULL);
    do{
        if(kbhit()){
            c=getch();
            switch(c){
            case 'q':
                state=STATE_QUIT;
                break;
            case 'p':
                if(state==STATE_RUN){
                    state=STATE_WAIT;
                }else{
                    state=STATE_RUN;
                }
            }
        }
        
        switch(state){
        case STATE_RUN:
            now=time(NULL);
            if(last-now!=0){
                last=now;
                printf("a");
                fflush(stdout);
            }
        }
    }while(state!=STATE_QUIT);
    
    return 0;
}

fork ってみた

 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
use strict;
use warnings;
use POSIX qw( SIGKILL SIGINT );
BEGIN { system 'stty -echo -icanon >/dev/tty' }
END{    system 'stty echo icanon   >/dev/tty' }

my $pid = fork;
if($pid) {
  for(;;){
    my $a = getc;
    if($a eq 'q') {
      kill SIGKILL , $pid;
      exit;
    }
    elsif($a eq 'p') {
      kill SIGINT , $pid;
    }
  }
}
else {
  my $flag = 1;
  local $| = 1;
  local $SIG{INT} = sub(){ $flag = !$flag };
  for(;;){
    print "a" if $flag;
    sleep 1;
  }
}
threadを使った
 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
use strict;
use threads;
use threads::shared;
use Term::ReadKey;
$| = 1;

my $f : shared;

sub observe {
  ReadMode 'cbreak';
  while(1){
    my $key;
    while(1){ last if $key = ReadKey(0) }
    {
      lock $f;
      if ($key eq 'q'){ ReadMode 'restore'; exit;}
      if ($key eq 'p'){ $f == 1 ? $f-- : $f++;}
    }
  }
}

sub print_a {
  while(1){
    if($f == 1){
      print "a";
      sleep 1;
    }
  }
}

$f = 1;
my $ob = threads->create(\&observe);
my $pr = threads->create(\&print_a);

$ob->join;
$pr->join;
標準出力といえるかかなり微妙ですが、Processing で。ウィンドウ中央に適当な色で a を書き続けます。
 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
boolean pause = false;

void setup(){
  size(200,200);
  frameRate(1);
  // "Processing -> Tools -> Create Font ..." and save "Courier-48.vlw" 
  PFont font = loadFont("Courier-48.vlw");
  textFont(font, 48);
  textAlign(CENTER);
}

void draw() {
  if (! pause) {
    fill(random(255),random(255),random(255));
    text("a", width/2, height/2);
  }
}

void keyPressed() {
  switch (key) {
    case 'p':
      pause = !pause;
      break;
    case 'q':
      fill(0, 30);
      text("quit", width/2, height/2);
      exit();
      break;
  }
}
これもleditは付けずに実行してください。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#load "unix.cma";;
open Unix;;

(*エコー、行バッファoff*)
let x = tcgetattr Unix.stdout in
  tcsetattr Unix.stdout TCSADRAIN {x with
    c_icanon = false;  c_vmin = 1; c_echo = false; };
  at_exit (fun () -> tcsetattr Unix.stdout TCSADRAIN x;);

(*一秒ごとのa*)
Sys.set_signal Sys.sigalrm (Sys.Signal_handle 
  (fun _ -> print_char 'a'; flush Pervasives.stdout));

(*本編*)
let t = ref (setitimer ITIMER_REAL {it_interval=1.0; it_value=1.0}) in
  while true do
    match input_char Pervasives.stdin with
    | 'q' -> exit 0
    | 'p' -> t := setitimer ITIMER_REAL !t
    | _ -> ()
  done;;

ざっくりと。

 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
module Doukaku179 = begin
    type PeriodicAutoWriter = class
        val mutable timer : System.Threading.Timer
        new() = { timer = null }
        member t.Start()  = t.timer <- new System.Threading.Timer(
                              (fun _ -> printf "%s" "a"), null, 0, 1000)
        member t.Stop()   = if t.timer <> null then (t.timer.Dispose();
                                                     t.timer <- null)
                                               else ()
        member t.Toggle() = if t.timer = null then t.Start() else t.Stop()
    end
    
    let read_char () = System.Console.ReadKey().KeyChar
    let keep_read () = seq { while true do yield read_char () done }
    let run () = let writer = new PeriodicAutoWriter() in
                 writer.Start ();
                 seq { for c in keep_read () do
                         yield (match c with
                                  'q' -> writer.Stop();   false
                                | 'p' -> writer.Toggle(); true
                                | _   -> true)
                       done }
                 |> Seq.find ((=) false)
                 |> (fun _ -> printfn "%s" "");;
end;;

#if COMPILED
let _ = Doukaku179.run ();;
#endif

man bashしたらいけることがわかったので作ってみました

1
2
3
4
5
6
7
stty -echo
while echo -n a && read -t 1 -n 1 a || [ "$a" != q ]; do
  if [ "$a" = p ]; then
    while read -n 1 a && [ "$a" != p ]; do :; done
    a=
  fi
done
こんなんで題意に添えているでしょうか?
System.Timers.Timerクラスを使用しています。Timerクラスは指定した間隔でイベントを発生させます。
後はループを回して、その中でTimerの停止、再開を切り替えています。
 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
using System;
using System.Timers;

class Program {
    static void Main(string[] args) {
        using(Timer timer = new Timer(1000)) {
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
            bool flag = true;
            while(flag) {
                ConsoleKeyInfo conKeyInfo = Console.ReadKey(true);
                ConsoleKey conKey = conKeyInfo.Key;
                switch(conKey) {
                case ConsoleKey.P:
                    timer.Enabled = !timer.Enabled;
                    break;
                case ConsoleKey.Q:
                    flag = false;
                    break;
                }
            }
           timer.Close();
        }
    }

    static void timer_Elapsed(object sender, ElapsedEventArgs e) {
        Console.Out.WriteLine("a");
    }
}
 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
require 'Win32API'

GetKeyState = Win32API.new('user32', 'GetKeyState', 'I', 'I')

pause = false

Thread.start {
  loop {
    print 'a' unless pause
    sleep 1
  }
}

loop {
  key_state = {:p => (GetKeyState.call(?P)[7] == 1),
               :q => (GetKeyState.call(?Q)[7] == 1)
  }
  exit if key_state[:q]
  if key_state[:p]
    pause = !pause
    sleep 0.3
  else
    sleep 0.1
  end
}

pキーの長押し時、動作に不具合があったので修正しました。

 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
require 'Win32API'

GetKeyState = Win32API.new('user32', 'GetKeyState', 'I', 'I')

pause = false
can_toggle_pause = true

Thread.start {
  loop {
    unless pause
      print 'a'
      sleep 1
    else
      sleep 0.1
    end
  }
}

loop {
  key_state = {:p => (GetKeyState.call(?P)[7] == 1),
               :q => (GetKeyState.call(?Q)[7] == 1)
  }
  exit if key_state[:q]
  if key_state[:p]
    if can_toggle_pause
      pause = !pause
      can_toggle_pause = false
    end
  else
    can_toggle_pause = true
  end
  sleep 0.05
}
もしかして、こっちの方が期待通り?と思い、自前でマルチスレッドで書いてみました。
Timerクラス使うのと大差ないコード量ですね、若干挙動は違いますが。
 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
using System;
using System.Threading;

class Program {
    static void Main(string[] args) {
        Thread thread = new Thread(new System.Threading.ThreadStart(tick));
        thread.IsBackground = true;
        thread.Start();
        bool flag = true;
        while(flag) {
            switch(Console.ReadKey(true).Key) {
            case ConsoleKey.P:
                switch_ = !switch_;
                break;
            case ConsoleKey.Q:
                flag = false;
                break;
            }
        }
    }
    static bool switch_ = true;
    static void tick() {
        while(true) {
            if(switch_) {
                Console.Out.Write("a");
            }
            Thread.Sleep(1000);
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import curses

w = curses.initscr()

try:
  curses.noecho()
  w.timeout(1000)
  flg = True
  while True:
    c = w.getch()
    if c == -1 and flg:
      w.addch('a')
    elif c == ord('q'):
      break
    elif c == ord('p'):
      flg = not flg
except:
  pass

curses.endwin()
sbcl依存のthreadパッケージを利用しています。一つわからない
ことがあって、それはdefpackage - in-packageの流れでパッケー
ジ化すると、うまく動かないところです。

thread programmingははじめてなので、変数の取り扱いの
注意点が抜けてるみたいです。
 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
(defun print-a ()
  (format *standard-output* "a")
  (finish-output)
  (sleep 1))

(defmacro read-thread(res)
  `(sb-thread:make-thread
    (lambda()(setf ,res (read *standard-input*)))))

(defun main ()
  (let* ((res nil)
     (stop nil)
     (thread (read-thread res)))
    (loop do
     (if (null (sb-thread:thread-alive-p thread))
         (case res
           ((q)
        (return))
           ((p)
        (and stop (print-a))
        (setf stop (not stop)
              thread (read-thread res)))
           (otherwise
        (unless stop (print-a))
        (setf thread (read-thread res))))
         (unless stop (print-a))))))
threadを扱うときに変数の入り方がわかりにくかったですね。

q<return>すると resの中には'common-lisp-user::q と入力さ
れていました。こんなことあるんですね。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
0a1,4
> (defpackage :dokaku179
>   (:use :common-lisp :sb-thread))
> (in-package :dokaku179)
> 
8c12,14
<     (lambda()(setf ,res (read *standard-input*)))))
---
>     (lambda()(progn
>            (in-package :dokaku179)
>            (setf ,res (read *standard-input*))))))

Haskellでも多言語同様にコンソール入力がエンターを押すまで読めない病にかかっています。

 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
module Main where

import System
import System.IO
import System.IO.Error
import Control.Monad

weaveIO :: (IO Bool) -> IO a -> (a -> IO a) -> IO a
weaveIO w m f = do
    a <- m 
    b <- w
    if b then f a
        else return a

getChar' :: IO Char
getChar' = getLine >>= return.head.(++" ")

nonBlockingGetCh :: IO (Maybe Char)
nonBlockingGetCh = do
    f <- hReady stdin
    if (not f) then return Nothing
        else do
            getChar' >>= return.Just

respondToKey :: IO Bool
respondToKey = nonBlockingGetCh >>= evalCh
    where
        evalCh Nothing = return True
        evalCh (Just 'q') = print "+++ quitting +++" >> return False
        evalCh (Just 'p') = waitForP ' '
        evalCh _ = return True
        waitForP 'p' = return True
        waitForP 'q' = print "+++ quitting +++" >> return False
        waitForP _ = print "+++ paused, hit 'p' again +++" >> getChar' >>= waitForP


showLineWeave :: Handle -> IO ()
showLineWeave h = weaveIO (respondToKey) (hGetLine h >>= putStrLn) (\_ -> showLineWeave h)
    

main = do
    args <- getArgs
    withFile (head args) ReadMode $ \h ->
        catch (showLineWeave h) (\e -> if (isEOFError e) then return () else ioError e)

あー、匿名で投稿してしもた...

これ、題意からそれてますねぇ…一秒後との出力じゃないし…

こちらが題意に沿ったものになります

 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
module Main where

import System
import System.IO
import System.IO.Error
import Control.Monad
import Control.Concurrent

weaveIO :: (IO Bool) -> IO a -> (a -> IO a) -> IO a
weaveIO w m f = do
    a <- m 
    b <- w
    if b then f a
        else return a

getChar' :: IO Char
getChar' = getLine >>= return.head.(++" ")

nonBlockingGetCh :: IO (Maybe Char)
nonBlockingGetCh = do
    f <- hReady stdin
    if (not f) then return Nothing
        else do
            getChar' >>= return.Just

respondToKey :: IO Bool
respondToKey = nonBlockingGetCh >>= evalCh
    where
        evalCh Nothing = return True
        evalCh (Just 'q') = print "+++ quitting +++" >> return False
        evalCh (Just 'p') = waitForP ' '
        evalCh _ = return True
        waitForP 'p' = return True
        waitForP 'q' = print "+++ quitting +++" >> return False
        waitForP _ = print "+++ paused, hit 'p' again +++" >> getChar' >>= waitForP


showLineWeave :: IO ()
showLineWeave = weaveIO (respondToKey) (putStr "a" >> hFlush stdout >> threadDelay 1000000) (\_ -> showLineWeave)
    

main = showLineWeave

スレッド? そりゃ食いもんか?

BASIC の INKEY$ はブロッキングしないので、ゲームとか作るのに便利です。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
100 *Init           '*** 初期化 ***
110   cls
120   t$=time$
130   active=1      ' タイマー実行中フラグ
140 *MainLoop       '*** メインループ ***
150   ' キー入力を1文字読み込む
160   c$=inkey$
170   ' qで終了、pで停止/再開
180   if c$="q" then end
190   if c$="p" then active=1-active
200   ' time$ (=hh:mm:ss) が変化したらタイマー発動
210   if t$=time$ then goto *MainLoop
220   t$=time$
230   if active=1 then gosub *OnTimer
240   goto *MainLoop
250 *OnTimer        '*** タイマーイベントハンドラ ***
260   print "a"
270   return

無謀にも PostScript で。 GhostScript 7.07 のファイル関連のオペレーターのbug だか仕様だかに泣かされました...

どうにもならなかったので ghostscript 2本並列実行で文字入力と表示を役割分担しています。 プロセス間通信は名前付きファイルで... 激しくファイルの open/close をしますので実用的ではありません。

 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
%% Terminal 1
%   stty raw < /dev/tty
%   gs -sDEVICE=nullpage echoback.ps 

%% Terminal 2
%   gs -sDEVICE=nullpage pause.ps 

%------------------ echoback.ps ----- Cut Here ---------
%!PS

/Stdin (%stdin) (r) file def
(/tmp/test) (w) file closefile

% p: 112, q: 113
{
    Stdin read
    {
        (/tmp/test) (w) file dup 2 index write closefile
        113 eq { exit } if
    } {exit} ifelse
} loop
quit



%--------------------  pause.ps ------  Cut Here  ---------
%!PS

/Sleep { % OutputFlag Timer  Sleep   OutputFlag'
    realtime
    {
        realtime 1 index sub
        2 index ge {exit} if
        (/tmp/test) (r) file dup read 
        {
            exch closefile (/tmp/test) (w) file exch
            dup 113 eq { quit } if
            112 eq { 4 -1 roll not 4 1 roll } if
        } if
        closefile
    } loop
    pop
    pop
} def


true 
{
    1000 Sleep
    dup {(a) print flush} if
} loop
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script src="jquery-1.2.6.js"></script><script>
$(function(){
  var stdout = $(document.body);
  var pause  = false
  var loop   = setInterval(function(){ pause || stdout.append('a') }, 1000);
  $().bind('keydown', function(e){ e = String.fromCharCode(e.keyCode);
    'P' == e ? pause = !pause :
    'Q' == e ? clearInterval(loop) : 0;
  });
})
</script>

どう見てもCです。本当にありがとうございました。

標準ライブラリphobosの整備を急いでほしいです

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
private import std.stdio, std.c.time;

void main() {
    bool running = true;
    for(;;) {
        sleep(1);
        if(kbhit) {
            switch(getch) {
                case 'p':
                    running = !running;
                    break;
                case 'q':
                    return;
                default:
            }
        }
        if(running) {
            write('a');
            fflush(stdout);
        }
    }
}