about summary refs log blame commit diff stats
path: root/anagrams.tlv
blob: a314eb852b74ae2b85e22b81a55cbba1fa6abf54 (plain) (tree)
























                                                                                
                            







                                                
                            


                                
                            
























                                                    


                                        
                          































































































                                                                 
                              

                             











                                








                                                                              
                         







                                    









                                                                       






                                                                        





                                      







                              
                           
                    

                       




                              




                              























                                                                                                   
                                      
                                        
                                       
                         
                                   
                          
                                    












                                                            
                       




                             
                                        








                                         








                                                                

                                         










                                                                          

                  













                                                                        



                               
                                        






















































































                                                                                         
# .tlv file generated by https://github.com/akkartik/teliva
# You may edit it if you are careful; however, you may see cryptic errors if you
# violate Teliva's assumptions.
#
# .tlv files are representations of Teliva programs. Teliva programs consist of
# sequences of definitions. Each definition is a table of key/value pairs. Keys
# and values are both strings.
#
# Lines in .tlv files always follow exactly one of the following forms:
# - comment lines at the top of the file starting with '#' at column 0
# - beginnings of definitions starting with '- ' at column 0, followed by a
#   key/value pair
# - key/value pairs consisting of '  ' at column 0, containing either a
#   spaceless value on the same line, or a multi-line value
# - multiline values indented by more than 2 spaces, starting with a '>'
#
# If these constraints are violated, Teliva may unceremoniously crash. Please
# report bugs at http://akkartik.name/contact
- __teliva_timestamp: original
  str_helpers:
    >-- some string helpers from http://lua-users.org/wiki/StringIndexing
    >
    >-- index characters using []
    >getmetatable('').__index = function(str,i)
    >  if type(i) == 'number' then
    >    return str:sub(i,i)
    >  else
    >    return string[i]
    >  end
    >end
    >
    >-- ranges using (), selected bytes using {}
    >getmetatable('').__call = function(str,i,j)
    >  if type(i)~='table' then
    >    return str:sub(i,j)
    >  else
    >    local t={}
    >    for k,v in ipairs(i) do
    >      t[k]=str:sub(v,v)
    >    end
    >    return table.concat(t)
    >  end
    >end
    >
    >-- iterate over an ordered sequence
    >function q(x)
    >  if type(x) == 'string' then
    >    return x:gmatch('.')
    >  else
    >    return ipairs(x)
    >  end
    >end
    >
    >-- insert within string
    >function string.insert(str1, str2, pos)
    >  return str1:sub(1,pos)..str2..str1:sub(pos+1)
    >end
    >
    >function string.remove(s, pos)
    >  return s:sub(1,pos-1)..s:sub(pos+1)
    >end
    >
    >-- TODO: backport utf-8 support from Lua 5.3
- __teliva_timestamp: original
  check_eq:
    >function check_eq(x, expected, msg)
    >  if eq(x, expected) then
    >    Window:addch('.')
    >  else
    >    print('F - '..msg)
    >    print('  expected '..str(expected)..' but got '..str(x))
    >    teliva_num_test_failures = teliva_num_test_failures + 1
    >    -- overlay first test failure on editors
    >    if teliva_first_failure == nil then
    >      teliva_first_failure = msg
    >    end
    >  end
    >end
- __teliva_timestamp: original
  eq:
    >function eq(a, b)
    >  if type(a) ~= type(b) then return false end
    >  if type(a) == 'table' then
    >    if #a ~= #b then return false end
    >    for k, v in pairs(a) do
    >      if b[k] ~= v then
    >        return false
    >      end
    >      return true
    >    end
    >  end
    >  return a == b
    >end
- __teliva_timestamp: original
  str:
    >-- smarter tostring
    >-- slow; used only for debugging
    >function str(x)
    >  if type(x) == 'table' then
    >    local result = ''
    >    result = result..#x..'{'
    >    for k, v in pairs(x) do
    >      result = result..str(k)..'='..str(v)..', '
    >    end
    >    result = result..'}'
    >    return result
    >  end
    >  return tostring(x)
    >end
- __teliva_timestamp: original
  map:
    >-- only for arrays
    >function map(l, f)
    >  result = {}
    >  for _, x in ipairs(l) do
    >    table.insert(result, f(x))
    >  end
    >  return result
    >end
- __teliva_timestamp: original
  reduce:
    >-- only for arrays
    >function reduce(l, f, init)
    >  result = init
    >  for _, x in ipairs(l) do
    >    result = f(result, x)
    >  end
    >  return result
    >end
- __teliva_timestamp: original
  filter:
    >-- only for arrays
    >function filter(l, f)
    >  result = {}
    >  for _, x in ipairs(l) do
    >    if f(x) then
    >      table.insert(result, x)
    >    end
    >  end
    >  return result
    >end
- __teliva_timestamp: original
  find_index:
    >function find_index(arr, x)
    >  for n, y in ipairs(arr) do
    >    if x == y then
    >      return n
    >    end
    >  end
    >end
- __teliva_timestamp: original
  trim:
    >function trim(s)
    >  return s:gsub('^%s*', ''):gsub('%s*$', '')
    >end
- __teliva_timestamp: original
  split:
    >function split(s, d)
    >  result = {}
    >  for match in (s..d):gmatch("(.-)"..d) do
    >    table.insert(result, match);
    >  end
    >  return result
    >end
- __teliva_timestamp: original
  sort_letters:
    >function sort_letters(s)
    >  tmp = {}
    >  for i=1,#s do
    >    table.insert(tmp, s[i])
    >  end
    >  table.sort(tmp)
    >  local result = ''
    >  for _, c in pairs(tmp) do
    >    result = result..c
    >  end
    >  return result
    >end
    >
    >function test_sort_letters(s)
    >  check_eq(sort_letters(''), '', 'test_sort_letters: empty')
    >  check_eq(sort_letters('ba'), 'ab', 'test_sort_letters: non-empty')
    >  check_eq(sort_letters('abba'), 'aabb', 'test_sort_letters: duplicates')
    >end
- __teliva_timestamp: original
  count_letters:
    >function count_letters(s)
    >  local result = {}
    >  for i=1,s:len() do
    >    local c = s[i]
    >    if result[c] == nil then
    >      result[c] = 1
    >    else
    >      result[c] = result[c] + 1
    >    end
    >  end
    >  return result
    >end
- __teliva_timestamp: original
  append:
    >-- concatenate list 'elems' into 'l', modifying 'l' in the process
    >function append(l, elems)
    >  for i=1,#elems do
    >    l[#l+1] = elems[i]
    >  end
    >end
- __teliva_timestamp: original
  menu:
    >-- To show app-specific hotkeys in the menu bar, add hotkey/command
    >-- arrays of strings to the menu array.
    >menu = {
    >  {'^h', 'backspace'},
    >}
- __teliva_timestamp: original
  Window:
    >Window = curses.stdscr()
- __teliva_timestamp: original
  doc:blurb:
    >Show all anagrams of a given word
- __teliva_timestamp: original
  word:
    >word = ''
- __teliva_timestamp: original
  cursor:
    >cursor = 1
- __teliva_timestamp: original
  main:
    >function main()
    >  Window:nodelay(true)
    >  while true do
    >    render(Window)
    >    update(Window)
    >  end
    >end
- __teliva_timestamp: original
  update:
    >function update(window)
    >  local key
    >  while true do
    >    key = window:getch()
    >    if key then break end
    >  end
    >  if key == curses.KEY_LEFT then
    >    if cursor > 1 then
    >      cursor = cursor-1
    >    end
    >  elseif key == curses.KEY_RIGHT then
    >    if cursor <= #word then
    >      cursor = cursor+1
    >    end
    >  elseif key == curses.KEY_BACKSPACE or key == 8 or key == 127 then  -- ctrl-h, ctrl-?, delete
    >    if cursor > 1 then
    >      cursor = cursor-1
    >      word = word:remove(cursor)
    >    end
    >  elseif key >= 32 and key < 127 then
    >    word = word:insert(string.char(key), cursor-1)
    >    cursor = cursor+1
    >  end
    >end
- __teliva_timestamp: original
  render:
    >function render(window)
    >  window:clear()
    >
    >  local prompt_str = ' what is your name? '
    >  window:attron(curses.A_REVERSE)
    >  window:mvaddstr(0, 0, prompt_str)
    >  window:attroff(curses.A_REVERSE)
    >  window:addstr(' ')
    >  window:attron(curses.A_BOLD)
    >  window:addstr(word)
    >  window:attroff(curses.A_BOLD)
    >  window:mvaddstr(2, 0, '')
    >  local results = anagrams(word)
    >  if #results > 0 then
    >    window:attron(curses.A_REVERSE)
    >    print(#results..' anagrams')
    >    window:attroff(curses.A_REVERSE)
    >    for i, w in ipairs(results) do
    >      window:addstr(w)
    >      window:addstr(' ')
    >    end
    >  end
    >
    >  window:mvaddstr(0, string.len(prompt_str)+cursor, '')
    >  window:refresh()
    >end
- __teliva_timestamp:
    >Mon Feb 21 17:42:28 2022
  anagrams:
    >function anagrams(word)
    >  return gather(sort_letters(word))
    >end
- __teliva_timestamp:
    >Mon Feb 21 18:18:07 2022
  gather:
    >function gather(s)
    >  if s == '' then return {} end
    >  local result = {}
    >  for i=1, #s do
    >    if i == 1 or s[i] ~= s[i-1] then
    >      append(result, combine(s[i], gather(take_out(s, i))))
    >    end
    >  end
    >  return result
    >end
- __teliva_timestamp:
    >Mon Feb 21 18:06:20 2022
  take_out:
    >function take_out(s, i)
    >  if i < 1 then return s:sub(1) end
    >  return s:sub(1, i-1) .. s:sub(i+1)
    >end
    >
    >function test_take_out()
    >  check_eq(take_out('', 0), '', 'take_out: empty')
    >  check_eq(take_out('abc', 0), 'abc', 'take_out: invalid low index')
    >  check_eq(take_out('abc', 4), 'abc', 'take_out: invalid high index')
    >  check_eq(take_out('abc', 1), 'bc', 'take_out: first index')
    >  check_eq(take_out('abc', 3), 'ab', 'take_out: final index')
    >  check_eq(take_out('abc', 2), 'ac', 'take_out: middle index')
    >end
- __teliva_timestamp: original
  __teliva_note:
    >basic version
  combine:
    >-- return 'l' with each element prefixed with 'prefix'
    >function combine(prefix, l)
    >  if #l == 0 then return {prefix} end
    >  local result = {}
    >  for _, elem in ipairs(l) do
    >    result[#result+1] = prefix..elem
    >  end
    >  return result
    >end
    >
    >function test_combine()
    >  check_eq(combine('abc', {}), {'abc'}, 'test_combine: empty list')
    >end
- __teliva_timestamp:
    >Sat Mar  5 15:24:00 2022
  count_anagrams:
    >function count_anagrams(s)
    >  local result = factorial(s:len())
    >  local letter_counts = count_letters(s)
    >  for k, v in pairs(letter_counts) do
    >    result = result / factorial(v)
    >  end
    >  return result
    >end
- __teliva_timestamp:
    >Sat Mar  5 15:24:34 2022
  factorial:
    >function factorial(n)
    >  local result = 1
    >  for i=1,n do
    >    result = result*i
    >  end
    >  return result
    >end
- __teliva_timestamp:
    >Sat Mar  5 15:53:23 2022
  key_pressed:
    >-- only works when nodelay (non-blocking keyboard)
    >function key_pressed(window)
    >  local c = window:getch()
    >  if c == nil then return false end
    >  window:ungetch(c)
    >  return true
    >end
- __teliva_timestamp:
    >Sat Mar  5 15:55:34 2022
  render:
    >function render(window)
    >  window:clear()
    >
    >  local prompt_str = ' what is your name? '
    >  window:attron(curses.A_REVERSE)
    >  window:mvaddstr(0, 0, prompt_str)
    >  window:attroff(curses.A_REVERSE)
    >  window:addstr(' ')
    >  window:attron(curses.A_BOLD)
    >  window:addstr(word)
    >  window:attroff(curses.A_BOLD)
    >  window:mvaddstr(2, 0, '')
    >  if #word > 0 then
    >    local num_anagrams = count_anagrams(word)
    >    window:attron(curses.A_REVERSE)
    >    print(num_anagrams..' anagrams')
    >    window:attroff(curses.A_REVERSE)
    >    local results = anagrams(word)
    >    if results == nil then  -- interrupted
    >      window:addstr('...')
    >    else
    >      assert(#results == num_anagrams, "something's wrong; the count is unexpected")
    >      for i, w in ipairs(results) do
    >        window:addstr(w)
    >        window:addstr(' ')
    >        if key_pressed(window) then
    >          break
    >        end
    >      end
    >    end
    >  end
    >
    >  window:mvaddstr(0, string.len(prompt_str)+cursor, '')
    >  window:refresh()
    >end
- __teliva_timestamp:
    >Sat Mar  5 15:56:35 2022
  __teliva_note:
    >restart computation when a key is pressed
  gather:
    >-- return a list of unique permutations of a sorted string 's'
    >-- the letters in 's' must be in alphabetical order, so that duplicates are adjacent
    >-- this function can take a long time for long strings, so we make it interruptible
    >-- if a key is pressed, it returns nil
    >-- since it's recursive, we also need to handle recursive calls returning nil
    >function gather(s)
    >  if s == '' then return {} end
    >  local result = {}
    >  for i=1, #s do
    >    if i == 1 or s[i] ~= s[i-1] then
    >      local subresult = gather(take_out(s, i))
    >      if subresult == nil then return nil end  -- interrupted
    >      append(result, combine(s[i], subresult))
    >    end
    >    if key_pressed(Window) then return nil end  -- interrupted
    >  end
    >  return result
    >end