RosettaCodeData/Task/Wordiff/Jq/wordiff.jq
2024-07-13 15:19:22 -07:00

108 lines
3.2 KiB
Text

include "MRG32k3a" {search: "."}; # see comment above
### Generic functions
# Determine if stream is non-decreasing
def is_sorted(stream):
first(foreach stream as $s ( null;
if . == null or $s >= .[0] then [$s]
else 0
end;
select(. == 0) ) )
// 1
| . == 1;
# Write to stderr
def inform(msg):
. as $in
| ("\(msg)\n" | stderr)
| $in;
# q means quit
def read($prompt; $regex):
def r:
($prompt | stderr | empty),
(try ((input
| if . == "q" then halt
else select(test($regex))
end) // r)
catch if . == "break" then halt else r end );
r;
# Returns levenshteinDistance(s1; $s2) <= $max
# A recursive algorithm is good enough if $max is small
def levenshteinDistance($s1; $s2; $max):
def lev:
. as [$s1, $s2, $max]
| if ($s1|length) == 0 then ($s2|length) <= $max
elif ($s2|length) == 0 then ($s1|length) <= $max
elif $s1[:1] == $s2[:1]
then [$s1[1:], $s2[1:], $max] | lev
else ($s1|length) <= $max
or ($s2|length) <= $max
or ([$s1[1:], $s2, $max-1] | lev)
or ([$s1, $s2[1:], $max-1] | lev)
or ([$s1[1:], $s2[1:], $max-1] | lev)
end ;
[$s1, $s2, $max] | lev;
##### The Wordiff Game
# Output: the sorted list of words
# The sort is skipped if we can readily determine the list is already sorted
def words:
[$dict | splits(" *\n *")]
| if is_sorted(.[]) then .
else sort
end;
def player1: read("Player 1, please enter your name : "; ".");
def player2: read("Player 2, please enter your name : "; ".");
def round:
.words as $words
| read("\(.player), enter your word: "; ".") as $word
| ($word|length) as $len
| .ok = false
| if $len < 3
then inform("Words must be at least three letters long.")
elif $word == .prevWord
then inform("You must change the previous word.")
elif .used[$word]
then inform("The word \"\($word)\" has been used before.")
elif # not in dictionary
# if $words is not sorted: ($word | IN($words[]) | not)
# binary search:
($words | bsearch($word) < 0)
then inform("Not in dictionary.")
elif levenshteinDistance($word; .prevWord; 1)
#### Good to go
then .ok = true
| .prevLen = ($word|length)
| .prevWord = $word
| .used[$word] = true
| .player = (if .player == .player1 then .player2 else .player1 end)
else inform("Sorry. Only one addition, deletion or alteration is allowed.")
end
| if (.ok|not) then inform("Please retry. The current word is: \(.prevWord)") end
| round ;
def play:
player1 as $player1
| { player1: $player1,
player2: ( player2 | if . == $player1 then . + "2" else . end)
}
| inform("Reading and perhaps sorting the list of words takes a few seconds... ")
| inform("Meanwhile note that you can quit by entering q at a prompt.")
| .words = words
# Avoid storing the subset of words needed to make a random selection:
| (.words | map(select(length | IN(3,4))) | .[length | prn(1)[]]) as $firstWord
| .prevLen = ($firstWord|length)
| .prevWord = $firstWord
| .used = {($firstWord): true} # a JSON object for efficiency
| .player = .player1
| "\nThe first word is: \($firstWord)",
round;
play