{
  "id": "P095",
  "slug": "sensor-logger",
  "key": "P095-sensor-logger",
  "title": "Sensor logger",
  "summary": "Temperature readings kept in a ring buffer of the last N, shown slot by slot with where the next write goes and how many were overwritten, and the average, lowest and highest over a window sliding along them, the extremes by a monotonic queue.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P095 - Sensor logger\n\nTemperature readings kept in a ring buffer of the last N. The program\nshows the buffer slot by slot, with where the next write goes and how\nmany readings have been overwritten. It also gives the average, lowest\nand highest over a window sliding along the readings. It starts with ten\nreadings in a buffer of eight, so two are already gone.\n\n- `main.eml` - the menu, reading temperatures, the buffer and the window\n  table\n- `logger.eml` - the ring buffer, its readings in order, window sums and\n  window extremes\n\nHow each part works:\n\n- Readings are whole tenths of a degree (21.5 is 215), so sums and\n  averages are exact.\n- The ring buffer writes at the next slot and moves on, wrapping round to\n  slot 0. Once it is full, that slot held the oldest reading, which is\n  overwritten. As in the corpus case `ring-buffer-bounds-report`, every\n  index stays inside 0 to N - 1, so nothing can be read or written out of\n  range. The oldest reading sits where the next write will go.\n- Windows, as in the corpus case `sliding-window-slices`: only full\n  windows, len - k + 1 of them. Each window's sum adds the reading coming\n  in and takes off the one going out.\n- The lowest and highest come from a queue of positions whose readings\n  only rise (or only fall) from front to back. A new reading removes from\n  the back everything it beats, and the front leaves when it slides out of\n  the window. Each reading enters once and leaves at most once, so this is\n  one pass, whatever the window size.\n\nWhat is checked: menu choices 1 to 5; 1 to 20 readings at a time, each\nfrom -50.0 to 60.0 with at most one decimal; a window from 1 to the number\nof readings held; a capacity from 2 to 12. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the starting buffer, wrapped round, and windows of 3;\n- two more readings overwriting the two oldest; the one window of 8;\n- a new buffer of 4: three readings, then two more, the second\n  overwriting the oldest; windows of 2.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- the readings abc, 21.55, 70 and -50.1;\n- window sizes 0, 9 and x; capacities 13 and 1; then cancels.\n\nBuilt on the verified corpus cases `ring-buffer-bounds-report` (a fixed\nbuffer whose indices never leave it) and `sliding-window-slices` (full\nwindows, counted at both ends).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P095 sensor logger: temperature readings kept in a ring buffer of the\n# last N, the slots and where the next one goes, and the average, lowest\n# and highest over a window sliding along what is kept.\nimport logger\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef tenths_of(s):\n    # \"21.5\", \"-3\", \"0.4\" as whole tenths, or [] if not a reading from\n    # -50.0 to 60.0 with at most one decimal.\n    s => t\n    1 => sg\n    if len(t) > 1 and t[0] == \"-\":\n        t[1:len(t)] => t\n        -1 => sg\n    0 => k\n    while k < len(t) and t[k] != \".\":\n        k + 1 => k\n    t[0:k] => whole\n    \"0\" => frac\n    if k < len(t):\n        t[k + 1:len(t)] => frac\n    if len(whole) == 0 or len(whole) > 2 or len(frac) != 1:\n        return []\n    0 => n\n    for c in whole + frac:\n        if not (c >= \"0\" and c <= \"9\"):\n            return []\n        n * 10 + int(c) => n\n    sg * n => v\n    if v < -500 or v > 600:\n        return []\n    return [v]\n\ndef show(v):\n    if v < 0:\n        return \"-\" + show(0 - v)\n    return str(int((v - v % 10) / 10)) + \".\" + str(v % 10)\n\ndef average(s, k):\n    # s tenths over k readings, in degrees to two places, halves away from 0.\n    if s < 0:\n        return \"-\" + average(0 - s, k)\n    # hundredths: s / k tenths x 10 = 10 s / k\n    20 * s + k => n\n    2 * k => d\n    0 => q\n    while n >= d:\n        n - d => n\n        q + 1 => q\n    \"\" => z\n    if q % 100 < 10:\n        \"0\" => z\n    return str(int((q - q % 100) / 100)) + \".\" + z + str(q % 100)\n\ndef lpad(s, n):\n    while len(s) < n:\n        \" \" + s => s\n    return s\n\ndef add(r):\n    plain(input(\"readings (up to 20, like 21.5 22 -3.4)> \")) => s\n    if s == \"\":\n        \"Cancelled.\" ^0\n        return r\n    [] => vs\n    \"\" => w\n    True => ok\n    for c in s + \" \":\n        if c == \" \":\n            if w != \"\":\n                tenths_of(w) => v\n                if len(v) == 0:\n                    False => ok\n                else:\n                    vs + v => vs\n            \"\" => w\n        else:\n            w + c => w\n    if not ok or len(vs) == 0 or len(vs) > 20:\n        \"Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.\" ^0\n        return r\n    r[3] => before\n    for v in vs:\n        logger.put(r, v) => r\n    r[3] - before => gone\n    if gone == 0:\n        (\"Logged \" + str(len(vs)) + \"; nothing had to be overwritten.\") ^0\n    elif gone == 1:\n        (\"Logged \" + str(len(vs)) + \"; the oldest reading was overwritten to make room.\") ^0\n    else:\n        (\"Logged \" + str(len(vs)) + \"; the \" + str(gone) + \" oldest readings were overwritten to make room.\") ^0\n    return r\n\ndef buffer(r):\n    r[0] => slots\n    \"\" => line\n    for i in [0:len(slots) - 1]:\n        \"     -\" => cell\n        if i < r[2] or r[2] == len(slots):\n            lpad(show(slots[i]), 6) => cell\n        line + cell => line\n    (\"  slot \" + line) ^0\n    \"\" => arrow\n    for i in [0:len(slots) - 1]:\n        if i == r[1]:\n            arrow + \"     ^\" => arrow\n        else:\n            arrow + \"      \" => arrow\n    (\"       \" + logger_trim(arrow) + \"  next write\") ^0\n    (\"Holding \" + str(r[2]) + \" of \" + str(len(slots)) + \"; \" + str(r[3]) + \" overwritten so far.\") ^0\n    \"\" => seq\n    for v in logger.in_order(r):\n        if seq != \"\":\n            seq + \" \" => seq\n        seq + show(v) => seq\n    if seq == \"\":\n        \"Nothing logged yet.\" ^0\n    else:\n        (\"Oldest to newest: \" + seq) ^0\n\ndef logger_trim(s):\n    len(s) => j\n    while j > 0 and s[j - 1] == \" \":\n        j - 1 => j\n    return s[0:j]\n\ndef windows(r):\n    logger.in_order(r) => xs\n    if len(xs) == 0:\n        \"Nothing logged yet.\" ^0\n        return\n    while True:\n        plain(input(\"window size (1 to \" + str(len(xs)) + \")> \")) => s\n        if s == \"\":\n            \"Cancelled.\" ^0\n            return\n        0 => k\n        True => ok\n        if len(s) > 2:\n            False => ok\n        for c in s:\n            if c >= \"0\" and c <= \"9\":\n                k * 10 + int(c) => k\n            else:\n                False => ok\n        if ok and k >= 1 and k <= len(xs):\n            logger.window_sums(xs, k) => sums\n            logger.window_extremes(xs, k, False) => lows\n            logger.window_extremes(xs, k, True) => highs\n            \"  readings   average   lowest  highest\" ^0\n            for i in [0:len(sums) - 1]:\n                (\"  \" + lpad(str(i + 1), 2) + \" to \" + lpad(str(i + k), 2) + lpad(average(sums[i], k), 10) + lpad(show(lows[i]), 9) + lpad(show(highs[i]), 9)) ^0\n            if len(sums) == 1:\n                (\"1 window of \" + str(k) + \" in \" + str(len(xs)) + \" readings: it is all of them.\") ^0\n            else:\n                (str(len(sums)) + \" windows of \" + str(k) + \" in \" + str(len(xs)) + \" readings: each sum adds the reading coming in and drops the one going out.\") ^0\n            return\n        (\"Type a whole number from 1 to \" + str(len(xs)) + \".\") ^0\n\ndef resize(r):\n    plain(input(\"capacity (2 to 12)> \")) => s\n    if s == \"\":\n        \"Cancelled.\" ^0\n        return r\n    if s == \"2\" or s == \"3\" or s == \"4\" or s == \"5\" or s == \"6\" or s == \"7\" or s == \"8\" or s == \"9\" or s == \"10\" or s == \"11\" or s == \"12\":\n        (\"A new, empty buffer of \" + s + \".\") ^0\n        return logger.ring(int(s))\n    \"Type a whole number from 2 to 12.\" ^0\n    return r\n\nlogger.ring(8) => r\nfor v in [205, 210, 214, 221, 230, 226, 219, 212, 208, 201]:\n    logger.put(r, v) => r\n\n\"== Sensor logger ==\" ^0\n\"The last readings in a ring buffer, and a window sliding along them.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        add(r) => r\n    elif choice == \"2\":\n        buffer(r)\n    elif choice == \"3\":\n        windows(r)\n    elif choice == \"4\":\n        resize(r) => r\n    elif choice == \"5\":\n        False => running\n    else:\n        \"Pick a number from 1 to 5.\" ^0\n\"Bye.\" ^0\n",
      "python": "import logger\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef tenths_of(s):\n    t = s\n    sg = 1\n    if len(t) > 1 and t[0] == \"-\":\n        t = t[1:len(t)]\n        sg = -1\n    k = 0\n    while k < len(t) and t[k] != \".\":\n        k = k + 1\n    whole = t[0:k]\n    frac = \"0\"\n    if k < len(t):\n        frac = t[k + 1:len(t)]\n    if len(whole) == 0 or len(whole) > 2 or len(frac) != 1:\n        return []\n    n = 0\n    for c in whole + frac:\n        if not (c >= \"0\" and c <= \"9\"):\n            return []\n        n = n * 10 + int(c)\n    v = sg * n\n    if v < -500 or v > 600:\n        return []\n    return [v]\n\ndef show(v):\n    if v < 0:\n        return \"-\" + show(0 - v)\n    return str(int((v - v % 10) / 10)) + \".\" + str(v % 10)\n\ndef average(s, k):\n    if s < 0:\n        return \"-\" + average(0 - s, k)\n    n = 20 * s + k\n    d = 2 * k\n    q = 0\n    while n >= d:\n        n = n - d\n        q = q + 1\n    z = \"\"\n    if q % 100 < 10:\n        z = \"0\"\n    return str(int((q - q % 100) / 100)) + \".\" + z + str(q % 100)\n\ndef lpad(s, n):\n    while len(s) < n:\n        s = \" \" + s\n    return s\n\ndef add(r):\n    s = plain(input(\"readings (up to 20, like 21.5 22 -3.4)> \"))\n    if s == \"\":\n        print(\"Cancelled.\")\n        return r\n    vs = []\n    w = \"\"\n    ok = True\n    for c in s + \" \":\n        if c == \" \":\n            if w != \"\":\n                v = tenths_of(w)\n                if len(v) == 0:\n                    ok = False\n                else:\n                    vs = vs + v\n            w = \"\"\n        else:\n            w = w + c\n    if not ok or len(vs) == 0 or len(vs) > 20:\n        print(\"Type 1 to 20 readings from -50.0 to 60.0, at most one decimal each.\")\n        return r\n    before = r[3]\n    for v in vs:\n        r = logger.put(r, v)\n    gone = r[3] - before\n    if gone == 0:\n        print(\"Logged \" + str(len(vs)) + \"; nothing had to be overwritten.\")\n    elif gone == 1:\n        print(\"Logged \" + str(len(vs)) + \"; the oldest reading was overwritten to make room.\")\n    else:\n        print(\"Logged \" + str(len(vs)) + \"; the \" + str(gone) + \" oldest readings were overwritten to make room.\")\n    return r\n\ndef buffer(r):\n    slots = r[0]\n    line = \"\"\n    for i in range(0, len(slots)):\n        cell = \"     -\"\n        if i < r[2] or r[2] == len(slots):\n            cell = lpad(show(slots[i]), 6)\n        line = line + cell\n    print(\"  slot \" + line)\n    arrow = \"\"\n    for i in range(0, len(slots)):\n        if i == r[1]:\n            arrow = arrow + \"     ^\"\n        else:\n            arrow = arrow + \"      \"\n    print(\"       \" + logger_trim(arrow) + \"  next write\")\n    print(\"Holding \" + str(r[2]) + \" of \" + str(len(slots)) + \"; \" + str(r[3]) + \" overwritten so far.\")\n    seq = \"\"\n    for v in logger.in_order(r):\n        if seq != \"\":\n            seq = seq + \" \"\n        seq = seq + show(v)\n    if seq == \"\":\n        print(\"Nothing logged yet.\")\n    else:\n        print(\"Oldest to newest: \" + seq)\n\ndef logger_trim(s):\n    j = len(s)\n    while j > 0 and s[j - 1] == \" \":\n        j = j - 1\n    return s[0:j]\n\ndef windows(r):\n    xs = logger.in_order(r)\n    if len(xs) == 0:\n        print(\"Nothing logged yet.\")\n        return\n    while True:\n        s = plain(input(\"window size (1 to \" + str(len(xs)) + \")> \"))\n        if s == \"\":\n            print(\"Cancelled.\")\n            return\n        k = 0\n        ok = True\n        if len(s) > 2:\n            ok = False\n        for c in s:\n            if c >= \"0\" and c <= \"9\":\n                k = k * 10 + int(c)\n            else:\n                ok = False\n        if ok and k >= 1 and k <= len(xs):\n            sums = logger.window_sums(xs, k)\n            lows = logger.window_extremes(xs, k, False)\n            highs = logger.window_extremes(xs, k, True)\n            print(\"  readings   average   lowest  highest\")\n            for i in range(0, len(sums)):\n                print(\"  \" + lpad(str(i + 1), 2) + \" to \" + lpad(str(i + k), 2) + lpad(average(sums[i], k), 10) + lpad(show(lows[i]), 9) + lpad(show(highs[i]), 9))\n            if len(sums) == 1:\n                print(\"1 window of \" + str(k) + \" in \" + str(len(xs)) + \" readings: it is all of them.\")\n            else:\n                print(str(len(sums)) + \" windows of \" + str(k) + \" in \" + str(len(xs)) + \" readings: each sum adds the reading coming in and drops the one going out.\")\n            return\n        print(\"Type a whole number from 1 to \" + str(len(xs)) + \".\")\n\ndef resize(r):\n    s = plain(input(\"capacity (2 to 12)> \"))\n    if s == \"\":\n        print(\"Cancelled.\")\n        return r\n    if s == \"2\" or s == \"3\" or s == \"4\" or s == \"5\" or s == \"6\" or s == \"7\" or s == \"8\" or s == \"9\" or s == \"10\" or s == \"11\" or s == \"12\":\n        print(\"A new, empty buffer of \" + s + \".\")\n        return logger.ring(int(s))\n    print(\"Type a whole number from 2 to 12.\")\n    return r\n\nr = logger.ring(8)\nfor v in [205, 210, 214, 221, 230, 226, 219, 212, 208, 201]:\n    r = logger.put(r, v)\nprint(\"== Sensor logger ==\")\nprint(\"The last readings in a ring buffer, and a window sliding along them.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        r = add(r)\n    elif choice == \"2\":\n        buffer(r)\n    elif choice == \"3\":\n        windows(r)\n    elif choice == \"4\":\n        r = resize(r)\n    elif choice == \"5\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 5.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "logger.eml",
      "eml": "# P095 sensor logger - readings in a ring buffer that keeps the last N and\n# overwrites the oldest, and statistics over a window sliding along them.\n# Readings are whole tenths of a degree (21.5 is 215). A buffer is\n# [slots, next slot to write, how many are held, how many overwritten].\n\ndef ring(n):\n    return [[0] * n, 0, 0, 0]\n\ndef put(r, value):\n    # Write at the next slot and move it on, wrapping round; when the\n    # buffer is full that slot held the oldest reading, which is lost. As in\n    # the corpus case ring-buffer-bounds-report, every index stays inside\n    # 0 .. N-1, so no read or write can go out of range.\n    r[0] => slots\n    len(slots) => n\n    value => slots[r[1]]\n    (r[1] + 1) % n => nxt\n    r[2] => held\n    r[3] => lost\n    if held < n:\n        held + 1 => held\n    else:\n        lost + 1 => lost\n    return [slots, nxt, held, lost]\n\ndef in_order(r):\n    # The held readings, oldest first: they start where the next write goes\n    # once the buffer is full, at slot 0 before that.\n    r[0] => slots\n    len(slots) => n\n    0 => start\n    if r[2] == n:\n        r[1] => start\n    [0] * r[2] => out\n    for k in [0:r[2] - 1]:\n        slots[(start + k) % n] => out[k]\n    return out\n\ndef window_sums(xs, k):\n    # The sum of every full window of k in a row - as in the corpus case\n    # sliding-window-slices, exactly len(xs) - k + 1 of them - by adding the\n    # reading coming in and taking off the one going out.\n    [] => out\n    if k > len(xs):\n        return out\n    0 => s\n    for i in [0:k - 1]:\n        s + xs[i] => s\n    out + [s] => out\n    for i in [k:len(xs) - 1]:\n        s + xs[i] - xs[i - k] => s\n        out + [s] => out\n    return out\n\ndef window_extremes(xs, k, biggest):\n    # The largest (or smallest) of every full window, in one pass: a queue\n    # of positions whose readings only fall (or only rise) from front to\n    # back. A newcomer removes everything it beats from the back; the front\n    # leaves when it slides out of the window. Each reading goes in once and\n    # out at most once.\n    [] => out\n    [0] * len(xs) => q\n    0 => head\n    0 => tail\n    for i in [0:len(xs) - 1]:\n        True => going\n        while going and tail > head:\n            q[tail - 1] => j\n            if (biggest and xs[j] <= xs[i]) or (not biggest and xs[j] >= xs[i]):\n                tail - 1 => tail\n            else:\n                False => going\n        i => q[tail]\n        tail + 1 => tail\n        if q[head] <= i - k:\n            head + 1 => head\n        if i >= k - 1:\n            out + [xs[q[head]]] => out\n    return out\n",
      "python": "def ring(n):\n    return [[0] * n, 0, 0, 0]\n\ndef put(r, value):\n    slots = r[0]\n    n = len(slots)\n    slots[r[1]] = value\n    nxt = (r[1] + 1) % n\n    held = r[2]\n    lost = r[3]\n    if held < n:\n        held = held + 1\n    else:\n        lost = lost + 1\n    return [slots, nxt, held, lost]\n\ndef in_order(r):\n    slots = r[0]\n    n = len(slots)\n    start = 0\n    if r[2] == n:\n        start = r[1]\n    out = [0] * r[2]\n    for k in range(0, r[2]):\n        out[k] = slots[(start + k) % n]\n    return out\n\ndef window_sums(xs, k):\n    out = []\n    if k > len(xs):\n        return out\n    s = 0\n    for i in range(0, k):\n        s = s + xs[i]\n    out = out + [s]\n    for i in range(k, len(xs)):\n        s = s + xs[i] - xs[i - k]\n        out = out + [s]\n    return out\n\ndef window_extremes(xs, k, biggest):\n    out = []\n    q = [0] * len(xs)\n    head = 0\n    tail = 0\n    for i in range(0, len(xs)):\n        going = True\n        while going and tail > head:\n            j = q[tail - 1]\n            if biggest and xs[j] <= xs[i] or not biggest and xs[j] >= xs[i]:\n                tail = tail - 1\n            else:\n                going = False\n        q[tail] = i\n        tail = tail + 1\n        if q[head] <= i - k:\n            head = head + 1\n        if i >= k - 1:\n            out = out + [xs[q[head]]]\n    return out\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n1\nabc\n1\n21.55\n1\n70\n1\n-50.1\n1\n\n3\n0\n9\nx\n\n4\n13\n4\n1\n4\n\n5\n",
      "screen": "== Sensor logger ==\nThe last readings in a ring buffer, and a window sliding along them.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 0\nPick a number from 1 to 5.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> x\nPick a number from 1 to 5.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 1\nreadings (up to 20, like 21.5 22 -3.4)> abc\nType 1 to 20 readings from -50.0 to 60.0, at most one decimal each.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 1\nreadings (up to 20, like 21.5 22 -3.4)> 21.55\nType 1 to 20 readings from -50.0 to 60.0, at most one decimal each.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 1\nreadings (up to 20, like 21.5 22 -3.4)> 70\nType 1 to 20 readings from -50.0 to 60.0, at most one decimal each.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 1\nreadings (up to 20, like 21.5 22 -3.4)> -50.1\nType 1 to 20 readings from -50.0 to 60.0, at most one decimal each.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 1\nreadings (up to 20, like 21.5 22 -3.4)> \nCancelled.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 3\nwindow size (1 to 8)> 0\nType a whole number from 1 to 8.\nwindow size (1 to 8)> 9\nType a whole number from 1 to 8.\nwindow size (1 to 8)> x\nType a whole number from 1 to 8.\nwindow size (1 to 8)> \nCancelled.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 4\ncapacity (2 to 12)> 13\nType a whole number from 2 to 12.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 4\ncapacity (2 to 12)> 1\nType a whole number from 2 to 12.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 4\ncapacity (2 to 12)> \nCancelled.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "2\n3\n3\n1\n19.5 -2.3\n2\n3\n8\n4\n4\n2\n1\n1 2 3\n1\n4.5 5\n2\n3\n2\n5\n",
      "screen": "== Sensor logger ==\nThe last readings in a ring buffer, and a window sliding along them.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 2\n  slot   20.8  20.1  21.4  22.1  23.0  22.6  21.9  21.2\n                        ^  next write\nHolding 8 of 8; 2 overwritten so far.\nOldest to newest: 21.4 22.1 23.0 22.6 21.9 21.2 20.8 20.1\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 3\nwindow size (1 to 8)> 3\n  readings   average   lowest  highest\n   1 to  3     22.17     21.4     23.0\n   2 to  4     22.57     22.1     23.0\n   3 to  5     22.50     21.9     23.0\n   4 to  6     21.90     21.2     22.6\n   5 to  7     21.30     20.8     21.9\n   6 to  8     20.70     20.1     21.2\n6 windows of 3 in 8 readings: each sum adds the reading coming in and drops the one going out.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 1\nreadings (up to 20, like 21.5 22 -3.4)> 19.5 -2.3\nLogged 2; the 2 oldest readings were overwritten to make room.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 2\n  slot   20.8  20.1  19.5  -2.3  23.0  22.6  21.9  21.2\n                                    ^  next write\nHolding 8 of 8; 4 overwritten so far.\nOldest to newest: 23.0 22.6 21.9 21.2 20.8 20.1 19.5 -2.3\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 3\nwindow size (1 to 8)> 8\n  readings   average   lowest  highest\n   1 to  8     18.35     -2.3     23.0\n1 window of 8 in 8 readings: it is all of them.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 4\ncapacity (2 to 12)> 4\nA new, empty buffer of 4.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 2\n  slot      -     -     -     -\n            ^  next write\nHolding 0 of 4; 0 overwritten so far.\nNothing logged yet.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 1\nreadings (up to 20, like 21.5 22 -3.4)> 1 2 3\nLogged 3; nothing had to be overwritten.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 1\nreadings (up to 20, like 21.5 22 -3.4)> 4.5 5\nLogged 2; the oldest reading was overwritten to make room.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 2\n  slot    5.0   2.0   3.0   4.5\n                  ^  next write\nHolding 4 of 4; 1 overwritten so far.\nOldest to newest: 2.0 3.0 4.5 5.0\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 3\nwindow size (1 to 4)> 2\n  readings   average   lowest  highest\n   1 to  2      2.50      2.0      3.0\n   2 to  3      3.75      3.0      4.5\n   3 to  4      4.75      4.5      5.0\n3 windows of 2 in 4 readings: each sum adds the reading coming in and drops the one going out.\n\n1) add readings  2) the buffer  3) window statistics  4) new capacity  5) quit\nchoice> 5\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "ring-buffer-bounds-report",
      "caseId": "196-ring-buffer-bounds-report",
      "title": "Ring buffer: telling a bad read from a bad write"
    },
    {
      "slug": "sliding-window-slices",
      "caseId": "210-sliding-window-slices",
      "title": "Sliding windows: the bug is always at the ends"
    }
  ],
  "updated": "2026-10-15"
}
