exercism / wren

Exercism exercises in Wren.
https://exercism.org/tracks/wren
MIT License
5 stars 14 forks source link

Building a training set of tags for wren #148

Closed ErikSchierboom closed 10 months ago

ErikSchierboom commented 10 months ago

Hello lovely maintainers :wave:

We've recently added "tags" to student's solutions. These express the constructs, paradigms and techniques that a solution uses. We are going to be using these tags for lots of things including filtering, pointing a student to alternative approaches, and much more.

In order to do this, we've built out a full AST-based tagger in C#, which has allowed us to do things like detect recursion or bit shifting. We've set things up so other tracks can do the same for their languages, but its a lot of work, and we've determined that actually it may be unnecessary. Instead we think that we can use machine learning to achieve tagging with good enough results. We've fine-tuned a model that can determine the correct tags for C# from the examples with a high success rate. It's also doing reasonably well in an untrained state for other languages. We think that with only a few examples per language, we can potentially get some quite good results, and that we can then refine things further as we go.

I released a new video on the Insiders page that talks through this in more detail.

We're going to be adding a fully-fledged UI in the coming weeks that allow maintainers and mentors to tag solutions and create training sets for the neural networks, but to start with, we're hoping you would be willing to manually tag 20 solutions for this track. In this post we'll add 20 comments, each with a student's solution, and the tags our model has generated. Your mission (should you choose to accept it) is to edit the tags on each issue, removing any incorrect ones, and add any that are missing. In order to build one model that performs well across languages, it's best if you stick as closely as possible to the C# tags as you can. Those are listed here. If you want to add extra tags, that's totally fine, but please don't arbitrarily reword existing tags, even if you don't like what Erik's chosen, as it'll just make it less likely that your language gets the correct tags assigned by the neural network.


To summarise - there are two paths forward for this issue:

  1. You're up for helping: Add a comment saying you're up for helping. Update the tags some time in the next few days. Add a comment when you're done. We'll then add them to our training set and move forward.
  2. You not up for helping: No problem! Just please add a comment letting us know :)

If you tell us you're not able/wanting to help or there's no comment added, we'll automatically crowd-source this in a week or so.

Finally, if you have questions or want to discuss things, it would be best done on the forum, so the knowledge can be shared across all maintainers in all tracks.

Thanks for your help! :blue_heart:


Note: Meta discussion on the forum

ErikSchierboom commented 10 months ago

Exercise: resistor-color

Code

var COLORS = [
  "black",
  "brown",
  "red",
  "orange",
  "yellow",
  "green",
  "blue",
  "violet",
  "grey",
  "white"
]

class Resistor {
  static colorCode(color) {
    return COLORS.indexOf(color)
  }
}

Tags:

construct:class
construct:method
construct:return
construct:static
construct:variable
construct:visibility-modifiers
paradigm:object-oriented
ErikSchierboom commented 10 months ago

Exercise: hamming

Code

class Hamming {
  static compute(left, right) {
    if (left.count != right.count) {
      if (left.count == 0) Fiber.abort("left strand must not be empty")
      if(right.count == 0) Fiber.abort("right strand must not be empty")
      Fiber.abort("left and right strands must be of equal length")
    }

    return (0...left.count).reduce(0) { |distance, i|
      return distance + (left[i] == right[i] ? 0 : 1)
    }
  }
}

Tags:

construct:add
construct:class
construct:if
construct:index
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:ternary
construct:throw
construct:visibility-modifiers
paradigm:functional
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
ErikSchierboom commented 10 months ago

Exercise: triangle

Code

class Triangle {
  construct new(a, b, c) {
    _sides = [a, b, c]
  }

  passesTriangleInequality {
    var sides = _sides[0..-1].sort()
    // destructure would really help
    var a = sides[0]
    var b = sides[1]
    var c = sides[2]

    return a > 0 && a + b >= c
  }

  matchingSides {
    // destructure would really help
    var a = _sides[0]
    var b = _sides[1]
    var c = _sides[2]

    if (a == b && b == c && a == c) {
      return 3
    } else if (a == b || a == c || b == c) {
      return 2
    } else {
      return 0
    }
  }

  isEquilateral {
    return passesTriangleInequality && matchingSides == 3
  }

  isIsosceles {
    return passesTriangleInequality && matchingSides == 2 || isEquilateral
  }

  isScalene {
    return passesTriangleInequality && matchingSides == 0
  }
}

Tags:

construct:add
construct:assignment
construct:boolean
construct:class
construct:comment
construct:field
construct:if
construct:indexing
construct:invocation
construct:logical-and
construct:logical-or
construct:method
construct:number
construct:parameter
construct:return
construct:variable
construct:visibility-modifiers
paradigm:imperative
paradigm:object-oriented
technique:boolean-logic
ErikSchierboom commented 10 months ago

Exercise: flatten-array

Code

class Flatten {
  static flatten(seq) {
    return seq
      .reduce([]) {|flattened, elem|
        if (elem is Sequence) {
          flattened.addAll(flatten(elem))
        } else {
          flattened.add(elem)
        }
        return flattened
      }
      .where {|elem| elem != null}
      .toList
  }
}

Tags:

construct:class
construct:constructor
construct:method
construct:parameter
construct:return
construct:string
construct:throw
construct:try
construct:visibility-modifiers
paradigm:object-oriented
technique:exceptions
ErikSchierboom commented 10 months ago

Exercise: allergies

Code

var ALLERGENS = [
    "eggs",
    "peanuts",
    "shellfish",
    "strawberries",
    "tomatoes",
    "chocolate",
    "pollen",
    "cats",
]
//
class Allergies {
  construct new(value) {
    _allergens = (0...ALLERGENS.count).where {|i| ((value >> i) & 1) == 1}
                                      .map   {|i| ALLERGENS[i]}
  }
  list() {_allergens.toList}
  allergicTo(name) {_allergens.contains(name)}
}

Tags:

construct:class
construct:comment
construct:field
construct:indexing
construct:initializer
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:list
construct:method
construct:number
construct:parameter
construct:property
construct:range
construct:shift-right
construct:string
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:object-oriented
technique:bit-manipulation
technique:bit-shifting
technique:higher-order-functions
uses:List
ErikSchierboom commented 10 months ago

Exercise: darts

Code

class Darts {
  static score(x, y) {
    var distance = (x * x + y * y).sqrt

    if (distance <= 1) {
      return 10
    } else if (distance <= 5) {
      return 5
    } else if (distance <= 10) {
      return 1
    } else {
      return 0
    }
  }
}

Tags:

construct:add
construct:class
construct:constructor
construct:else
construct:if
construct:int
construct:integral-number
construct:invocation
construct:method
construct:multiply
construct:number
construct:parameter
construct:return
construct:variable
construct:visibility-modifiers
paradigm:object-oriented
ErikSchierboom commented 10 months ago

Exercise: armstrong-numbers

Code

class Number {
  static isArmstrong(n) {
    var str = n.toString
    var m = str.
      map {|x| Num.fromString(x)}.
      reduce(0, Fn.new {|acc, x| acc + x.pow(str.codePoints.count)})
    return m == n      
  }
}

Tags:

construct:add
construct:class
construct:implicit-conversion
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:map
construct:method
construct:number
construct:parameter
construct:return
construct:static-method
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:object-oriented
technique:higher-order-functions
ErikSchierboom commented 10 months ago

Exercise: anagram

Code

class Anagram {
  static find(word, list) {
    return list
      .where { |w| downcase(w) != downcase(word) }
      .where { |w| normalize(w) == normalize(word) }
      .toList
  }

  static normalize(word) {
    return word
      .map { |c| downcase(c) }
      .toList
      .sort { |a, b| a.bytes[0] < b.bytes[0] }
      .join()
  }

  static downcase(s) {
    return s.map { |c|
      if (("A".bytes[0].."Z".bytes[0]).contains(c.bytes[0])) {
        return String.fromByte(c.bytes[0] + ("a".bytes[0] - "A".bytes[0]))
      }
      return c
    }.join()
  }
}

Tags:

construct:add
construct:boolean
construct:class
construct:if
construct:index
construct:initializer
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:list
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:subtract
construct:throw
construct:to-list
construct:visibility-modifiers
paradigm:functional
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
uses:List
ErikSchierboom commented 10 months ago

Exercise: minesweeper

Code

class Minesweeper {
  static bound(y, n) { 0.max(y - 1)..(y + 1).min(n-1)}

  static annotateMine(ng, x, y) {
    ng[y][x] = "*"
    bound(y, ng.count).each {|y|
      bound(x, ng[y].count).each {|x|
        if (ng[y][x] == " ") {
          ng[y][x] = 1
        } else if (ng[y][x] != "*") {
          ng[y][x] = ng[y][x] + 1
        }
      }
    }
  }
  static annotate(grid) {
    var ng = grid.map {|line| List.filled(line.count, " ")}.toList

    for (y in 0...grid.count) {
      for (x in 0...grid[0].count) {
        if (grid[y][x] == "*") {
          annotateMine(ng, x, y)
        }
      }
    }

    return ng.map {|line| line.join()}.toList
  }
}

Tags:

construct:add
construct:assignment
construct:class
construct:for-loop
construct:if
construct:indexing
construct:initializer
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:list
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:subtract
construct:throw
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
technique:looping
uses:List
ErikSchierboom commented 10 months ago

Exercise: protein-translation

Code

var TRANSLATIONS = {
  "AUG": "Methionine",
  "UUU": "Phenylalanine",
  "UUC": "Phenylalanine",
  "UUA": "Leucine",
  "UUG": "Leucine",
  "UCU": "Serine",
  "UCC": "Serine",
  "UCA": "Serine",
  "UCG": "Serine",
  "UAU": "Tyrosine",
  "UAC": "Tyrosine",
  "UGU": "Cysteine",
  "UGC": "Cysteine",
  "UGG": "Tryptophan",
  "UAA": "Stop",
  "UAG": "Stop",
  "UGA": "Stop"
}

class Tools {
  static translate() { [] }

  static translate(strand) {
    var proteins = []
    for (codon in codons(strand)) {
      var protein = TRANSLATIONS[codon]
      if (!protein) Fiber.abort("Invalid codon")
      if (protein ==  "Stop") break
      proteins.add(TRANSLATIONS[codon])
    }
    return proteins
  }

  static codons(strand) {
    var codons = []
    while (!strand.isEmpty) {
      codons.add(strand.take(3).join())
      strand = strand.skip(3)
    }
    return codons
  }
}

Tags:

construct:assignment
construct:boolean
construct:break
construct:class
construct:dictionary
construct:for-loop
construct:if
construct:invocation
construct:method
construct:parameter
construct:return
construct:string
construct:throw
construct:variable
construct:visibility-modifiers
construct:while-loop
paradigm:imperative
paradigm:object-oriented
technique:exceptions
technique:looping
ErikSchierboom commented 10 months ago

Exercise: raindrops

Code

class Raindrops {
  static convert(n) {
    var result = ""
    if (n % 3 == 0) result = result + "Pling"
    if (n % 5 == 0) result = result + "Plang"
    if (n % 7 == 0) result = result + "Plong"
    return result == "" ? n.toString : result
  }
}

Tags:

construct:add
construct:assignment
construct:class
construct:if
construct:int
construct:integral-number
construct:invocation
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:ternary
construct:variable
construct:visibility-modifiers
paradigm:imperative
paradigm:object-oriented
ErikSchierboom commented 10 months ago

Exercise: resistor-color-trio

Code

var COLORS = {
  "black": 0,
  "brown": 1,
  "red": 2,
  "orange": 3,
  "yellow": 4,
  "green": 5,
  "blue": 6,
  "violet": 7,
  "grey": 8,
  "white": 9,
}

class ResistorColorTrio {
  static value(color) {
    var v = COLORS[color]
    if (v == null) Fiber.abort("invalid color")
    return v
  }
  construct new(colors) {
    var nums = colors.map {|x| this.type.value(x) }.toList
    _ohms = (nums[0] * 10 + nums[1]) * 10.pow(nums[2])
    _unit = "ohms"
    if (_ohms >= 1000) {
      _ohms = _ohms / 1000
      _unit = "kiloohms"
    }
  }
  label { "Resistor value: %(_ohms) %(_unit)" }
}

Tags:

construct:assignment
construct:class
construct:dictionary
construct:divide
construct:field
construct:floating-point-number
construct:if
construct:implicit-conversion
construct:indexing
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:list
construct:map
construct:method
construct:multiply
construct:null
construct:nullability
construct:number
construct:parameter
construct:return
construct:string
construct:throw
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
uses:List
uses:Map
ErikSchierboom commented 10 months ago

Exercise: run-length-encoding

Code

class RLE {
  static encode(s) {
    var last
    var count
    var encoded = ""

    var append = Fn.new {
      encoded = encoded + (count > 1 ? "%(count.toString)" : "") + last
    }

    for (c in s) {
      if (c != last) {
        if (last) append.call()
        count = 0
      }

      count = count + 1
      last = c
    }

    if (last) append.call()

    return encoded
  }

  static decode(s) {
    var decoded = ""
    var i = 0

    while (i < s.count) {
      if (isNum(s[i])) {
        var count = ""
        while (isNum(s[i])) {
          count = count + s[i]
          i = i + 1
        }
        count = Num.fromString(count)

        decoded = decoded + List.filled(count, s[i]).join()
      } else {
        decoded = decoded + s[i]
      }

      i = i + 1
    }

    return decoded
  }

  static isNum(c) { "0123456789".contains(c) }
}

Tags:

construct:add
construct:assignment
construct:boolean
construct:class
construct:for-loop
construct:if
construct:implicit-conversion
construct:indexed-access
construct:initializer
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:ternary
construct:variable
construct:visibility-modifiers
construct:while-loop
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:higher-order-functions
technique:looping
ErikSchierboom commented 10 months ago

Exercise: twelve-days

Code

var GIFT = [
  "a Partridge in a Pear Tree",
  "two Turtle Doves",
  "three French Hens",
  "four Calling Birds",
  "five Gold Rings",
  "six Geese-a-Laying",
  "seven Swans-a-Swimming",
  "eight Maids-a-Milking",
  "nine Ladies Dancing",
  "ten Lords-a-Leaping",
  "eleven Pipers Piping",
  "twelve Drummers Drumming"
]

var NTH = {
  1: "first",
  2: "second",
  3: "third",
  4: "fourth",
  5: "fifth",
  6: "sixth",
  7: "seventh",
  8: "eighth",
  9: "ninth",
  10: "tenth",
  11: "eleventh",
  12: "twelfth"
}

class TwelveDays {
  static recite(verse) { "%(opening(verse)) %(gifts(verse)).\n" }
  static recite(start, end) { (start..end).map { |day| recite(day) }.join("\n") }

  static opening(day) { "On the %(NTH[day]) day of Christmas my true love gave to me:" }

  static gifts(day) {
    if (day == 1) return GIFT[0]
    return "%(GIFT[(day - 1)..1].join(", ")), and %(GIFT[0])"
  }
}

Tags:

construct:class
construct:if
construct:indexing
construct:initializer
construct:int
construct:interval
construct:invocation
construct:lambda
construct:list
construct:map
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:subtract
construct:throw
construct:using
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
ErikSchierboom commented 10 months ago

Exercise: twelve-days

Code

var GIFTS = [
  "a Partridge in a Pear Tree.",
  "two Turtle Doves, and",
  "three French Hens,",
  "four Calling Birds,",
  "five Gold Rings,",
  "six Geese-a-Laying,",
  "seven Swans-a-Swimming,",
  "eight Maids-a-Milking,",
  "nine Ladies Dancing,",
  "ten Lords-a-Leaping,",
  "eleven Pipers Piping,",
  "twelve Drummers Drumming,",
]

var ORDINALS = [
  "first", "second", "third", "fourth", "fifth", "sixth",
  "seventh", "eighth", "ninth", "tenth", "eleventh", "twelfth"
]

class TwelveDays {
  static recite(verse) {
    var head = "On the %(ORDINALS[verse - 1]) day of Christmas my true love gave to me: "
    var tail = GIFTS[(verse - 1)..0].join(" ") + "\n"
    return head + tail
  }

  static recite(start, end) {
    return (start..end).map {|n| recite(n) }.join("\n")
  }
}

Tags:

construct:add
construct:class
construct:field
construct:index
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:list
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:subtract
construct:throw
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:object-oriented
technique:exceptions
technique:higher-order-functions
technique:recursion
uses:List<T>
ErikSchierboom commented 10 months ago

Exercise: luhn

Code

/* This is every 2nd digit index is "doubled"
 * idx 0: val = key * 2 > 10 ? key * 2 - 9 : key * 2
 * idx 1: val = key
 */
var DigitValue = [
  {0:0, 1:2, 2:4, 3:6, 4:8, 5:1, 6:3, 7:5, 8:7, 9:9},
  {0:0, 1:1, 2:2, 3:3, 4:4, 5:5, 6:6, 7:7, 8:8, 9:9},
]

class Luhn {
  static valid(number) {
    number = number.replace(" ", "")
    if (number.count < 2) return false
    if (number.any {|c| !"0123456789".contains(c)}) return false

    var idx = 0
    var sum = number[-1..0]
      .map {|char| Num.fromString(char)}
      .reduce(0) {|sum, digit| sum + DigitValue[(idx = idx + 1) % 2][digit]}

    return sum % 10 == 0
  }

}

Tags:

construct:add
construct:assignment
construct:boolean
construct:class
construct:comment
construct:if
construct:implicit-conversion
construct:indexed-access
construct:initializer
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:map
construct:method
construct:number
construct:object
construct:parameter
construct:return
construct:string
construct:ternary
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:higher-order-functions
ErikSchierboom commented 10 months ago

Exercise: word-count

Code

class Words {
    static sanitize(word) {
        return word
            .trim("!\"#$\%&'()*+ -./:;<=>?@[\\]^_`{|}~")
            .trim()
    }
    static sanitizeSentence(s) {
        return s
            .replace(",", " ")
            .replace("\n", " ")
            .replace("\t", " ")
    }

    static lowerCase(c) {
        var ord = c.codePoints[0]
            if (ord >= 65 && ord <= 90) {
                return String.fromCodePoint(ord + 32)
            }
        return c
    }
    static toLowerCase(s) {
        return s.map {|c| lowerCase(c)}.join("")
    }
    static calculateWordFrequency(sanitized) {
        var dict = {}
        for (word in sanitized.split(" ")) {
            var lcTrim = sanitize(toLowerCase(word.trim().trim("'").trim()))
            if (lcTrim != "") {
                if (dict.containsKey(lcTrim)) {
                    dict[lcTrim] = dict[lcTrim] + 1
                } else {
                    dict[lcTrim] = 1
                }
            }
        }
        return dict
    }
    static count(s) {
        return calculateWordFrequency(sanitizeSentence(s))
    }
}

Tags:

construct:add
construct:assignment
construct:boolean
construct:class
construct:dictionary
construct:for-loop
construct:if
construct:implicit-conversion
construct:indexing
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:logical-and
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:subtract
construct:throw
construct:variable
construct:visibility-modifiers
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:boolean-logic
technique:exceptions
technique:higher-order-functions
technique:looping
uses:Dictionary
ErikSchierboom commented 10 months ago

Exercise: circular-buffer

Code

var BufferFullError  = "Buffer full"
var BufferEmptyError = "Buffer empty"

class CircularBuffer {
  construct new(size) {
    _size = size
    _buffer = []
  }

  isFull  { _buffer.count == _size }
  isEmpty { _buffer.isEmpty }

  clear() { _buffer.clear() }

  write(elem) {
    if (isFull) Fiber.abort(BufferFullError)
    _buffer.add(elem)
  }

  read() {
    if (isEmpty) Fiber.abort(BufferEmptyError)
    return _buffer.removeAt(0)
  }

  forceWrite(elem) {
    if (isFull) read()
    write(elem)
  }
}

Tags:

construct:class
construct:field
construct:if
construct:index
construct:invocation
construct:method
construct:number
construct:parameter
construct:return
construct:string
construct:variable
construct:visibility-modifiers
paradigm:object-oriented
ErikSchierboom commented 10 months ago

Exercise: sieve

Code

// The key to making this fast is to limit the number of loop iterations.

class Sieve {
  construct new(limit) {
    _limit = limit
  }

  primes {
    // only find the primes onece
    if (_primes == null) {
      if (_limit < 2) {
        _primes = []
      } else {
        _candidates = List.filled(_limit+1, true)
        // 2 is the first prime
        markMultiples_(2)
        // 3 is the second prime: this and all subsequent primes are odd numbers
        var prime = 3
        while (prime <= _limit) {
          if (_candidates[prime]) markMultiples_(prime)
          prime = prime + 2
        }
        // and collect the prime numbers
        _primes = (2.._limit).where {|i| _candidates[i]}.toList
      }
    }
    return _primes
  }

  markMultiples_(prime) {
    // all multiples `prime * k` where k < prime have already been marked as non-prime
    var idx = prime * prime
    // for prime > 2, all even multiples are already non-prime
    var step = (prime == 2) ? 2 : 2 * prime
    while (idx <= _limit) {
      _candidates[idx] = false
      idx = idx + step
    }
  }
}

Tags:

construct:add
construct:assignment
construct:boolean
construct:class
construct:comment
construct:constructor
construct:field
construct:if
construct:index
construct:initializer
construct:int
construct:integral-number
construct:invocation
construct:lambda
construct:list
construct:logical-and
construct:loop
construct:method
construct:multiply
construct:null
construct:nullability
construct:number
construct:parameter
construct:return
construct:ternary
construct:variable
construct:visibility-modifiers
construct:while-loop
paradigm:functional
paradigm:imperative
paradigm:object-oriented
technique:boolean-logic
technique:higher-order-functions
technique:looping
uses:List
ErikSchierboom commented 10 months ago

Exercise: isogram

Code

import "essentials" for Strings
class Isogram {
  static isIsogram(string) {
    var res = true
    var seen = {}
    for (c in Strings.downcase(string)) {
      if (!"abcdefghijklmnopqrstuvwxyz".contains(c)) continue
      if (seen.containsKey(c)) {
        return false
      }
      seen[c] = true
    }
    return res
  }
}

Tags:

construct:assignment
construct:boolean
construct:class
construct:continue
construct:for-loop
construct:if
construct:implicit-conversion
construct:indexed-access
construct:initializer
construct:invocation
construct:method
construct:parameter
construct:return
construct:set
construct:string
construct:throw
construct:variable
construct:visibility-modifiers
paradigm:imperative
paradigm:object-oriented
technique:exceptions
technique:looping
uses:Set
joshgoebel commented 10 months ago

No bandwidth for this ATM.

ErikSchierboom commented 10 months ago

This is an automated comment

Hello :wave: Next week we're going to start using the tagging work people are doing on these. If you've already completed the work, thank you! If you've not, but intend to this week, that's great! If you're not going to get round to doing it, and you've not yet posted a comment letting us know, could you please do so, so that we can find other people to do it. Thanks!

glennj commented 10 months ago

I will not have time for this one.