       IDENTIFICATION DIVISION.
       PROGRAM-ID. max-licenses-in-use.

       ENVIRONMENT DIVISION.
       INPUT-OUTPUT SECTION.
       FILE-CONTROL.
           SELECT license-file ASSIGN "mlijobs.txt"
               ORGANIZATION LINE SEQUENTIAL
               FILE STATUS file-status.

       DATA DIVISION.
       FILE SECTION.
       FD  license-file.
       01  license-record.
           03  FILLER                   PIC X(8).
           03  action                   PIC X(3).
               88  license-out          VALUE "OUT".
           03  FILLER                   PIC X(3).
           03  license-timestamp        PIC X(19).
           03  FILLER                   PIC X(13).

       WORKING-STORAGE SECTION.
       01  file-status                  PIC XX.
           88  file-ok                  VALUE "00".

       01  max-licenses-out             PIC 9(6).
       01  num-max-times                PIC 99.
       01  max-license-times-area.
           03  max-timestamps           PIC X(19) OCCURS 1 TO 50 TIMES
               DEPENDING ON num-max-times.
       01  current-licenses-out         PIC 9(6).

       01  i                            PIC 99.

       PROCEDURE DIVISION.
       DECLARATIVES.
       license-file-error SECTION.
           USE AFTER ERROR ON license-file.

           DISPLAY "An unexpected error has occurred. Error "
               file-status ". The program will close."
           GOBACK
           .
       END DECLARATIVES.

       main-line.
           OPEN INPUT license-file
           IF NOT file-ok
               DISPLAY "File could not be opened. Error " file-status
                   "."
               GOBACK
           END-IF

           PERFORM FOREVER
               READ license-file
                   AT END
                       EXIT PERFORM
               END-READ

               IF license-out
                   ADD 1 TO current-licenses-out

                   EVALUATE TRUE
                       WHEN current-licenses-out > max-licenses-out
                           MOVE 1 TO num-max-times
                           MOVE current-licenses-out TO max-licenses-out
                           MOVE license-timestamp
                               TO max-timestamps (num-max-times)

                       WHEN current-licenses-out = max-licenses-out
                           ADD 1 TO num-max-times
                           MOVE license-timestamp
                               TO max-timestamps (num-max-times)
                   END-EVALUATE
               ELSE
                   SUBTRACT 1 FROM current-licenses-out
               END-IF
           END-PERFORM

           CLOSE license-file

           DISPLAY "License count at log end: " current-licenses-out
           DISPLAY "Maximum simulataneous licenses: " max-licenses-out
           DISPLAY "Time(s):"
           PERFORM VARYING i FROM 1 BY 1 UNTIL num-max-times < i
               DISPLAY max-timestamps (i)
           END-PERFORM

           GOBACK
           .


(defun max-licenses (&optional (logfile "mlijobs.txt"))
  (with-open-file (log logfile :direction :input)
    (do ((current-logs 0) (max-logs 0) (max-log-times '())
         (line #1=(read-line log nil nil) #1#))
        ((null line)
         (format t "~&Maximum simultaneous license use is ~w at the ~
                     following time~p: ~{~%  ~a~}."
                 max-logs (length max-log-times) (nreverse max-log-times)))
      (cl-ppcre:register-groups-bind (op time)
          ("License (\\b.*\\b)[ ]{1,2}@ (\\b.*\\b)" line)
        (cond ((string= "OUT" op) (incf current-logs))
              ((string= "IN"  op) (decf current-logs))
              (t (cerror "Ignore it." "Malformed entry ~s." line)))
        (cond ((> current-logs max-logs)
               (setf max-logs current-logs
                     max-log-times (list time)))
              ((= current-logs max-logs)
               (push time max-log-times)))))))


void main() {
    import std.stdio;
    int nOut, maxOut = -1;
    string[] maxTimes;

    foreach (string job; lines(File("mlijobs.txt"))) {
        nOut += (job[8] == 'O') ? 1 : -1;
        if (nOut > maxOut) {
            maxOut = nOut;
            maxTimes = null;
        }
        if (nOut == maxOut)
            maxTimes ~= job[14 .. 33];
    }

    writefln("Maximum simultaneous license use is %d at" ~
             " the following times:\n%( %s\n%)", maxOut, maxTimes);
}


var out := 0
var maxOut := 0
var maxTimes := []

def events := ["OUT " => 1, "IN  " => -1]

for line in <file:mlijobs.txt> {
    def `License @{via (events.fetch) delta}@@ @time for job @num$\n` := line

    out += delta
    if (out > maxOut) {
        maxOut := out
        maxTimes := []
    }
    if (out == maxOut) {
        maxTimes with= time
    }
}

println(`Maximum simultaneous license use is $maxOut at the following times:`)
for time in maxTimes {
    println(` $time`)
}


class
    APPLICATION

create
    make

feature

    make
            -- Max Licences used.
        local
            count: INTEGER
            max_count: INTEGER
            date: STRING
        do
            read_list
            create date.make_empty
            across
                data as d
            loop
                if d.item.has_substring ("OUT") then
                    count := count + 1
                    if count > max_count then
                        max_count := count
                        date := d.item
                    end
                elseif d.item.has_substring ("IN") then
                    count := count - 1
                end
            end
            io.put_string ("Max Licences OUT: " + max_count.out)
            io.new_line
            io.put_string ("Date: " + date.substring (15, 33))
        end

    original_list: STRING = "mlijobs.txt"

feature {NONE}

    read_list
            -- Data read into 'data.
        local
            l_file: PLAIN_TEXT_FILE
        do
            create l_file.make_open_read_write (original_list)
            l_file.read_stream (l_file.count)
            data := l_file.last_string.split ('%N')
            l_file.close
        end

    data: LIST [STRING]

end


-module( text_processing_max_licenses ).

-export( [out_dates_from_file/1, task/0] ).

out_dates_from_file( Name ) ->
    {ok, Binary} = file:read_file( Name ),
    Lines = binary:split( Binary, <<"\n">>, [global] ),
    {_N, _Date, Dict} = lists:foldl( fun out_dates/2, {0, "", dict:new()}, Lines ),
    [{X, dict:fetch(X, Dict)} || X <- dict:fetch_keys( Dict )].

task() ->
    [{Max, Dates} | _T] = lists:reverse(lists:sort(out_dates_from_file("mlijobs.txt")) ),
    io:fwrite( "Max licenses was ~p at ~p~n", [Max, Dates] ).

out_dates( <<>>, Acc ) -> Acc;
out_dates( Line, {N, Date, Dict} ) ->
    [_License, Direction, <<"@">>, New_date | _T] = [X || X <- binary:split(Line, <<" ">>, [global]), X =/= <<>>],
    New_n = out_dates_n( N, Direction ),
    New_dict = out_dates_dict( N, New_n, Date, Dict ),
    {New_n, New_date, New_dict}.

out_dates_dict( N, New_n, Date, Dict ) when N > New_n -> dict:append( N, Date, Dict );
out_dates_dict( _N, _New_n, _Date, Dict ) -> Dict.

out_dates_n( N, <<"OUT">> ) -> N + 1;
out_dates_n( N, <<"IN">> ) -> N - 1.


function split(sequence s, integer c)
    sequence out
    integer first, delim
    out = {}
    first = 1
    while first <= length(s) do
        delim = find_from(c, s, first)
        if delim = 0 then
            delim = length(s) + 1
        end if
        out = append(out, s[first..delim-1])
        first = delim + 1
    end while
    return out
end function

include get.e
function val(sequence s)
    sequence v
    v = value(s)
    return v[2] - v[1] * v[1]
end function

constant fn = open("mlijobs.txt", "r")
integer maxout
sequence jobs, line, maxtime
object x
jobs = {}
maxout = 0
while 1 do
    x = gets(fn)
    if atom(x) then
        exit
    end if
    line = split(x, ' ')
    line[$] = val(line[$])
    if equal(line[2], "OUT") then
        jobs &= line[$]
        if length(jobs) > maxout then
            maxout = length(jobs)
            maxtime = {line[4]}
        elsif length(jobs) = maxout then
            maxtime = append(maxtime, line[4])
        end if
    else
        jobs[find(line[$],jobs)] = jobs[$]
        jobs = jobs[1..$-1]
    end if
end while
close(fn)

printf(1, "Maximum simultaneous license use is %d at the following times:\n", maxout)
for i = 1 to length(maxtime) do
    printf(1, "%s\n", {maxtime[i]})
end for


USING: kernel sequences splitting math accessors io.encodings.ascii
io.files math.parser io ;
IN: maxlicenses

TUPLE: maxlicense max-count current-count times ;

<PRIVATE

: <maxlicense> ( -- max ) -1 0 V{ } clone \ maxlicense boa ; inline

: out? ( line -- ? ) [ "OUT" ] dip subseq? ; inline

: line-time ( line -- time ) " " split harvest fourth ; inline

: update-max-count ( max -- max' )
    dup [ current-count>> ] [ max-count>> ] bi >
    [ dup current-count>> >>max-count V{ } clone >>times ] when ;

: (inc-current-count) ( max ? -- max' )
    [ [ 1 + ] change-current-count ]
    [ [ 1 - ] change-current-count ]
    if
    update-max-count ; inline

: inc-current-count ( max ? time -- max' time )
    [ (inc-current-count) ] dip ;

: current-max-equal? ( max -- max ? )
    dup [ current-count>> ] [ max-count>> ] bi = ;

: update-time ( max time -- max' )
    [ current-max-equal? ] dip
    swap
    [ [ suffix ] curry change-times ] [ drop ] if ;

: split-line ( line -- ? time ) [ out? ] [ line-time ] bi ;

: process ( max line -- max ) split-line inc-current-count update-time ;

PRIVATE>

: find-max-licenses ( -- max )
    "resource:work/mlijobs.txt" ascii file-lines
    <maxlicense> [ process ] reduce ;

: print-max-licenses ( max -- )
    [ times>> ] [ max-count>> ] bi
    "Maximum simultaneous license use is " write
    number>string write
    " at the following times: " print
    [ print ] each ;


20 constant date-size
create max-dates date-size 100 * allot
variable max-out
variable counter

stdin value input

: process ( addr len -- )
  8 /string
  over 3 s" OUT" compare 0= if
    1 counter +!
    counter @ max-out @ > if
      counter @ max-out !
      drop 5 + date-size max-dates  place
    else counter @ max-out @ = if
      drop 5 + date-size max-dates +place
    else 2drop then then
  else drop 2 s" IN" compare 0= if
    -1 counter +!
  then then ;

: main
  0 max-out !
  0 counter !
  s" mlijobs.txt" r/o open-file throw to input
  begin  pad 80 input read-line throw
  while  pad swap process
  repeat drop
  input close-file throw
  max-out @ . ." max licenses in use @"
  max-dates count type cr ;

main bye


 PROGRAM MAX_LICENSES
   IMPLICIT NONE

   INTEGER :: out=0, maxout=0, maxcount=0, err
   CHARACTER(50) :: line
   CHARACTER(19) :: maxtime(100)

   OPEN (UNIT=5, FILE="Licenses.txt", STATUS="OLD", IOSTAT=err)
   IF (err > 0) THEN
     WRITE(*,*) "Error opening file Licenses.txt"
     STOP
   END IF

   DO
     READ(5, "(A)", IOSTAT=err) line
     IF (err == -1) EXIT          ! EOF detected
     IF (line(9:9) == "O") THEN
       out = out + 1
     ELSE IF (line(9:9) == "I") THEN
       out = out - 1
     END IF
     IF (out > maxout ) THEN
       maxout = maxout + 1
       maxcount = 1
       maxtime(maxcount) = line(15:33)
     ELSE IF (out == maxout) THEN
       maxcount = maxcount + 1
       maxtime(maxcount) = line(15:33)
     END IF
   END DO

   CLOSE(5)

   WRITE(*,"(A,I4,A)") "Maximum simultaneous license use is", maxout, " at the following times:"
   WRITE(*,"(A)") maxtime(1:maxcount)

 END PROGRAM MAX_LICENSES


Const CRLF = Chr(13) & Chr(10)
Dim As String currline, maxtime
Dim As Integer counter = 0, max = 0
Dim As String filename = "mlijobs.txt"

Open filename For Input As #1
While Not Eof(1)
    Line Input #1, currline

    If Mid(currline,9,3) = "OUT" Then
        counter += 1
    Else
        counter -= 1
    End If
    If counter > max Then
        max = counter
        maxtime = Mid(currline,15,19)
    Elseif counter = max Then
        maxtime &= CRLF & Mid(currline,15,19)
    End If
Wend
Print Str(max); " license(s) used at ;"; CRLF; maxtime
Close #1
Print !"\nPress ENTER to exit"
Sleep


@set{count;0};@set{max;0}

License OUT \@ * *\[email protected]{count}@testmax{${count},*}
License IN  \@ * *\[email protected]{count}
\[email protected]{${max},${times${max}}}

testmax:*,*[email protected]{${max};$1;@set{max;$1};;}@append{times${count};$2\n}

report:*,*=Maximum simultaneous license use is * at\n*


package main

import (
    "bufio"
    "bytes"
    "fmt"
    "log"
    "os"
)

const (
    filename   = "mlijobs.txt"
    inoutField = 1
    timeField  = 3
    numFields  = 7
)

func main() {
    file, err := os.Open(filename)
    if err != nil {
        log.Fatal(err)
    }
    defer file.Close()
    var ml, out int
    var mlTimes []string
    in := []byte("IN")
    s := bufio.NewScanner(file)
    for s.Scan() {
        f := bytes.Fields(s.Bytes())
        if len(f) != numFields {
            log.Fatal("unexpected format,", len(f), "fields.")
        }
        if bytes.Equal(f[inoutField], in) {
            out--
            if out < 0 {
                log.Fatalf("negative license use at %s", f[timeField])
            }
            continue
        }
        out++
        if out < ml {
            continue
        }

        if out > ml {
            ml = out
            mlTimes = mlTimes[:0]
        }
        mlTimes = append(mlTimes, string(f[timeField]))
    }
    if err = s.Err(); err != nil {
        log.Fatal(err)
    }

    fmt.Println("max licenses:", ml)
    fmt.Println("at:")
    for _, t := range mlTimes {
        fmt.Println(" ", t)
    }
}


def max = 0
def dates = []
def licenses = [:]
new File('licenseFile.txt').eachLine { line ->
    (line =~ /License (\w+)\s+@ ([\d\/_:]+) for job (\d+)/).each { matcher, action, date, job ->
        switch (action) {
        case 'IN':
            assert licenses[job] != null : "License has not been checked out for $job"
            licenses.remove job
            break
        case 'OUT':
            assert licenses[job] == null : "License has already been checked out for $job"
            licenses[job] = date
            def count = licenses.keySet().size()
            if (count > max) {
                max = count
                dates = [ date ]
            } else if (count == max) {
                dates << date
            }
            break
        default:
            throw new IllegalArgumentException("Unsupported license action $action")
        }
    }
}

println "Maximum Licenses $max"
dates.each { date -> println "  $date" }


import Data.List

main = do
  f <- readFile "./../Puzzels/Rosetta/inout.txt"
  let (ioo,dt) = unzip. map ((\(_:io:_:t:_)-> (io,t)). words) . lines $ f
      cio = drop 1 . scanl (\c io -> if io == "IN" then pred c else succ c) 0 $ ioo
      mo = maximum cio
  putStrLn $ "Maximum simultaneous license use is " ++ show mo ++ " at:"
  mapM_ (putStrLn . (dt!!)) . elemIndices mo $ cio


CHARACTER Licenses="Licenses.txt"
REAL :: counts(1), Top10(10)

OPEN(FIle=Licenses, fmt='8x,A3,3x,A19,Nb ,', LENgth=lines)

ALLOCATE(counts, lines)
counts(1) = 1
DO line = 2, lines
   counts(line) = counts(line-1) + 1 - 2*(Licenses(line,1)=='IN')
ENDDO

SORT(Vector=counts, Descending=1, Index=Top10)

DO i = 1, LEN(Top10)
  WRITE() counts(Top10(i)), Licenses(Top10(i), 2)
ENDDO

END


procedure main(A)
    maxCount := count := 0

    every !&input ?  case tab(upto('@')) of {
        "License OUT ": {
             maxTime := (maxCount <:= (count +:= 1), [])
             put(maxTime, (maxCount = count, ="@ ", tab(find(" for "))))
             }
        "License IN  ": count -:= (count > 0, 1)     # Error check
        }

    write("There were ",maxCount," licenses out at:")
    every write("\t",!maxTime)
end


   require 'files'
   'I D' =: (8 ; 14+i.19) {"1 &.> <'m' fread 'licenses.txt' NB.  read file as matrix, select columns
   lu    =:  +/\ _1 ^ 'OI' i. I                             NB.  Number of licenses in use at any given time
   mx    =:  ([email protected]:= >./) lu                                 NB.  Indicies of maxima

   NB.  Output results
   (mx { D) ,~ 'Maximum simultaneous license use is ' , ' at the following times:' ,~ ": {. ,mx { lu


import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.LinkedList;

public class License {
  public static void main(String[] args) throws FileNotFoundException, IOException{
    BufferedReader in = new BufferedReader(new FileReader(args[0]));
    int max = Integer.MIN_VALUE;
    LinkedList<String> dates = new LinkedList<String>();
    String line;
    int count = 0;
    while((line = in.readLine()) != null){
      if(line.startsWith("License OUT ")) count++;
      if(line.startsWith("License IN ")) count--;
      if(count > max){
        max = count;
        String date = line.split(" ")[3];
        dates.clear();
        dates.add(date);
      }else if(count == max){
        String date = line.split(" ")[3];
        dates.add(date);
      }
    }
    System.out.println("Max licenses out: "+max);
    System.out.println("At time(s): "+dates);
  }
}


var file_system = new ActiveXObject("Scripting.FileSystemObject");
var fh = file_system.openTextFile('mlijobs.txt', 1); // 1 == open for reading
var in_use = 0, max_in_use = -1, max_in_use_at = [];

while ( ! fh.atEndOfStream) {
    var line = fh.readline();
    if (line.substr(8,3) == "OUT") {
        in_use++;
        if (in_use > max_in_use) {
            max_in_use = in_use;
            max_in_use_at = [ line.split(' ')[3] ];
        }
        else if (in_use == max_in_use)
            max_in_use_at.push( line.split(' ')[3] );
    }
    else if (line.substr(8,2) == "IN")
        in_use--;
}

fh.close();

WScript.echo("Max licenses out: " + max_in_use + "\n  " + max_in_use_at.join('\n  '));


# Input: an array of strings
def max_licenses_in_use:
  # state: [in_use = 0, max_in_use = -1, max_in_use_at = [] ]
  reduce .[] as $line
    ([0,  -1, [] ];
     ($line|split(" ")) as $line
     | if $line[1] == "OUT" then
         .[0] += 1                          # in_use++;
         | if   .[0] > .[1]                 # (in_use > max_in_use)
           then .[1] = .[0]                 # max_in_use = in_use
             |  .[2] = [$line[3]]           # max_in_use_at = [$line[3]]
           elif .[0] == .[1]                # (in_use == max_in_use)
             then .[2] += [$line[3]]        # max_in_use_at << $line[3]
           else .
           end
       elif $line[1] == "IN" then .[0] -= 1 # in_use--
       else .
       end )
   | "Max licenses out: \(.[1]) at:\n \(.[2]|join("\n "))" ;

# The file is read in as a single string and so must be split at newlines:
split("\n") | max_licenses_in_use


function maximumsimultlicenses(io::IO)
    out, maxout, maxtimes = 0, -1, String[]
    for job in readlines(io)
        out += ifelse(occursin("OUT", job), 1, -1)
        if out > maxout
            maxout = out
            empty!(maxtimes)
        end
        if out == maxout
            push!(maxtimes, split(job)[4])
        end
    end
    return maxout, maxtimes
end

let (maxout, maxtimes) = open(maximumsimultlicenses, "data/mlijobs.txt")
    println("Maximum simultaneous license use is $maxout at the following times: \n - ", join(maxtimes, "\n - "))
end


  r:[email protected]&a=x:|/a:+\{:[x[8+!3]~"OUT";1;-1]}'m:0:"mlijobs.txt";
  `0:,/"Maximum simultaneous license use is ",$x;
  `0:" at the following times:\n";`0:r[;14+!19]


// version 1.1.51

import java.io.File

fun main(args: Array<String>) {
    val filePath = "mlijobs.txt"
    var licenses = 0
    var maxLicenses = 0
    val dates = mutableListOf<String>()
    val f = File(filePath)

    f.forEachLine { line ->
        if (line.startsWith("License OUT")) {
            licenses++
            if (licenses > maxLicenses) {
                maxLicenses = licenses
                dates.clear()
                dates.add(line.substring(14, 33))
            }
            else if(licenses == maxLicenses) {
                dates.add(line.substring(14, 33))
            }
        }
        else if (line.startsWith("License IN")) {
            licenses--
        }
    }
    println("Maximum simultaneous license use is $maxLicenses at the following time(s):")
    println(dates.map { "  $it" }.joinToString("\n"))
}


filename = "mlijobs.txt"
io.input( filename )

max_out, n_out = 0, 0
occurr_dates = {}

while true do
    line = io.read( "*line" )
    if line == nil then break end

    if string.find( line, "OUT" ) ~= nil then
        n_out = n_out + 1
        if n_out > max_out then
            max_out = n_out
            occurr_dates = {}
            occurr_dates[#occurr_dates+1] = string.match( line, "@ ([%d+%p]+)" )
        elseif n_out == max_out then
            occurr_dates[#occurr_dates+1] = string.match( line, "@ ([%d+%p]+)" )
        end
    else
        n_out = n_out - 1
    end
end

print( "Maximum licenses in use:", max_out )
print( "Occurrences:" )
for i = 1, #occurr_dates do
    print( "", occurr_dates[i] )
end


Module Checkit {
      Document a$, max_time$
      Load.doc a$, "mlijobs.txt"
      const dl$=" ", nl$={
      }
      Def long m, out, max_out=-1
      m=Paragraph(a$, 0)
      If Forward(a$,m) then {
            While m {
                  job$=Paragraph$(a$,(m))
                  out+=If(Piece$(job$,dl$,2)="OUT"->1&, -1&)
                  If out>max_out then max_out=out : Clear max_time$
                  If out=max_out then max_time$=Piece$(job$,dl$,4)+nl$
            }
      }
      Report Format$("Maximum simultaneous license use is {0} at the following times:",max_out)
      Print "    ";  ' left margin
      Report max_time$
}
Checkit


divert(-1)
define(`current',0)
define(`max',0)
define(`OUT',`define(`current',incr(current))`'ifelse(eval(current>max),1,
   `define(`max',current)`'divert(-1)`'undivert(1)`'divert(1)',
   `ifelse(current,max,`divert(1)undivert(1)')')')
define(`IN',`define(`current',decr(current))')
define(`for',`divert(-1)')
include(mlijobs.txt))
divert
max
undivert(1)


LC = 0; LCMax = 0; Scan[
   If[MemberQ[#, "OUT"], LC++;
      If[LCMax < LC, LCMax = LC; LCMaxtimes = {};];
      If[LCMax == LC, AppendTo[LCMaxtimes, #[[4]]]],
   LC--;] &, Import["mlijobs.txt", "Table"]]
Print["The maximum number of licenses used was ", LCMax, ", at ", LCMaxtimes]


fn licencesInUse =
(
    local logFile = openFile "mlijobs.txt"
    local out = 0
    local maxOut = -1
    local maxTimes = #()

    while not EOF logFile do
    (
        line = readLine logFile

        if findString line "OUT" != undefined then
        (
            out += 1
        )
        else
        (
            out -= 1
        )

        if out > maxOut then
        (
            maxOut = out
            maxTimes = #()
        )

        if out == maxOut then
        (
            append maxTimes (filterString line " ")[4]
        )
    )
    format "Maximum simultaneous license use is % at the following times:\n" maxOut

    for time in maxTimes do
    (
        format "%\n" time
    )

    close logFile
)

licencesInUse()


import strutils

var
  curOut = 0
  maxOut = -1
  maxTimes = newSeq[string]()

for job in lines "mlijobs.txt":
  if "OUT" in job: inc curOut else: dec curOut
  if curOut > maxOut:
    maxOut = curOut
    maxTimes.setLen(0)
  if curOut == maxOut:
    maxTimes.add job.split[3]

echo "Maximum simultaneous license use is ", maxOut, " at the following times:"
for i in maxTimes: echo "  ", i


let () =
  let out = ref 0 in
  let max_out = ref(-1) in
  let max_times = ref [] in

  let ic = open_in "mlijobs.txt" in
  try while true do
    let line = input_line ic in
    let io, date, n =
      Scanf.sscanf line
        "License %3[IN OUT] %_c %19[0-9/:_] for job %d"
        (fun io date n -> (io, date, n))
    in
    if io = "OUT" then incr out else decr out;
    if !out > !max_out then
    ( max_out := !out;
      max_times := [date]; )
    else if !out = !max_out then
      max_times := date :: !max_times;
  done
  with End_of_file ->
    close_in ic;
    Printf.printf
      "Maximum simultaneous license use is %d \
       at the following times:\n" !max_out;
    List.iter print_endline !max_times;
;;


declare
  fun {MaxLicenses Filename ?Times}
     InUse = {NewCell 0}
     MaxInUse = {NewCell 0}
     MaxTimes = {NewCell nil}
  in
     for Job in {ReadLines Filename} do
        case {List.take Job 11} of "License OUT" then
     InUse := @InUse + 1
     if @InUse > @MaxInUse then
        MaxInUse := @InUse
        MaxTimes := nil
     end
     if @InUse == @MaxInUse then
        JobTime = {Nth {String.tokens Job & } 4}
     in
        MaxTimes := JobTime|@MaxTimes
     end
        [] "License IN " then
     InUse := @InUse - 1
        end
     end
     Times = {Reverse @MaxTimes}
     @MaxInUse
  end

  %% Helper.
  %% Returns a lazy list. So we don't keep the whole logfile in memory...
  fun {ReadLines Filename}
     F = {New class $ from Open.file Open.text end init(name:Filename)}
     fun lazy {ReadNext}
        case {F getS($)} of
           false then nil
        [] Line then
           Line|{ReadNext}
        end
     end
  in
     %% close file when handle becomes unreachable
     {Finalize.register F proc {$ F} {F close} end}
     {ReadNext}
  end

  Times
  MaxInUse = {MaxLicenses "mlijobs.txt" ?Times}
in
  {System.showInfo
   "Maximum simultaneous license use is "#MaxInUse#" at the following times:"}
  {ForAll Times System.showInfo}


license()={
    my(v=externstr("type mlijobs.txt"),u,cur,rec,t);
    for(i=1,#v,
        u=Vec(v[i]);
        if(#u>9 && u[9] == "O",
            if(cur++>rec,
                rec=cur;
                t=[v[i]]
            ,
                if(cur == rec,t=concat(t,[v[i]]))
            )
        ,
            cur--
        )
    );
    print(apply(s->concat(vecextract(Vec(s),"15..33")), t));
    rec
};


#!/usr/bin/perl -w
use strict;

my $out = 0;
my $max_out = -1;
my @max_times;

open FH, '<mlijobs.txt' or die "Can't open file: $!";
while (<FH>) {
    chomp;
    if (/OUT/) {
        $out++;
    } else {
        $out--;
    }
    if ($out > $max_out) {
        $max_out = $out;
        @max_times = ();
    }
    if ($out == $max_out) {
        push @max_times, (split)[3];
    }
}
close FH;

print "Maximum simultaneous license use is $max_out at the following times:\n";
print "  $_\n" foreach @max_times;


-- demo\rosetta\Max_licences.exw
with javascript_semantics -- (include version/first of next three lines only)
include mlijobs.e -- global constant lines, or:
--assert(write_lines("mlijobs.txt",lines)!=-1) -- first run, or get from link above, then:
--constant lines = read_lines("mlijobs.txt")

integer maxout = 0, jobnumber
sequence jobs = {}, maxtime, scanres
string inout, jobtime
for i=1 to length(lines) do
    string line = lines[i]
    scanres = scanf(line,"License %s @ %s for job %d")
    if length(scanres)!=1 then
        printf(1,"error scanning line: %s\n",{line})
        {} = wait_key()
        abort(0)
    end if
    {{inout,jobtime,jobnumber}} = scanres
    if inout="OUT" then
        jobs &= jobnumber
        if length(jobs)>maxout then
            maxout = length(jobs)
            maxtime = {jobtime}
        elsif length(jobs)=maxout then
            maxtime = append(maxtime, jobtime)
        end if
    else
        jobs[find(jobnumber,jobs)] = jobs[$]
        jobs = jobs[1..$-1]
    end if
end for

printf(1, "Maximum simultaneous license use is %d at the following times:\n", maxout)
for i = 1 to length(maxtime) do
    printf(1, "%s\n", {maxtime[i]})
end for

?"done"
{} = wait_key()


$handle = fopen ("mlijobs.txt", "rb");
$maxcount = 0;
$count = 0;
$times = array();
while (!feof($handle)) {
    $buffer = fgets($handle);
    $op = trim(substr($buffer,8,3));
    switch ($op){
        case 'IN':
            $count--;
        break;
        case 'OUT':
            $count++;
            preg_match('/([\d|\/|_|:]+)/',$buffer,$time);
            if($count>$maxcount){
                $maxcount = $count;
                $times = Array($time[0]);
            }elseif($count == $maxcount){
                $times[] = $time[0];
            }
        break;
    }
}
fclose ($handle);

echo $maxcount . '
';
for($i=0;$i<count($times);$i++){
    echo $times[$i] . '
';
}


import util.

go =>
  Jobs = read_file_lines("mlijobs.txt"),
  Counts = asum(Jobs).to_array, % convert to array for speed
  Max = max(Counts),
  MaxIxs = [I : I in 1..Counts.length, Counts[I] == Max],
  printf("Max number of licenses is %d.\n", Max),
  printf("Interval:\n%w\n", [Jobs[J,15..33] : J in MaxIxs].join("\n")),
  nl.

% Accumulative sum
asum(List) = Asum =>
  asum(List,[],Asum).
asum([],Asum0,Asum) =>
  Asum = Asum0.reverse().
asum([H|T],[],Asum) =>
  C = cond(slice(H,9,11) == "OUT", 1, -1),
  asum(T,[C],Asum).
asum([H|T],Asum0,Asum) =>
  Asum0 = [Last|_],
  C = cond(slice(H,9,11) == "OUT", 1, -1),
  asum(T,[Last+C|Asum0],Asum).


#!/usr/bin/picolisp /usr/lib/picolisp/lib.l

(zero Count MaxCount)

(in (opt)
   (while (split (line) " ")
      (case (pack (cadr (setq Line @)))
         (IN
            (dec 'Count) )
         (OUT
            (let Time (cadddr Line)
               (cond
                  ((> (inc 'Count) MaxCount)
                     (setq MaxCount Count  MaxTimes Time) )
                  ((= Count MaxCount)
                     (setq MaxTimes (pack MaxTimes " and " Time)) ) ) ) ) ) ) )

(prinl "The biggest number of licenses is " MaxCount " at " MaxTimes " !")
(bye)


text3: procedure options (main); /* 19 November 2011 */
   declare line character (80) varying;
   declare (nout, max_nout) fixed;
   declare saveline character (80) varying controlled;
   declare k fixed binary;
   declare in file input;

   open file (in) title ('/TEXT-MAX.DAT,TYPE(TEXT),RECSIZE(80)' );

   on endfile (in) go to finish_up;

   max_nout, nout = 0;
   do forever;
      get file (in) edit (line) (L);
      if substr(line, 9, 4) = 'OUT' then nout = nout+1;
      else if substr(line, 9, 3) = 'IN' then
         nout = nout-1;
      if nout = max_nout then
         do; allocate saveline; saveline = line; end;
      if nout > max_nout then
         do;
            do while (allocation(saveline) > 0); free saveline; end;
            max_nout = nout;
            allocate saveline;
            saveline = line;
         end;
   end;

finish_up:
   put skip list ('The maximum number of licences taken out = ' || max_nout);
   do while (allocation(saveline) > 0);
      k = index(saveline, '@');
      if k > 0 then put skip list ('It occurred at ' || substr(saveline, k+1) );
      free saveline;
   end;
end text3;


[int]$count = 0
[int]$maxCount = 0
[datetime[]]$times = @()

$jobs = Get-Content -Path ".\mlijobs.txt" | ForEach-Object {
    [string[]]$fields   = $_.Split(" ",[StringSplitOptions]::RemoveEmptyEntries)
    [datetime]$datetime = Get-Date $fields[3].Replace("_"," ")
    [PSCustomObject]@{
        State = $fields[1]
        Date  = $datetime
        Job   = $fields[6]
    }
}

foreach ($job in $jobs)
{
    switch ($job.State)
    {
        "IN"
        {
            $count--
        }
        "OUT"
        {
            $count++

            if ($count -gt $maxCount)
            {
                $maxCount = $count
                $times = @()
                $times+= $job.Date
            }
            elseif ($count -eq $maxCount)
            {
                $times+= $job.Date
            }
        }
    }
}

[PSCustomObject]@{
    LicensesOut = $maxCount
    StartTime   = $times[0]
    EndTime     = $times[1]
}


OpenConsole()

If ReadFile(0, OpenFileRequester("Text processing/3","mlijobs.txt","All files",1))
  While Not Eof(0)
    currline$=ReadString(0)
    If StringField(currline$,2," ")="OUT"
      counter+1
    Else
      counter-1
    EndIf
    If counter>max
      max=counter
      maxtime$=StringField(currline$,4," ")
    ElseIf counter=max
      maxtime$+#CRLF$+StringField(currline$,4," ")
    EndIf
  Wend
  PrintN(Str(max)+" license(s) used at ;"+#CRLF$+maxtime$)
  CloseFile(0)
Else
  PrintN("Failed to open the file.")
EndIf

PrintN(#CRLF$+"Press ENTER to exit"): Input()
CloseConsole()


out, max_out, max_times = 0, -1, []
for job in open('mlijobs.txt'):
    out += 1 if "OUT" in job else -1
    if out > max_out:
        max_out, max_times = out, []
    if out == max_out:
        max_times.append(job.split()[3])

print("Maximum simultaneous license use is %i at the following times:" % max_out)
print('  ' + '\n  '.join(max_times))


# Read in data, discard useless bits
dfr <- read.table("mlijobs.txt")
dfr <- dfr[,c(2,4)]
# Find most concurrent licences, and when
n.checked.out <- cumsum(ifelse(dfr$V2=="OUT", 1, -1))
times <- strptime(dfr$V4, "%Y/%m/%d_%H:%M:%S")
most.checked.out <- max(n.checked.out)
when.most.checked.out <- times[which(n.checked.out==most.checked.out)]
# As a bonus, plot license use
plot(times, n.checked.out, type="s")


#lang racket
;;; reads a licence file on standard input
;;; returns max licences used and list of times this occurred
(define (count-licences)
  (let inner ((ln (read-line)) (in-use 0) (max-in-use 0) (times-list null))
    (if (eof-object? ln)
        (values max-in-use (reverse times-list))
        (let ((mtch (regexp-match #px"License (IN |OUT) @ (.*) for job.*" ln)))
          (cond
            [(string=? "OUT" (second mtch))
             (let ((in-use+1 (add1 in-use)))
               (cond
                 [(> in-use+1 max-in-use)
                  (inner (read-line) in-use+1 in-use+1 (list (third mtch)))]
                 [(= in-use+1 max-in-use)
                  (inner (read-line) in-use+1 max-in-use (cons (third mtch) times-list))]
                 [else (inner (read-line) in-use+1 max-in-use times-list)]))]
            [(string=? "IN " (second mtch))
             (inner (read-line) (sub1 in-use) max-in-use times-list)]
            [else (inner (read-line) in-use max-in-use times-list)])))))

(define-values (max-used max-used-when)
(with-input-from-file "mlijobs.txt" count-licences))
(printf "Maximum licences in simultaneously used is ~a at the following times:~%"
        max-used)
(for-each displayln max-used-when)


my %licenses;

%licenses<count max> = 0,0;

for $*IN.lines -> $line {
    my ( $license, $date_time );
    ( *, $license, *, $date_time ) = split /\s+/, $line;
    if $license eq 'OUT' {
        %licenses<count>++;
        if %licenses<count> > %licenses<max> {
            %licenses<max>   = %licenses<count>;
            %licenses<times> = [$date_time];
        }
        elsif %licenses<count> == %licenses<max> {
            %licenses<times>.push($date_time);
        }
    }
    elsif $license eq 'IN' {
        if %licenses<count> == %licenses<max> {
            %licenses<times>[*-1] ~= " through " ~ $date_time;
        }
        %licenses<count>--;
    }
    else {
        # Not a licence OUT or IN event, do nothing
    }
};

my $plural = %licenses<times>.elems == 1 ?? '' !! 's';

say "Maximum concurrent licenses in use: {%licenses<max>}, in the time period{$plural}:";
say join ",\n", %licenses<times>.list;


/*REXX program processes instrument data as read from a time sorted data file.*/
iFID= 'LICENSE.LOG'                    /*the fileID of the   input  file.     */
high=0                                 /*highest number of licenses (so far). */
#=0                                    /*the count of number of licenses out. */
n=0                                    /*the number of  highest licenses out. */
    do recs=0  while lines(iFID)\==0   /* [↓]  read file  until  end─of─file. */
    parse value linein(iFID) with . ? . $       /*get IN│OUT status, job info.*/
    if ?=='IN'  then                   #=#-1    /*decrement the license count.*/
                else if ?=='OUT'  then #=#+1    /*increment  "     "      "   */
    if # >high then do;  n=1;    job.1=$;  end  /*the job info for highest cnt*/
    if #==high then do;  n=n+1;  job.n=$;  end  /* "   "    "   "   equal   " */
    high=max(high,#)                            /*calculate max license count.*/
    end   /*while ···*/

say recs  'records read from the input file: '  iFID
say 'The maximum number of licenses out is '    high        " at:"
say
     do j=1  for n                     /*show what/when max licenses occurred.*/
     say left('',20)  job.j            /*indent the information displayed.    */
     end   /*j*/                       /*stick a fork in it,  we're all done. */


out = 0
max_out = -1
max_times = []

File.foreach('mlijobs.txt') do |line|
  out += line.include?("OUT") ? 1 : -1
  if out > max_out
    max_out = out
    max_times = []
  end
  max_times << line.split[3]  if out == max_out
end

puts "Maximum simultaneous license use is #{max_out} at the following times:"
max_times.each {|time| puts "  #{time}"}


open "c:\data\temp\logFile.txt" for input as #f
while not(eof(#f))
  line input #f, a$
  if word$(a$,2," ") = "IN" then count = count - 1 else count = count + 1
  maxCount = max(maxCount,count)
wend
open "c:\data\temp\logFile.txt" for input as #f
while not(eof(#f))
  line input #f, a$
  if word$(a$,2," ") = "IN" then count = count - 1 else count = count + 1
  if count = maxCount then theDate$ = theDate$ + " | " + word$(a$,4," ") + " Job:";word$(a$,7," ")
wend
print maxCount;" ";theDate$


type Timestamp = String;

fn compute_usage<R, S, E>(lines: R) -> Result<(u32, Vec<Timestamp>), E>
where
    S: AsRef<str>,
    R: Iterator<Item = Result<S, E>>,
{
    let mut timestamps = Vec::new();
    let mut current = 0;
    let mut maximum = 0;

    for line in lines {
        let line = line?;
        let line = line.as_ref();

        if line.starts_with("License IN") {
            current -= 1;
        } else if line.starts_with("License OUT") {
            current += 1;

            if maximum <= current {
                let date = line.split_whitespace().nth(3).unwrap().to_string();

                if maximum < current {
                    maximum = current;
                    timestamps.clear();
                }

                timestamps.push(date);
            }
        }
    }

    Ok((maximum, timestamps))
}

fn main() -> std::io::Result<()> {
    use std::io::{BufRead, BufReader};
    let file = std::fs::OpenOptions::new().read(true).open("mlijobs.txt")?;
    let (max, timestamps) = compute_usage(BufReader::new(file).lines())?;
    println!("Maximum licenses out: {}", max);
    println!("At time(s): {:?}", timestamps);
    Ok(())
}


import java.io.{BufferedReader, InputStreamReader}
import java.net.URL

object License0 extends App {
  val url = new URL("https://raw.githubusercontent.com/def-/nim-unsorted/master/mlijobs.txt")
  val in = new BufferedReader(new InputStreamReader(url.openStream()))

  val dates = new collection.mutable.ListBuffer[String]
  var (count: Int, max: Int) = (0, Int.MinValue)
  var line: String = _

  while ( {line = in.readLine; line} != null) {
    if (line.startsWith("License OUT ")) count += 1
    if (line.startsWith("License IN ")) count -= 1 // Redundant test when "OUT"
    if (count > max) { // Fruitless execution when "License IN "
      max = count
      val date = line.split(" ")(3)
      dates.clear()
      dates += date
    } else if (count == max) {
      val date = line.split(" ")(3)
      dates += date
    }
  }

  println("Max licenses out: " + max)
  println("At time(s): " + dates.mkString(", "))

}


$ include "seed7_05.s7i";

const proc: main is func
  local
    var file: inFile is STD_NULL;
    var string: line is "";
    var integer: currLicenses is 0;
    var integer: maxLicenses is 0;
    var array string: maxLicenseTimes is 0 times "";
    var string: eventTime is "";
  begin
    inFile := open("mlijobs.txt", "r");
    while hasNext(inFile) do
      line := getln(inFile);
      if line[9 len 3] = "OUT" then
        incr(currLicenses);
        if currLicenses >= maxLicenses then
          if currLicenses > maxLicenses then
            maxLicenses := currLicenses;
            maxLicenseTimes := 0 times "";
          end if;
          maxLicenseTimes &:= line[15 len 19];
        end if;
      elsif currLicenses > 0 then
        decr(currLicenses);
      end if;
    end while;
    close(inFile);
    writeln("Maximum simultaneous license use is " <& maxLicenses <& " at the following times:");
    for eventTime range maxLicenseTimes do
      writeln(eventTime);
    end for;
  end func;


var out = 0
var max_out = -1
var max_times = []

ARGF.each { |line|
    out += (line ~~ /OUT/ ? 1 : -1)
    if (out > max_out) {
        max_out = out
        max_times = []
    }
    if (out == max_out) {
        max_times << line.split(' ')[3]
    }
}

say "Maximum simultaneous license use is #{max_out} at the following times:"
max_times.each {|t| say "  #{t}" }


 set out 0
 set max_out -1
 set max_times {}

 foreach job [split [read [open "mlijobs.txt" "r"]] "\n"] {
     if {[lindex $job 1] == "OUT"} {
         incr out
     } {
         incr out -1
     }
     if {$out > $max_out} {
         set max_out $out
         set max_times {}
     }
     if {$out == $max_out} {
         lappend max_times [lindex $job 3]
     }
 }

 puts "Maximum simultaneous license use is $max_out at the following times:"
 foreach t $max_times {
     puts "  $t"
 }


$$ MODE TUSCRIPT
joblog="mlijobs.txt",jobnrout=0
log=FILE (joblog)
DICT jobnrout CREATE
LOOP l=log
jobout=EXTRACT (l,":License :"|,": :")
 IF (jobout=="out") THEN
  time=EXTRACT (l,":@ :"|,": :"), jobnrout=jobnrout+1
  DICT jobnrout APPEND/QUIET jobnrout,num,cnt,time;" "
 ELSE
  jobnrout=jobnrout-1
 ENDIF
ENDLOOP
DICT jobnrout UNLOAD jobnrout,num,cnt,time
DICT jobnrout SIZE maxlicout
times=SELECT (time,#maxlicout)
PRINT "The max. number of licences out is ", maxlicout
PRINT "at these times: ", times


@(bind *times* #H((:eql-based) nil))
@(bind *licenses-out* 0)
@(bind *maximum-licenses-out* 0)
@(collect)
License @statuses @@ @dateTimes for job @jobNumbers
@(end)
@(do (each ((status statuses)
             (dateTime dateTimes)
             (jobNumber jobNumbers))
       (set *licenses-out*
         (if (equal status "OUT")
           (progn
             (when (>= (+ *licenses-out* 1) *maximum-licenses-out*)
               (set *maximum-licenses-out* (+ *licenses-out* 1))
               (pushhash *times* *maximum-licenses-out* dateTime))
             (+ *licenses-out* 1))
           (+ *licenses-out* -1)))))
@(output)
Maximum # of licenses out: @{*maximum-licenses-out*}
Peak time(s): @{(reverse (gethash *times* *maximum-licenses-out*))}
@(end)


#import std
#import nat

log    = ^(~&hh==`O,~&tth)*FtPS sep` *F mlijobs_dot_txt
scan   = @NiX ~&ar^& ^C/~&alrhr2X ~&arlh?/~&faNlCrtPXPR ~&fabt2R
search = @lSzyCrSPp leql$^&[email protected]; ^/[email protected] ~&rS
format = ^|C\~& --' licenses in use at'@h+ %nP

#show+

main = format search scan log


File_Open("|(PATH_ONLY)\data\mlijobs.txt", BROWSE)

#1 = 0          // Number of licenses active
#2 = 0          // Max number of active licenses found

Repeat(ALL) {
    Search("|{OUT,IN}|[email protected]", ADVANCE+ERRBREAK)
    if (Match_Item == 1) {                      // "OUT"
        #1++
        if (#1 > #2) {                          // new high value
            #2 = #1
            Reg_Empty(10)                       // empty the time list
        }
        if (#1 == #2) {                         // same as high value
            Reg_Copy(10, 1, APPEND)             // store time
        }
    } else {                                    // "IN"
        #1--
    }
}

Message("Maximum simultaneous license use is ")
Num_Type(#2, LEFT+NOCR)
Message(" at the following times:\n")
Reg_Type(10)

Buf_Quit(OK)


import "io" for File

var lines = File.read("mlijobs.txt").replace("\r", "").split("\n")
var out = 0
var max = 0
var times = []
for (line in lines) {
    if (line.startsWith("License OUT")) {
        out = out + 1
        if (out >= max) {
            var sp = line.split(" ")
            if (out > max) {
                max = out
                times.clear()
            }
            times.add(sp[3])
        }
    } else if (line.startsWith("License IN")) {
        out = out - 1
    }
}

System.print("The maximum licenses that were out = %(max) at time(s):")
System.print("  " + times.join("\n  "))


nOut,maxOut,maxTimes:=0,-1,List();
foreach job in (File("mlijobs.txt")){
   _,status,_,date:=job.split();
   nOut+=( if(status.toUpper()=="OUT") 1 else -1 );
   if(nOut>maxOut){ maxOut=nOut; maxTimes.clear(); }
   if(nOut==maxOut) maxTimes.append(date);
}
println(("Maximum simultaneous license use is %d at"
         " the following times:\n %s").fmt(maxOut,maxTimes.concat("\n")));


[Char = String] CH2NUM
L(chars) ‘abc def ghi jkl mno pqrs tuv wxyz’.split(‘ ’)
   V num = L.index + 2
   L(ch) chars
      CH2NUM[ch] = String(num)

F mapnum2words(words)
   DefaultDict[String, [String]] number2words
   V reject = 0
   L(word) words
      X.try
         number2words[word.map(ch -> :CH2NUM[ch]).join(‘’)].append(word)
      X.catch KeyError
         reject++
   R (number2words, reject)

V words = File(‘unixdict.txt’).read().rtrim("\n").split("\n")
print(‘Read #. words from 'unixdict.txt'’.format(words.len))
V wordset = Set(words)
V (num2words, reject) = mapnum2words(words)

F interactiveconversions()
   L(inp) (‘rosetta’, ‘code’, ‘2468’, ‘3579’)
      print("\nType a number or a word to get the translation and textonyms: "inp)
      I all(inp.map(ch -> ch C ‘23456789’))
         I inp C :num2words
            print(‘  Number #. has the following textonyms in the dictionary: #.’.format(inp, (:num2words[inp]).join(‘, ’)))
         E
            print(‘  Number #. has no textonyms in the dictionary.’.format(inp))
      E I all(inp.map(ch -> ch C :CH2NUM))
         V num = inp.map(ch -> :CH2NUM[ch]).join(‘’)
         print(‘  Word #. is#. in the dictionary and is number #. with textonyms: #.’.format(inp, (I inp C :wordset {‘’} E ‘n't’), num, (:num2words[num]).join(‘, ’)))
      E
         print(‘  I don't understand '#.'’.format(inp))

V morethan1word = sum(num2words.keys().filter(w -> :num2words[w].len > 1).map(w -> 1))
V maxwordpernum = max(num2words.values().map(values -> values.len))
print(‘
There are #. words in #. which can be represented by the Textonyms mapping.
They require #. digit combinations to represent them.
#. digit combinations represent Textonyms.’.format(words.len - reject, ‘'unixdict.txt'’, num2words.len, morethan1word))

print("\nThe numbers mapping to the most words map to #. words each:".format(maxwordpernum))
V maxwpn = sorted(num2words.filter((key, val) -> val.len == :maxwordpernum))
L(num, wrds) maxwpn
   print(‘  #. maps to: #.’.format(num, wrds.join(‘, ’)))

interactiveconversions()


# find textonyms in a list of words #
# use the associative array in the Associate array/iteration task     #
PR read "aArray.a68" PR

# returns the number of occurances of ch in text #
PROC count = ( STRING text, CHAR ch )INT:
     BEGIN
         INT result := 0;
         FOR c FROM LWB text TO UPB text DO IF text[ c ] = ch THEN result +:= 1 FI OD;
         result
     END # count # ;

CHAR invalid char = "*";

# returns text with the characters replaced by their text digits      #
PROC to text = ( STRING text )STRING:
     BEGIN
         STRING result := text;
         FOR pos FROM LWB result TO UPB result DO
             CHAR c = to upper( result[ pos ] );
             IF   c = "A" OR c = "B" OR c = "C"            THEN result[ pos ] := "2"
             ELIF c = "D" OR c = "E" OR c = "F"            THEN result[ pos ] := "3"
             ELIF c = "G" OR c = "H" OR c = "I"            THEN result[ pos ] := "4"
             ELIF c = "J" OR c = "K" OR c = "L"            THEN result[ pos ] := "5"
             ELIF c = "M" OR c = "N" OR c = "O"            THEN result[ pos ] := "6"
             ELIF c = "P" OR c = "Q" OR c = "R" OR c = "S" THEN result[ pos ] := "7"
             ELIF c = "T" OR c = "U" OR c = "V"            THEN result[ pos ] := "8"
             ELIF c = "W" OR c = "X" OR c = "Y" OR c = "Z" THEN result[ pos ] := "9"
             ELSE # not a character that can be encoded #       result[ pos ] := invalid char
             FI
         OD;
         result
     END # to text # ;

# read the list of words and store in an associative array           #

CHAR separator = "/"; # character that will separate the textonyms   #

IF  FILE input file;
    STRING file name = "unixdict.txt";
    open( input file, file name, stand in channel ) /= 0
THEN
    # failed to open the file #
    print( (  "Unable to open """ + file name + """", newline ) )
ELSE
    # file opened OK #
    BOOL at eof := FALSE;
    # set the EOF handler for the file #
    on logical file end( input file, ( REF FILE f )BOOL:
                                     BEGIN
                                         # note that we reached EOF on the #
                                         # latest read #
                                         at eof := TRUE;
                                         # return TRUE so processing can continue #
                                         TRUE
                                     END
                       );
    REF AARRAY words   := INIT LOC AARRAY;
    INT word count     := 0;
    INT combinations   := 0;
    INT multiple count := 0;
    INT max length     := 0;
    WHILE STRING word;
          get( input file, ( word, newline ) );
          NOT at eof
    DO
        STRING text word = to text( word );
        IF count( text word, invalid char ) = 0 THEN
            # the word can be fully encoded #
            word count +:= 1;
            INT length := ( UPB word - LWB word ) + 1;
            IF length > max length THEN
                # this word is longer than the maximum length found so far #
                max length := length
            FI;
            IF ( words // text word ) = "" THEN
                # first occurance of this encoding #
                combinations +:= 1;
                words // text word := word
            ELSE
                # this encoding has already been used #
                IF count( words // text word, separator ) = 0
                THEN
                    # this is the second time this encoding is used #
                    multiple count +:= 1
                FI;
                words // text word +:= separator + word
            FI
        FI
    OD;
    # close the file #
    close( input file );

    # find the maximum number of textonyms #

    INT max textonyms := 0;

    REF AAELEMENT e := FIRST words;
    WHILE e ISNT nil element DO
        INT textonyms := count( value OF e, separator );
        IF  textonyms > max textonyms
        THEN
            max textonyms := textonyms
        FI;
        e := NEXT words
    OD;

    print( ( "There are ", whole( word count, 0 ), " words in ", file name, " which can be represented by the digit key mapping.", newline ) );
    print( ( "They require ", whole( combinations, 0 ), " digit combinations to represent them.", newline ) );
    print( ( whole( multiple count, 0 ), " combinations represent Textonyms.", newline ) );

    # show the textonyms with the maximum number #
    print( ( "The maximum number of textonyms for a particular digit key mapping is ", whole( max textonyms + 1, 0 ), " as follows:", newline ) );
    e := FIRST words;
    WHILE e ISNT nil element DO
        IF  INT textonyms := count( value OF e, separator );
            textonyms = max textonyms
        THEN
            print( ( "    ", key OF e, " encodes ", value OF e, newline ) )
        FI;
        e := NEXT words
    OD;

    # show the textonyms with the maximum length #
    print( ( "The longest words are ", whole( max length, 0 ), " chracters long", newline ) );
    print( ( "Encodings with this length are:", newline ) );
    e := FIRST words;
    WHILE e ISNT nil element DO
        IF max length = ( UPB key OF e - LWB key OF e ) + 1
        THEN
            print( ( "    ", key OF e, " encodes ", value OF e, newline ) )
        FI;
        e := NEXT words
    OD;

FI


#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <glib.h>

char text_char(char c) {
    switch (c) {
    case 'a': case 'b': case 'c':
        return '2';
    case 'd': case 'e': case 'f':
        return '3';
    case 'g': case 'h': case 'i':
        return '4';
    case 'j': case 'k': case 'l':
        return '5';
    case 'm': case 'n': case 'o':
        return '6';
    case 'p': case 'q': case 'r': case 's':
        return '7';
    case 't': case 'u': case 'v':
        return '8';
    case 'w': case 'x': case 'y': case 'z':
        return '9';
    default:
        return 0;
    }
}

bool text_string(const GString* word, GString* text) {
    g_string_set_size(text, word->len);
    for (size_t i = 0; i < word->len; ++i) {
        char c = text_char(g_ascii_tolower(word->str[i]));
        if (c == 0)
            return false;
        text->str[i] = c;
    }
    return true;
}

typedef struct textonym_tag {
    const char* text;
    size_t length;
    GPtrArray* words;
} textonym_t;

int compare_by_text_length(const void* p1, const void* p2) {
    const textonym_t* t1 = p1;
    const textonym_t* t2 = p2;
    if (t1->length > t2->length)
        return -1;
    if (t1->length < t2->length)
        return 1;
    return strcmp(t1->text, t2->text);
}

int compare_by_word_count(const void* p1, const void* p2) {
    const textonym_t* t1 = p1;
    const textonym_t* t2 = p2;
    if (t1->words->len > t2->words->len)
        return -1;
    if (t1->words->len < t2->words->len)
        return 1;
    return strcmp(t1->text, t2->text);
}

void print_words(GPtrArray* words) {
    for (guint i = 0, n = words->len; i < n; ++i) {
        if (i > 0)
            printf(", ");
        printf("%s", g_ptr_array_index(words, i));
    }
    printf("\n");
}

void print_top_words(GArray* textonyms, guint top) {
    for (guint i = 0; i < top; ++i) {
        const textonym_t* t = &g_array_index(textonyms, textonym_t, i);
        printf("%s = ", t->text);
        print_words(t->words);
    }
}

void free_strings(gpointer ptr) {
    g_ptr_array_free(ptr, TRUE);
}

bool find_textonyms(const char* filename, GError** error_ptr) {
    GError* error = NULL;
    GIOChannel* channel = g_io_channel_new_file(filename, "r", &error);
    if (channel == NULL) {
        g_propagate_error(error_ptr, error);
        return false;
    }
    GHashTable* ht = g_hash_table_new_full(g_str_hash, g_str_equal,
                                           g_free, free_strings);
    GString* word = g_string_sized_new(64);
    GString* text = g_string_sized_new(64);
    guint count = 0;
    gsize term_pos;
    while (g_io_channel_read_line_string(channel, word, &term_pos,
                                         &error) == G_IO_STATUS_NORMAL) {
        g_string_truncate(word, term_pos);
        if (!text_string(word, text))
            continue;
        GPtrArray* words = g_hash_table_lookup(ht, text->str);
        if (words == NULL) {
            words = g_ptr_array_new_full(1, g_free);
            g_hash_table_insert(ht, g_strdup(text->str), words);
        }
        g_ptr_array_add(words, g_strdup(word->str));
        ++count;
    }
    g_io_channel_unref(channel);
    g_string_free(word, TRUE);
    g_string_free(text, TRUE);
    if (error != NULL) {
        g_propagate_error(error_ptr, error);
        g_hash_table_destroy(ht);
        return false;
    }

    GArray* words = g_array_new(FALSE, FALSE, sizeof(textonym_t));
    GHashTableIter iter;
    gpointer key, value;
    g_hash_table_iter_init(&iter, ht);
    while (g_hash_table_iter_next(&iter, &key, &value)) {
        GPtrArray* v = value;
        if (v->len > 1) {
            textonym_t textonym;
            textonym.text = key;
            textonym.length = strlen(key);
            textonym.words = v;
            g_array_append_val(words, textonym);
        }
    }

    printf("There are %u words in '%s' which can be represented by the digit key mapping.\n",
           count, filename);
    guint size = g_hash_table_size(ht);
    printf("They require %u digit combinations to represent them.\n", size);
    guint textonyms = words->len;
    printf("%u digit combinations represent Textonyms.\n", textonyms);

    guint top = 5;
    if (textonyms < top)
        top = textonyms;

    printf("\nTop %u by number of words:\n", top);
    g_array_sort(words, compare_by_word_count);
    print_top_words(words, top);

    printf("\nTop %u by length:\n", top);
    g_array_sort(words, compare_by_text_length);
    print_top_words(words, top);

    g_array_free(words, TRUE);
    g_hash_table_destroy(ht);
    return true;
}

int main(int argc, char** argv) {
    if (argc != 2) {
        fprintf(stderr, "usage: %s word-list\n", argv[0]);
        return EXIT_FAILURE;
    }
    GError* error = NULL;
    if (!find_textonyms(argv[1], &error)) {
        if (error != NULL) {
            fprintf(stderr, "%s: %s\n", argv[1], error->message);
            g_error_free(error);
        }
        return EXIT_FAILURE;
    }
    return EXIT_SUCCESS;
}


#include <fstream>
#include <iostream>
#include <unordered_map>
#include <vector>

struct Textonym_Checker {
private:
    int total;
    int elements;
    int textonyms;
    int max_found;
    std::vector<std::string> max_strings;
    std::unordered_map<std::string, std::vector<std::string>> values;

    int get_mapping(std::string &result, const std::string &input)
    {
        static std::unordered_map<char, char> mapping = {
            {'A', '2'}, {'B', '2'}, {'C', '2'},
            {'D', '3'}, {'E', '3'}, {'F', '3'},
            {'G', '4'}, {'H', '4'}, {'I', '4'},
            {'J', '5'}, {'K', '5'}, {'L', '5'},
            {'M', '6'}, {'N', '6'}, {'O', '6'},
            {'P', '7'}, {'Q', '7'}, {'R', '7'}, {'S', '7'},
            {'T', '8'}, {'U', '8'}, {'V', '8'},
            {'W', '9'}, {'X', '9'}, {'Y', '9'}, {'Z', '9'}
        };

        result = input;
        for (char &c : result) {
            if (!isalnum(c)) return 0;
            if (isalpha(c)) c = mapping[toupper(c)];
        }

        return 1;
    }

public:
    Textonym_Checker() : total(0), elements(0), textonyms(0), max_found(0) { }

    ~Textonym_Checker() { }

    void add(const std::string &str) {
        std::string mapping;
        total++;

        if (!get_mapping(mapping, str)) return;

        const int num_strings = values[mapping].size();

        if (num_strings == 1) textonyms++;
        elements++;

        if (num_strings > max_found) {
            max_strings.clear();
            max_strings.push_back(mapping);
            max_found = num_strings;
        }
        else if (num_strings == max_found)
            max_strings.push_back(mapping);

        values[mapping].push_back(str);
    }

    void results(const std::string &filename) {
        std::cout << "Read " << total << " words from " << filename << "\n\n";

        std::cout << "There are " << elements << " words in " << filename;
        std::cout << " which can be represented by the digit key mapping.\n";
        std::cout << "They require " << values.size() <<
                     " digit combinations to represent them.\n";
        std::cout << textonyms << " digit combinations represent Textonyms.\n\n";
        std::cout << "The numbers mapping to the most words map to ";
        std::cout << max_found + 1 << " words each:\n";

        for (auto it1 : max_strings) {
            std::cout << '\t' << it1 << " maps to: ";
            for (auto it2 : values[it1])
                std::cout << it2 << " ";
            std::cout << '\n';
        }
        std::cout << '\n';
    }

    void match(const std::string &str) {
        auto match = values.find(str);

        if (match == values.end()) {
            std::cout << "Key '" << str << "' not found\n";
        }
        else {
            std::cout << "Key '" << str << "' matches: ";
            for (auto it : values[str])
                std::cout << it << " ";
            std::cout << '\n';
        }
    }
};

int main()
{
    auto filename = "unixdict.txt";
    std::ifstream input(filename);
    Textonym_Checker tc;

    if (input.is_open()) {
        std::string line;
        while (getline(input, line))
            tc.add(line);
    }

    input.close();

    tc.results(filename);
    tc.match("001");
    tc.match("228");
    tc.match("27484247");
    tc.match("7244967473642");
}


(def table
  {\a 2 \b 2 \c 2       \A 2 \B 2 \C 2
   \d 3 \e 3 \f 3       \D 3 \E 3 \F 3
   \g 4 \h 4 \i 4       \G 4 \H 4 \I 4
   \j 5 \k 5 \l 5       \J 5 \K 5 \L 5
   \m 6 \n 6 \o 6       \M 6 \N 6 \O 6
   \p 7 \q 7 \r 7 \s 7  \P 7 \Q 7 \R 7 \S 7
   \t 8 \u 8 \v 8       \T 8 \U 8 \V 8
   \w 9 \x 9 \y 9 \z 9  \W 9 \X 9 \Y 9 \Z 9})

(def words-url "http://www.puzzlers.org/pub/wordlists/unixdict.txt")

(def words (-> words-url slurp clojure.string/split-lines))

(def digits (partial map table))

(let [textable  (filter #(every? table %) words) ;; words with letters only
      mapping   (group-by digits textable)       ;; map of digits to words
      textonyms (filter #(< 1 (count (val %))) mapping)] ;; textonyms only
  (print
   (str "There are " (count textable) " words in " \' words-url \'
        " which can be represented by the digit key mapping. They require "
        (count mapping) " digit combinations to represent them. "
        (count textonyms) " digit combinations represent Textonyms.")))


void main() {
    import std.stdio, std.string, std.range, std.algorithm, std.ascii;

    immutable src = "unixdict.txt";
    const words = src.File.byLineCopy.map!strip.filter!(w => w.all!isAlpha).array;

    immutable table = makeTrans("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
                                "2223334445556667777888999922233344455566677778889999");

    string[][string] dials;
    foreach (const word; words)
        dials[word.translate(table)] ~= word;

    auto textonyms = dials.byPair.filter!(p => p[1].length > 1).array;

    writefln("There are %d words in %s which can be represented by the digit key mapping.", words.length, src);
    writefln("They require %d digit combinations to represent them.", dials.length);
    writefln("%d digit combinations represent Textonyms.", textonyms.length);

    "\nTop 5 in ambiguity:".writeln;
    foreach (p; textonyms.schwartzSort!(p => -p[1].length).take(5))
        writefln("    %s => %-(%s %)", p[]);

    "\nTop 5 in length:".writeln;
    foreach (p; textonyms.schwartzSort!(p => -p[0].length).take(5))
        writefln("    %s => %-(%s %)", p[]);
}


program Textonyms;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Classes,
  System.Generics.Collections,
  System.Character;

const
  TEXTONYM_MAP = '22233344455566677778889999';

type
  TextonymsChecker = class
  private
    Total, Elements, Textonyms, MaxFound: Integer;
    MaxStrings: TList<string>;
    Values: TDictionary<string, TList<string>>;
    FFileName: TFileName;
    function Map(c: Char): Char;
    function GetMapping(var return: string; const Input: string): Boolean;
  public
    constructor Create(FileName: TFileName);
    destructor Destroy; override;
    procedure Add(const Str: string);
    procedure Load(FileName: TFileName);
    procedure Test;
    function Match(const str: string): Boolean;
    property FileName: TFileName read FFileName;
  end;

{ TextonymsChecker }

procedure TextonymsChecker.Add(const Str: string);
var
  mapping: string;
  num_strings: Integer;

  procedure AddValues(mapping: string; NewItem: string);
  begin
    if not Values.ContainsKey(mapping) then
      Values.Add(mapping, TList<string>.Create);

    Values[mapping].Add(NewItem);
  end;

begin
  inc(total);

  if not GetMapping(mapping, Str) then
    Exit;

  if Values.ContainsKey(mapping) then
    num_strings := Values[mapping].Count
  else
    num_strings := 0;

  inc(Textonyms, ord(num_strings = 1));
  inc(Elements);

  if (num_strings > maxfound) then
  begin
    MaxStrings.Clear;
    MaxStrings.Add(mapping);
    MaxFound := num_strings;
  end
  else if num_strings = MaxFound then
  begin
    MaxStrings.Add(mapping);
  end;

  AddValues(mapping, Str);
end;

constructor TextonymsChecker.Create(FileName: TFileName);
begin
  MaxStrings := TList<string>.Create;
  Values := TDictionary<string, TList<string>>.Create;
  Total := 0;
  Textonyms := 0;
  MaxFound := 0;
  Elements := 0;
  Load(FileName);
end;

destructor TextonymsChecker.Destroy;
var
  key: string;
begin
  for key in Values.Keys do
    Values[key].Free;

  Values.Free;
  MaxStrings.Free;
  inherited;
end;

function TextonymsChecker.GetMapping(var return: string; const Input: string): Boolean;
var
  i: Integer;
begin
  return := Input;
  for i := 1 to return.Length do
  begin
    if not return[i].IsLetterOrDigit then
      exit(False);

    if return[i].IsLetter then
      return[i] := Map(return[i]);
  end;
  Result := True;
end;

procedure TextonymsChecker.Load(FileName: TFileName);
var
  i: Integer;
begin
  if not FileExists(FileName) then
  begin
    writeln('File "', FileName, '" not found');
    exit;
  end;

  with TStringList.Create do
  begin
    LoadFromFile(FileName);
    for i := 0 to count - 1 do
    begin
      self.Add(Strings[i]);
    end;
    Free;
  end;
end;

function TextonymsChecker.Map(c: Char): Char;
begin
  Result := TEXTONYM_MAP.Chars[Ord(UpCase(c)) - Ord('A')];
end;

function TextonymsChecker.Match(const str: string): Boolean;
var
  w: string;
begin
  Result := Values.ContainsKey(str);

  if not Result then
  begin
    writeln('Key "', str, '" not found');
  end
  else
  begin
    write('Key "', str, '" matches: ');
    for w in Values[str] do
    begin
      write(w, ' ');
    end;
    writeln;
  end;
end;

procedure TextonymsChecker.Test;
var
  i, j: Integer;
begin
  writeln('Read ', Total, ' words from ', FileName, #10);
  writeln(' which can be represented by the digit key mapping.');
  writeln('They require ', Values.Count, ' digit combinations to represent them.');
  writeln(textonyms, ' digit combinations represent Textonyms.', #10);
  write('The numbers mapping to the most words map to');
  writeln(MaxFound + 1, ' words each:');

  for i := 0 to MaxStrings.Count - 1 do
  begin
    write(^I, MaxStrings[i], ' maps to: ');
    for j := 0 to Values[MaxStrings[i]].Count - 1 do
    begin
      write(Values[MaxStrings[i]][j], ' ');
    end;
    Writeln;
  end;

end;

var
  Tc: TextonymsChecker;

begin
  Tc := TextonymsChecker.Create('unixdict.txt');
  Tc.Test;

  tc.match('001');
  tc.match('228');
  tc.match('27484247');
  tc.match('7244967473642');

  Tc.Free;
  readln;
end.


USING: assocs assocs.extras interpolate io io.encodings.utf8
io.files kernel literals math math.parser prettyprint sequences
unicode ;

<< CONSTANT: src "unixdict.txt" >>

CONSTANT: words
    $[ src utf8 file-lines [ [ letter? ] all? ] filter ]

CONSTANT: digits "22233344455566677778889999"

: >phone ( str -- n )
    [ CHAR: a - digits nth ] map string>number ;

: textonyms ( seq -- assoc )
    [ [ >phone ] keep ] map>alist expand-keys-push-at ;

: #textonyms ( assoc -- n )
    [ nip length 1 > ] assoc-filter assoc-size ;

words length src words textonyms [ assoc-size ] keep #textonyms

[I There are ${} words in ${} which can be represented by the digit key mapping.
They require ${} digit combinations to represent them.
${} digit combinations represent Textonyms.I] nl nl

"7325 -> " write words textonyms 7325 of .


package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"strings"
	"unicode"
)

func main() {
	log.SetFlags(0)
	log.SetPrefix("textonyms: ")

	wordlist := flag.String("wordlist", "wordlist", "file containing the list of words to check")
	flag.Parse()
	if flag.NArg() != 0 {
		flag.Usage()
		os.Exit(2)
	}

	t := NewTextonym(phoneMap)
	_, err := ReadFromFile(t, *wordlist)
	if err != nil {
		log.Fatal(err)
	}
	t.Report(os.Stdout, *wordlist)
}

// phoneMap is the digit to letter mapping of a typical phone.
var phoneMap = map[byte][]rune{
	'2': []rune("ABC"),
	'3': []rune("DEF"),
	'4': []rune("GHI"),
	'5': []rune("JKL"),
	'6': []rune("MNO"),
	'7': []rune("PQRS"),
	'8': []rune("TUV"),
	'9': []rune("WXYZ"),
}

// ReadFromFile is a generic convience function that allows the use of a
// filename with an io.ReaderFrom and handles errors related to open and
// closing the file.
func ReadFromFile(r io.ReaderFrom, filename string) (int64, error) {
	f, err := os.Open(filename)
	if err != nil {
		return 0, err
	}
	n, err := r.ReadFrom(f)
	if cerr := f.Close(); err == nil && cerr != nil {
		err = cerr
	}
	return n, err
}

type Textonym struct {
	numberMap map[string][]string // map numeric string into words
	letterMap map[rune]byte       // map letter to digit
	count     int                 // total number of words in numberMap
	textonyms int                 // number of numeric strings with >1 words
}

func NewTextonym(dm map[byte][]rune) *Textonym {
	lm := make(map[rune]byte, 26)
	for d, ll := range dm {
		for _, l := range ll {
			lm[l] = d
		}
	}
	return &Textonym{letterMap: lm}
}

func (t *Textonym) ReadFrom(r io.Reader) (n int64, err error) {
	t.numberMap = make(map[string][]string)
	buf := make([]byte, 0, 32)
	sc := bufio.NewScanner(r)
	sc.Split(bufio.ScanWords)
scan:
	for sc.Scan() {
		buf = buf[:0]
		word := sc.Text()

		// XXX we only bother approximating the number of bytes
		// consumed. This isn't used in the calling code and was
		// only included to match the io.ReaderFrom interface.
		n += int64(len(word)) + 1

		for _, r := range word {
			d, ok := t.letterMap[unicode.ToUpper(r)]
			if !ok {
				//log.Printf("ignoring %q\n", word)
				continue scan
			}
			buf = append(buf, d)
		}
		//log.Printf("scanned %q\n", word)
		num := string(buf)
		t.numberMap[num] = append(t.numberMap[num], word)
		t.count++
		if len(t.numberMap[num]) == 2 {
			t.textonyms++
		}
		//log.Printf("%q → %v\t→ %v\n", word, num, t.numberMap[num])
	}
	return n, sc.Err()
}

func (t *Textonym) Most() (most int, subset map[string][]string) {
	for k, v := range t.numberMap {
		switch {
		case len(v) > most:
			subset = make(map[string][]string)
			most = len(v)
			fallthrough
		case len(v) == most:
			subset[k] = v
		}
	}
	return most, subset
}

func (t *Textonym) Report(w io.Writer, name string) {
	// Could be fancy and use text/template package but fmt is sufficient
	fmt.Fprintf(w, `
There are %v words in %q which can be represented by the digit key mapping.
They require %v digit combinations to represent them.
%v digit combinations represent Textonyms.
`,
		t.count, name, len(t.numberMap), t.textonyms)

	n, sub := t.Most()
	fmt.Fprintln(w, "\nThe numbers mapping to the most words map to",
		n, "words each:")
	for k, v := range sub {
		fmt.Fprintln(w, "\t", k, "maps to:", strings.Join(v, ", "))
	}
}


import Data.Char (toUpper)
import Data.Function (on)
import Data.List (groupBy, sortBy)
import Data.Maybe (fromMaybe, isJust, isNothing)

toKey :: Char -> Maybe Char
toKey ch
  | ch < 'A' = Nothing
  | ch < 'D' = Just '2'
  | ch < 'G' = Just '3'
  | ch < 'J' = Just '4'
  | ch < 'M' = Just '5'
  | ch < 'P' = Just '6'
  | ch < 'T' = Just '7'
  | ch < 'W' = Just '8'
  | ch <= 'Z' = Just '9'
  | otherwise = Nothing

toKeyString :: String -> Maybe String
toKeyString st
  | any isNothing mch = Nothing
  | otherwise = Just $ map (fromMaybe '!') mch
  where
    mch = map (toKey . toUpper) st

showTextonym :: [(String, String)] -> String
showTextonym ts =
  fst (head ts)
    ++ " => "
    ++ concat
      [ w ++ " "
        | (_, w) <- ts
      ]

main :: IO ()
main = do
  let src = "unixdict.txt"
  contents <- readFile src
  let wordList = lines contents
      keyedList =
        [ (key, word)
          | (Just key, word) <-
              filter (isJust . fst) $
                zip (map toKeyString wordList) wordList
        ]
      groupedList =
        groupBy ((==) `on` fst) $
          sortBy (compare `on` fst) keyedList
      textonymList = filter ((> 1) . length) groupedList
  mapM_ putStrLn $
    [ "There are "
        ++ show (length keyedList)
        ++ " words in "
        ++ src
        ++ " which can be represented by the digit key mapping.",
      "They require "
        ++ show (length groupedList)
        ++ " digit combinations to represent them.",
      show (length textonymList) ++ " digit combinations represent Textonyms.",
      "",
      "Top 5 in ambiguity:"
    ]
      ++ fmap
        showTextonym
        ( take 5 $
            sortBy (flip compare `on` length) textonymList
        )
      ++ ["", "Top 5 in length:"]
      ++ fmap
        showTextonym
        (take 5 $ sortBy (flip compare `on` (length . fst . head)) textonymList)


main := method(
    setupLetterToDigitMapping

    file := File clone openForReading("./unixdict.txt")
    words := file readLines
    file close

    wordCount := 0
    textonymCount := 0
    dict := Map clone
    words foreach(word,
        (key := word asPhoneDigits) ifNonNil(
            wordCount = wordCount+1
            value := dict atIfAbsentPut(key,list())
            value append(word)
            if(value size == 2,textonymCount = textonymCount+1)
        )
    )
    write("There are ",wordCount," words in ",file name)
    writeln(" which can be represented by the digit key mapping.")
    writeln("They require ",dict size," digit combinations to represent them.")
    writeln(textonymCount," digit combinations represent Textonyms.")

    samplers := list(maxAmbiquitySampler, noMatchingCharsSampler)
    dict foreach(key,value,
        if(value size == 1, continue)
        samplers foreach(sampler,sampler examine(key,value))
    )
    samplers foreach(sampler,sampler report)
)

setupLetterToDigitMapping := method(
    fromChars := Sequence clone
    toChars := Sequence clone
    list(
        list("ABC", "2"), list("DEF", "3"), list("GHI", "4"),
        list("JKL", "5"), list("MNO", "6"), list("PQRS","7"),
        list("TUV", "8"), list("WXYZ","9")
    ) foreach( map,
        fromChars appendSeq(map at(0), map at(0) asLowercase)
        toChars alignLeftInPlace(fromChars size, map at(1))
    )

    Sequence asPhoneDigits := block(
        str := call target asMutable translate(fromChars,toChars)
        if( str contains(0), nil, str )
    ) setIsActivatable(true)
)

maxAmbiquitySampler := Object clone do(
    max := list()
    samples := list()
    examine := method(key,textonyms,
        i := key size - 1
        if(i > max size - 1,
            max setSize(i+1)
            samples setSize(i+1)
        )
        nw := textonyms size
        nwmax := max at(i)
        if( nwmax isNil or nw > nwmax,
            max atPut(i,nw)
            samples atPut(i,list(key,textonyms))
        )
    )
    report := method(
        writeln("\nExamples of maximum ambiquity for each word length:")
        samples foreach(sample,
            sample ifNonNil(
                writeln("    ",sample at(0)," -> ",sample at(1) join(" "))
            )
        )
    )
)

noMatchingCharsSampler := Object clone do(
    samples := list()
    examine := method(key,textonyms,
        for(i,0,textonyms size - 2 ,
            for(j,i+1,textonyms size - 1,
                if( _noMatchingChars(textonyms at(i), textonyms at(j)),
                    samples append(list(textonyms at(i),textonyms at(j)))
                )
            )
        )
    )
    _noMatchingChars := method(t1,t2,
        t1 foreach(i,ich,
            if(ich == t2 at(i), return false)
        )
        true
    )
    report := method(
        write("\nThere are ",samples size," textonym pairs which ")
        writeln("differ at each character position.")
        if(samples size > 10, writeln("The ten largest are:"))
        samples sortInPlace(at(0) size negate)
        if(samples size > 10,samples slice(0,10),samples) foreach(sample,
            writeln("    ",sample join(" ")," -> ",sample at(0) asPhoneDigits)
        )
    )
)

main


require'regex strings web/gethttp'

strip=:dyad define
  (('(?s)',x);'') rxrplc y
)

fetch=:monad define
  txt=. '.*<pre>' strip '</pre>.*' strip gethttp y
  cutopen tolower txt-.' '
)

keys=:noun define
 2 abc
 3 def
 4 ghi
 5 jkl
 6 mno
 7 pqrs
 8 tuv
 9 wxyz
)

reporttext=:noun define
There are #{0} words in #{1} which can be represented by the digit key mapping.
They require #{2} digit combinations to represent them.
#{3} digit combinations represent Textonyms.
)

report=:dyad define
  x rplc (":&.>y),.~('#{',":,'}'"_)&.>i.#y
)

textonymrpt=:dyad define
  'digits letters'=. |:>;,&.>,&.>/&.>/"1 <;._1;._2 x
  valid=. (#~ */@e.&letters&>) fetch y NB. ignore illegals
  reps=. {&[email protected](letters&i.)&.> valid NB. reps is digit seq
  reporttext report (#valid);y;(#~.reps);+/(1<#)/.~reps
)


import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.Vector;

public class RTextonyms {

  private static final Map<Character, Character> mapping;
  private int total, elements, textonyms, max_found;
  private String filename, mappingResult;
  private Vector<String> max_strings;
  private Map<String, Vector<String>> values;

  static {
    mapping = new HashMap<Character, Character>();
    mapping.put('A', '2'); mapping.put('B', '2'); mapping.put('C', '2');
    mapping.put('D', '3'); mapping.put('E', '3'); mapping.put('F', '3');
    mapping.put('G', '4'); mapping.put('H', '4'); mapping.put('I', '4');
    mapping.put('J', '5'); mapping.put('K', '5'); mapping.put('L', '5');
    mapping.put('M', '6'); mapping.put('N', '6'); mapping.put('O', '6');
    mapping.put('P', '7'); mapping.put('Q', '7'); mapping.put('R', '7'); mapping.put('S', '7');
    mapping.put('T', '8'); mapping.put('U', '8'); mapping.put('V', '8');
    mapping.put('W', '9'); mapping.put('X', '9'); mapping.put('Y', '9'); mapping.put('Z', '9');
  }

  public RTextonyms(String filename) {

    this.filename = filename;
    this.total = this.elements = this.textonyms = this.max_found = 0;
    this.values = new HashMap<String, Vector<String>>();
    this.max_strings = new Vector<String>();

    return;
  }

  public void add(String line) {

    String mapping = "";
    total++;
    if (!get_mapping(line)) {
      return;
    }
    mapping = mappingResult;

    if (values.get(mapping) == null) {
      values.put(mapping, new Vector<String>());
    }

    int num_strings;
    num_strings = values.get(mapping).size();
    textonyms += num_strings == 1 ? 1 : 0;
    elements++;

    if (num_strings > max_found) {
      max_strings.clear();
      max_strings.add(mapping);
      max_found = num_strings;
    }
    else if (num_strings == max_found) {
      max_strings.add(mapping);
    }

    values.get(mapping).add(line);

    return;
  }

  public void results() {

    System.out.printf("Read %,d words from %s%n%n", total, filename);
    System.out.printf("There are %,d words in %s which can be represented by the digit key mapping.%n", elements,
        filename);
    System.out.printf("They require %,d digit combinations to represent them.%n", values.size());
    System.out.printf("%,d digit combinations represent Textonyms.%n", textonyms);
    System.out.printf("The numbers mapping to the most words map to %,d words each:%n", max_found + 1);
    for (String key : max_strings) {
      System.out.printf("%16s maps to: %s%n", key, values.get(key).toString());
    }
    System.out.println();

    return;
  }

  public void match(String key) {

    Vector<String> match;
    match = values.get(key);
    if (match == null) {
      System.out.printf("Key %s not found%n", key);
    }
    else {
      System.out.printf("Key %s matches: %s%n", key, match.toString());
    }

    return;
  }

  private boolean get_mapping(String line) {

    mappingResult = line;
    StringBuilder mappingBuilder = new StringBuilder();
    for (char cc : line.toCharArray()) {
      if (Character.isAlphabetic(cc)) {
        mappingBuilder.append(mapping.get(Character.toUpperCase(cc)));
      }
      else if (Character.isDigit(cc)) {
        mappingBuilder.append(cc);
      }
      else {
        return false;
      }
    }
    mappingResult = mappingBuilder.toString();

    return true;
  }

  public static void main(String[] args) {

    String filename;
    if (args.length > 0) {
      filename = args[0];
    }
    else {
      filename = "./unixdict.txt";
    }
    RTextonyms tc;
    tc = new RTextonyms(filename);
    Path fp = Paths.get(filename);
    try (Scanner fs = new Scanner(fp, StandardCharsets.UTF_8.name())) {
      while (fs.hasNextLine()) {
        tc.add(fs.nextLine());
      }
    }
    catch (IOException ex) {
      ex.printStackTrace();
    }

    List<String> numbers = Arrays.asList(
        "001", "228", "27484247", "7244967473642",
        "."
        );

    tc.results();
    for (String number : numbers) {
      if (number.equals(".")) {
        System.out.println();
      }
      else {
        tc.match(number);
      }
    }

    return;
  }
}


def textonym_value:
    gsub("a|b|c|A|B|C"; "2")
  | gsub("d|e|f|D|E|F"; "3")
  | gsub("g|h|i|G|H|I"; "4")
  | gsub("j|k|l|J|K|L"; "5")
  | gsub("m|n|o|M|N|O"; "6")
  | gsub("p|q|r|s|P|Q|R|S"; "7")
  | gsub("t|u|v|T|U|V"; "8")
  | gsub("w|x|y|z|W|X|Y|Z"; "9");

def explore:
  # given an array (or hash), find the maximum length of the items (or values):
  def max_length: [.[] | length] | max;

  # The length of the longest textonym in the dictionary of numericString => array:
  def longest:
    [to_entries[] | select(.value|length > 1) | .key | length] | max;

  # pretty-print a key-value pair:
  def pp: "\(.key) maps to: \(.value|tostring)";

  split("\n")
  | map(select(test("^[a-zA-Z]+$")))  # select the strictly alphabetic strings
  | length as $nwords
  | reduce .[] as $line
    ( {};
      ($line | textonym_value) as $key
      | .[$key] += [$line] )
  | max_length as $max_length
  | longest    as $longest
  | "There are \($nwords) words in the Textonyms/wordlist word list that can be represented by the digit-key mapping.",
    "They require \(length) digit combinations to represent them.",
    "\( [.[] | select(length>1) ] | length ) digit combinations represent Textonyms.",
    "The numbers mapping to the most words map to \($max_length) words:",
     (to_entries[] | select((.value|length) == $max_length) | pp ),
    "The longest Textonyms in the word list have length \($longest):",
     (to_entries[] | select((.key|length) == $longest and (.value|length > 1)) | pp)
;

explore


using Printf

const tcode = (Regex=>Char)[r"A|B|C|Ä|Å|Á|Â|Ç" => '2',
                            r"D|E|F|È|Ê|É" => '3',
                            r"G|H|I|Í" => '4',
                            r"J|K|L" => '5',
                            r"M|N|O|Ó|Ö|Ô|Ñ" => '6',
                            r"P|Q|R|S" => '7',
                            r"T|U|V|Û|Ü" => '8',
                            r"W|X|Y|Z" => '9']

function tpad(str::IOStream)
    tnym = (String=>Array{String,1})[]
    for w in eachline(str)
        w = chomp(w)
        t = uppercase(w)
        for (k,v) in tcode
            t = replace(t, k, v)
        end
        t = replace(t, r"\D", '1')
        tnym[t] = [get(tnym, t, String[]), w]
    end
    return tnym
end


// version 1.1.4-3

import java.io.File

val wordList = "unixdict.txt"
val url = "http://www.puzzlers.org/pub/wordlists/unixdict.txt"

const val DIGITS = "22233344455566677778889999"

val map = mutableMapOf<String, MutableList<String>>()

fun processList() {
    var countValid = 0
    val f = File(wordList)
    val sb = StringBuilder()

    f.forEachLine { word->
        var valid = true
        sb.setLength(0)
        for (c in word.toLowerCase()) {
            if (c !in 'a'..'z') {
                valid = false
                break
            }
            sb.append(DIGITS[c - 'a'])
        }
        if (valid) {
            countValid++
            val key = sb.toString()
            if (map.containsKey(key)) {
                map[key]!!.add(word)
            }
            else {
                map.put(key, mutableListOf(word))
            }
        }
    }
    var textonyms = map.filter { it.value.size > 1 }.toList()
    val report = "There are $countValid words in '$url' " +
                 "which can be represented by the digit key mapping.\n" +
                 "They require ${map.size} digit combinations to represent them.\n" +
                 "${textonyms.size} digit combinations represent Textonyms.\n"
    println(report)

    val longest = textonyms.sortedByDescending { it.first.length }
    val ambiguous = longest.sortedByDescending { it.second.size }

    println("Top 8 in ambiguity:\n")
    println("Count   Textonym  Words")
    println("======  ========  =====")
    var fmt = "%4d    %-8s  %s"
    for (a in ambiguous.take(8)) println(fmt.format(a.second.size, a.first, a.second))

    fmt = fmt.replace("8", "14")
    println("\nTop 6 in length:\n")
    println("Length  Textonym        Words")
    println("======  ==============  =====")
    for (l in longest.take(6)) println(fmt.format(l.first.length, l.first, l.second))
}

fun main(args: Array<String>) {
    processList()
}


-- Global variables
http = require("socket.http")
keys = {"VOICEMAIL", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"}
dictFile = "http://www.puzzlers.org/pub/wordlists/unixdict.txt"

-- Return the sequence of keys required to type a given word
function keySequence (str)
    local sequence, noMatch, letter = ""
    for pos = 1, #str do
        letter = str:sub(pos, pos)
        for i, chars in pairs(keys) do
            noMatch = true
            if chars:match(letter) then
                sequence = sequence .. tostring(i)
                noMatch = false
                break
            end
        end
        if noMatch then return nil end
    end
    return tonumber(sequence)
end

-- Generate table of words grouped by key sequence
function textonyms (dict)
    local combTable, keySeq = {}
    for word in dict:gmatch("%S+") do
        keySeq = keySequence(word)
        if keySeq then
            if combTable[keySeq] then
                table.insert(combTable[keySeq], word)
            else
                combTable[keySeq] = {word}
            end
        end
    end
    return combTable
end

-- Analyse sequence table and print details
function showReport (keySeqs)
    local wordCount, seqCount, tCount = 0, 0, 0
    for seq, wordList in pairs(keySeqs) do
        wordCount = wordCount + #wordList
        seqCount = seqCount + 1
        if #wordList > 1 then tCount = tCount + 1 end
    end
    print("There are " .. wordCount .. " words in " .. dictFile)
    print("which can be represented by the digit key mapping.")
    print("They require " .. seqCount .. " digit combinations to represent them.")
    print(tCount .. " digit combinations represent Textonyms.")
end

-- Main procedure
showReport(textonyms(http.request(dictFile)))


ClearAll[Numerify,rls]
rls={"A"->2,"B"->2,"C"->2,"D"->3,"E"->3,"F"->3,"G"->4,"H"->4,"I"->4,"J"->5,"K"->5,"L"->5,"M"->6,"N"->6,"O"->6,"P"->7,"Q"->7,"R"->7,"S"->7,"T"->8,"U"->8,"V"->8,"W"->9,"X"->9,"Y"->9,"Z"->9};
Numerify[s_String]:=Characters[ToUpperCase[s]]/.rls
dict=Once[Import["http://www.rosettacode.org/wiki/Textonyms/wordlist","XML"]];
dict=Cases[dict,XMLElement["pre",{},{x_}]:>x,\[Infinity]];
dict=TakeLargestBy[dict,ByteCount,1][[1]];
dict=DeleteDuplicates[StringTrim/*ToUpperCase/@StringSplit[dict]];
dict=Select[dict,StringMatchQ[([email protected]@Keys[rls])..]];
Print["Number of words from Textonyms/wordlist are: ",Length[dict]]
grouped=GroupBy[dict[[;;;;10]],Numerify];
Print["Number of unique numbers: ",Length[grouped]]
grouped=Select[grouped,Length/*GreaterThan[1]];
Print["Most with the same number:"]
KeyValueMap[List,TakeLargestBy[grouped,Length,1]]//Grid
Print["5 longest words with textonyms:"]
[email protected]@@Normal[ReverseSortBy[grouped,First/*Length][[;;5]]]//Grid


import "listUtil"
import "mapUtil"

groups = "abc def ghi jkl mno pqrs tuv wxyz".split
charToNum = {}
for i in groups.indexes
	for ch in groups[i]
		charToNum[ch] = i + 2
	end for
end for

words = file.readLines("/sys/data/englishWords.txt")

wordToNum = function(word)
	parts = word.split("")
	parts.apply function(ch)
		return charToNum[ch]
	end function
	return parts.join("")
end function

numToWords = {}
moreThan1Word = 0
for word in words
	num = wordToNum(word.lower)
	if numToWords.hasIndex(num) then
		numToWords[num].push word
	else
		numToWords[num] = [word]
	end if
	if numToWords[num].len == 2 then moreThan1Word = moreThan1Word + 1
end for

print "There are " + words.len + " words in englishWords.txt which can be represented  by the digit key mapping."
print "They require " + numToWords.len + " digit combinations to represent them."
print moreThan1Word + " digit combinations represent Textonyms."

while true
	print
	inp = input("Enter a word or digit combination: ")
	if not inp then break
	if val(inp) > 0 then
		print inp + " -> " + numToWords.get(inp)
	else
		num = wordToNum(inp.lower)
		print "Digit key combination for """ + inp + """ is: " + num
		print num + " -> " + numToWords.get(num)
	end if
end while


import algorithm, sequtils, strformat, strutils, tables

const

  WordList = "unixdict.txt"
  Url = "http://www.puzzlers.org/pub/wordlists/unixdict.txt"

  Digits = "22233344455566677778889999"

proc processList(wordFile: string) =

  var mapping: Table[string, seq[string]]
  var countValid = 0

  for word in wordFile.lines:
    var valid = true
    var key: string
    for c in word.toLowerAscii:
      if c notin 'a'..'z':
        valid = false
        break
      key.add Digits[ord(c) - ord('a')]
    if valid:
      inc countValid
      mapping.mgetOrPut(key, @[]).add word

  let textonyms = toSeq(mapping.pairs).filterIt(it[1].len > 1)
  echo &"There are {countValid} words in '{Url}' ",
       &"which can be represented by the digit key mapping."
  echo &"They require {mapping.len} digit combinations to represent them."
  echo &"{textonyms.len} digit combinations represent Textonyms.\n"

  let longest = textonyms.sortedByIt(-it[0].len)
  let ambiguous = longest.sortedByIt(-it[1].len)
  echo "Top 8 in ambiguity:\n"
  echo "Count   Textonym  Words"
  echo "======  ========  ====="
  for a in ambiguous[0..7]:
    echo &"""{a[1].len:4}    {a[0]:>8}  {a[1].join(", ")}"""

  echo "\nTop 6 in length:\n"
  echo "Length  Textonym        Words"
  echo "======  ==============  ====="
  for l in longest[0..5]:
    echo &"""{l[0].len:4}    {l[0]:>14}  {l[1].join(", ")}"""

processList(WordList)


my $src = 'unixdict.txt';

# filter word-file for valid input, transform to low-case
open $fh, "<", $src;
@words = grep { /^[a-zA-Z]+$/ } <$fh>;
map { tr/A-Z/a-z/ } @words;

# translate words to dials
map { tr/abcdefghijklmnopqrstuvwxyz/22233344455566677778889999/ } @dials = @words;

# get unique values (modify @dials) and non-unique ones (are textonyms)
@dials = grep {!$h{$_}++} @dials;
@textonyms = grep { $h{$_} > 1 } @dials;

print "There are @{[scalar @words]} words in '$src' which can be represented by the digit key mapping.
They require @{[scalar @dials]} digit combinations to represent them.
@{[scalar @textonyms]} digit combinations represent Textonyms.";


with javascript_semantics
sequence digit = repeat(-1,255)
         digit['a'..'c'] = '2'
         digit['d'..'f'] = '3'
         digit['g'..'i'] = '4'
         digit['j'..'l'] = '5'
         digit['m'..'o'] = '6'
         digit['p'..'s'] = '7'
         digit['t'..'v'] = '8'
         digit['w'..'z'] = '9'

function digits(string word)
    string keycode = repeat(' ',length(word))
    for i=1 to length(word) do
        integer ch = word[i]
        assert(ch>='a' and ch<='z')
        keycode[i] = digit[ch]
    end for
    return {keycode,word}
end function

function az(string word) return min(word)>='a' and max(word)<='z' end function

sequence words = apply(filter(unix_dict(),az),digits), max_idx, long_idx
string word, keycode, last = ""
integer keycode_count = 0, textonyms = 0,
        this_count = 0, max_count = 0, longest = 0

printf(1,"There are %d words in unixdict.txt which can be represented by the digit key mapping.\n",{length(words)})

-- Sort by keycode: while words are ordered we get
--  eg {"a","ab","b","ba"} -> {"2","22","2","22"}
words = sort(deep_copy(words))

for i=1 to length(words) do
    {keycode,word} = words[i]
    if keycode=last then
        textonyms += this_count=1
        this_count += 1
        if this_count>=max_count then
            if this_count>max_count then
                max_idx = {i}
            else
                max_idx &= i
            end if
            max_count = this_count
        end if
    else
        keycode_count += 1
        last = keycode
        this_count = 1
    end if
    if length(word)>=longest then
        if length(word)>longest then
            long_idx = {i}
        else
            long_idx &= i
        end if
        longest = length(word)
    end if
end for
printf(1,"They require %d digit combinations to represent them.\n",{keycode_count})
printf(1,"%d digit combinations represent Textonyms.\n",{textonyms})
printf(1,"The maximum number of textonyms for a particular digit key mapping is %d:\n",{max_count})
for i=1 to length(max_idx) do
    integer k = max_idx[i], l = k-max_count+1
    string dups = join(vslice(words[l..k],2),"/")
    printf(1," %s encodes %s\n",{words[k][1],dups})
end for
printf(1,"The longest words are %d characters long\n",longest)
printf(1,"Encodings with this length are:\n")
for i=1 to length(long_idx) do
    printf(1,"   %s encodes %s\n",words[long_idx[i]])
end for


F isint(f)
   R Complex(f).imag == 0 & fract(Complex(f).real) == 0

print([Complex(1.0), 2, (3.0 + 0.0i), 4.1, (3 + 4i), (5.6 + 0i)].map(f -> isint(f)))
print(isint(25.000000))
print(isint(24.999999))
print(isint(25.000100))
print(isint(-5e-2))
print(isint(Float.infinity))
print(isint(5.0 + 0.0i))
print(isint(5 - 5i))


$url  = "http://www.puzzlers.org/pub/wordlists/unixdict.txt"
$file = "$env:TEMP\unixdict.txt"
(New-Object System.Net.WebClient).DownloadFile($url, $file)
$unixdict = Get-Content -Path $file

[string]$alpha = "abcdefghijklmnopqrstuvwxyz"
[string]$digit = "22233344455566677778889999"

$table = [ordered]@{}

for ($i = 0; $i -lt $alpha.Length; $i++)
{
    $table.Add($alpha[$i], $digit[$i])
}

$words = foreach ($word in $unixdict)
{
    if ($word -match "^[a-z]*$")
    {
        [PSCustomObject]@{
            Word   = $word
            Number = ($word.ToCharArray() | ForEach-Object {$table.$_}) -join ""
        }
    }
}

$digitCombinations = $words | Group-Object -Property Number

$textonyms = $digitCombinations | Where-Object -Property Count -GT 1 | Sort-Object -Property Count -Descending

Write-Host ("There are {0} words in {1} which can be represented by the digit key mapping." -f $words.Count, $url)
Write-Host ("They require {0} digit combinations to represent them."                        -f $digitCombinations.Count)
Write-Host ("{0} digit combinations represent Textonyms.`n"                                 -f $textonyms.Count)

Write-Host "Top 5 in ambiguity:"
$textonyms | Select-Object -First 5 -Property Count,
                                              @{Name="Textonym"; Expression={$_.Name}},
                                              @{Name="Words"   ; Expression={$_.Group.Word -join ", "}} | Format-Table -AutoSize
Write-Host "Top 5 in length:"
$textonyms | Sort-Object {$_.Name.Length} -Descending |
             Select-Object -First 5 -Property @{Name="Length"  ; Expression={$_.Name.Length}},
                                              @{Name="Textonym"; Expression={$_.Name}},
                                              @{Name="Words"   ; Expression={$_.Group.Word -join ", "}} | Format-Table -AutoSize

Remove-Item -Path $file -Force -ErrorAction SilentlyContinue


# set the required precision of LONG LONG values using    #
# "PR precision n PR" if required                         #
PR precision 24 PR

# returns TRUE if v has an integer value, FALSE otherwise #
OP ISINT = ( LONG LONG COMPL v )BOOL:
   IF im OF v /= 0 THEN
       # v has an imaginary part #
       FALSE
   ELSE
       # v has a real part only #
       ENTIER re OF v = v
   FI; # ISINT #

# test ISINT #

PROC test is int = ( LONG LONG COMPLEX v )VOID:
     print( ( re OF v, "_", im OF v, IF ISINT v THEN " is " ELSE " is not " FI, "integral", newline ) );

test is int( 1 );
test is int( 1.00000001 );
test is int( 4 I 3 );
test is int( 4.0 I 0 );
test is int( 123456789012345678901234 )


from collections import defaultdict
import urllib.request

CH2NUM = {ch: str(num) for num, chars in enumerate('abc def ghi jkl mno pqrs tuv wxyz'.split(), 2) for ch in chars}
URL = 'http://www.puzzlers.org/pub/wordlists/unixdict.txt'

def getwords(url):
 return urllib.request.urlopen(url).read().decode("utf-8").lower().split()

def mapnum2words(words):
    number2words = defaultdict(list)
    reject = 0
    for word in words:
        try:
            number2words[''.join(CH2NUM[ch] for ch in word)].append(word)
        except KeyError:
            # Reject words with non a-z e.g. '10th'
            reject += 1
    return dict(number2words), reject

def interactiveconversions():
    global inp, ch, num
    while True:
        inp = input("\nType a number or a word to get the translation and textonyms: ").strip().lower()
        if inp:
            if all(ch in '23456789' for ch in inp):
                if inp in num2words:
                    print("  Number {0} has the following textonyms in the dictionary: {1}".format(inp, ', '.join(
                        num2words[inp])))
                else:
                    print("  Number {0} has no textonyms in the dictionary.".format(inp))
            elif all(ch in CH2NUM for ch in inp):
                num = ''.join(CH2NUM[ch] for ch in inp)
                print("  Word {0} is{1} in the dictionary and is number {2} with textonyms: {3}".format(
                    inp, ('' if inp in wordset else "n't"), num, ', '.join(num2words[num])))
            else:
                print("  I don't understand %r" % inp)
        else:
            print("Thank you")
            break

if __name__ == '__main__':
    words = getwords(URL)
    print("Read %i words from %r" % (len(words), URL))
    wordset = set(words)
    num2words, reject = mapnum2words(words)
    morethan1word = sum(1 for w in num2words if len(num2words[w]) > 1)
    maxwordpernum = max(len(values) for values in num2words.values())
    print("""
There are {0} words in {1} which can be represented by the Textonyms mapping.
They require {2} digit combinations to represent them.
{3} digit combinations represent Textonyms.\
""".format(len(words) - reject, URL, len(num2words), morethan1word))

    print("\nThe numbers mapping to the most words map to %i words each:" % maxwordpernum)
    maxwpn = sorted((key, val) for key, val in num2words.items() if len(val) == maxwordpernum)
    for num, wrds in maxwpn:
        print("  %s maps to: %s" % (num, ', '.join(wrds)))

    interactiveconversions()