package main

import (
    "fmt"
    "strings"
)

var encoded =
    "MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH" +
    "VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD" +
    "ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS" +
    "FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG" +
    "ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ" +
    "ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS" +
    "JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT" +
    "LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST" +
    "MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH" +
    "QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV" +
    "RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW" +
    "TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO" +
    "SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR" +
    "ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX" +
    "BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB" +
    "BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA" +
    "FWAML ZZRXJ EKAHV FASMU LVVUT TGK"

var freq = [26]float64{
    0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015,
    0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749,
    0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758,
    0.00978, 0.02360, 0.00150, 0.01974, 0.00074,
}

func sum(a []float64) (sum float64) {
    for _, f := range a {
        sum += f
    }
    return
}

func bestMatch(a []float64) int {
    sum := sum(a)
    bestFit, bestRotate := 1e100, 0
    for rotate := 0; rotate < 26; rotate++ {
        fit := 0.0
        for i := 0; i < 26; i++ {
            d := a[(i+rotate)%26]/sum - freq[i]
            fit += d * d / freq[i]
        }
        if fit < bestFit {
            bestFit, bestRotate = fit, rotate
        }
    }
    return bestRotate
}

func freqEveryNth(msg []int, key []byte) float64 {
    l := len(msg)
    interval := len(key)
    out := make([]float64, 26)
    accu := make([]float64, 26)
    for j := 0; j < interval; j++ {
        for k := 0; k < 26; k++ {
            out[k] = 0.0
        }
        for i := j; i < l; i += interval {
            out[msg[i]]++
        }
        rot := bestMatch(out)
        key[j] = byte(rot + 65)
        for i := 0; i < 26; i++ {
            accu[i] += out[(i+rot)%26]
        }
    }
    sum := sum(accu)
    ret := 0.0
    for i := 0; i < 26; i++ {
        d := accu[i]/sum - freq[i]
        ret += d * d / freq[i]
    }
    return ret
}

func decrypt(text, key string) string {
    var sb strings.Builder
    ki := 0
    for _, c := range text {
        if c < 'A' || c > 'Z' {
            continue
        }
        ci := (c - rune(key[ki]) + 26) % 26
        sb.WriteRune(ci + 65)
        ki = (ki + 1) % len(key)
    }
    return sb.String()
}

func main() {
    enc := strings.Replace(encoded, " ", "", -1)
    txt := make([]int, len(enc))
    for i := 0; i < len(txt); i++ {
        txt[i] = int(enc[i] - 'A')
    }
    bestFit, bestKey := 1e100, ""
    fmt.Println("  Fit     Length   Key")
    for j := 1; j <= 26; j++ {
        key := make([]byte, j)
        fit := freqEveryNth(txt, key)
        sKey := string(key)
        fmt.Printf("%f    %2d     %s", fit, j, sKey)
        if fit < bestFit {
            bestFit, bestKey = fit, sKey
            fmt.Print(" <--- best so far")
        }
        fmt.Println()
    }
    fmt.Println("\nBest key :", bestKey)
    fmt.Printf("\nDecrypted text:\n%s\n", decrypt(enc, bestKey))
}


{-# LANGUAGE TupleSections #-}
import Data.List(transpose, nub, sort, maximumBy)
import Data.Ord (comparing)
import Data.Char (ord)
import Data.Map (Map, fromListWith, toList, findWithDefault)

average :: Fractional a => [a] -> a
average as = sum as / fromIntegral (length as)

-- Create a map from each entry in list to the number of occurrences of
-- that entry in the list.
countEntries :: Ord a => [a] -> Map a Int
countEntries = fromListWith (+) . fmap (,1)

-- Break a string up into substrings of n chars.
breakup :: Int -> [a] -> [[a]]
breakup _ [] = []
breakup n as =
    let (h, r) = splitAt n as
    in h:breakup n r

-- Dole out elements of a string over a n element distribution.
distribute :: [a] -> Int -> [[a]]
distribute as n = transpose $ breakup n as

-- The probability that members of a pair of characters taken randomly
-- from a given string are equal.
coincidence :: (Ord a, Fractional b) => [a] -> b
coincidence str =
    let charCounts = snd <$> toList (countEntries str)
        strln = length str
        d = fromIntegral $ strln * (strln - 1)
        n = fromIntegral $ sum $ fmap (\cc -> cc * (cc-1)) charCounts
    in n / d

-- Use the average probablity of coincidence for all the members of
-- a distribution to rate the distribution - the higher the better.
-- The correlation increases artificially for smaller
-- pieces/longer keys, so weigh against them a little
rate :: (Ord a, Fractional b) => [[a]] -> b
rate d =  average (fmap coincidence d) - fromIntegral (length d) / 3000.0

-- Multiply elements of lists together and add up the results.
dot :: Num a => [a] -> [a] -> a
dot v0 v1 = sum $ zipWith (*) v0 v1

-- Given two lists of floats, rotate one of them by the number of
-- characters indicated by letter and then 'dot' them together.
rotateAndDot :: Num a => [a] -> [a] -> Char -> a
rotateAndDot v0 v1 letter = dot v0 (drop (ord letter - ord 'A') (cycle v1))

-- Find decoding offset that results in best match
-- between actual char frequencies and expected frequencies.
getKeyChar :: RealFrac a => [a] -> String -> Char
getKeyChar expected sample =
    let charCounts = countEntries sample
        countInSample c = findWithDefault 0 c charCounts
        actual = fmap (fromIntegral . countInSample) ['A'..'Z']
    in maximumBy (comparing $ rotateAndDot expected actual) ['A'..'Z']

main = do
    let cr = filter (/=' ') crypt
        -- Assume that if there are less than 20 characters
        -- per column, the key's too long to guess
        distributions = fmap (distribute cr) [1..length cr `div` 20]
        bestDistribution = maximumBy (comparing rate) distributions
        key = fmap (getKeyChar englishFrequencies) bestDistribution
        alphaSum a b = ['A'..'Z'] !! ((ord b - ord a) `mod` 26)
    mapM_ putStrLn ["Key: " ++ key, "Decrypted Text: " ++ zipWith alphaSum (cycle key) cr]

englishFrequencies =
    [ 0.08167, 0.01492, 0.02782, 0.04253,
      0.12702, 0.02228, 0.02015, 0.06094,
      0.06966, 0.00153, 0.00772, 0.04025,
      0.02406, 0.06749, 0.07507, 0.01929,
      0.00095, 0.05987, 0.06327, 0.09056,
      0.02758, 0.00978, 0.02360, 0.00150,
      0.01974, 0.00074 ]

crypt = "\
    \MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH\
    \VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD\
    \ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS\
    \FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG\
    \ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ\
    \ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS\
    \JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT\
    \LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST\
    \MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH\
    \QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV\
    \RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW\
    \TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO\
    \SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR\
    \ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX\
    \BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB\
    \BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA\
    \FWAML ZZRXJ EKAHV FASMU LVVUT TGK\
    \"


public class Vig{
static String encodedMessage =
    "MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA FWAML ZZRXJ EKAHV FASMU LVVUT TGK";

final static double freq[] = {
    0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015,
    0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749,
    0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758,
    0.00978, 0.02360, 0.00150, 0.01974, 0.00074
};

public static void main(String[] args) {
    int lenghtOfEncodedMessage = encodedMessage.length();
    char[] encoded = new char [lenghtOfEncodedMessage] ;
    char[] key =  new char [lenghtOfEncodedMessage] ;

    encodedMessage.getChars(0, lenghtOfEncodedMessage, encoded, 0);
    int txt[] = new int[lenghtOfEncodedMessage];
    int len = 0, j;

    double fit, best_fit = 1e100;

    for (j = 0; j < lenghtOfEncodedMessage; j++)
        if (Character.isUpperCase(encoded[j]))
            txt[len++] = encoded[j] - 'A';

    for (j = 1; j < 30; j++) {
        fit = freq_every_nth(txt, len, j, key);
        System.out.printf("%f, key length: %2d ", fit, j);
            System.out.print(key);
        if (fit < best_fit) {
            best_fit = fit;
            System.out.print(" <--- best so far");
        }
        System.out.print("\n");

    }
}

    static String decrypt(String text, final String key) {
        String res = "";
        text = text.toUpperCase();
        for (int i = 0, j = 0; i < text.length(); i++) {
            char c = text.charAt(i);
            if (c < 'A' || c > 'Z') continue;
            res += (char)((c - key.charAt(j) + 26) % 26 + 'A');
            j = ++j % key.length();
        }
        return res;
    }

static int best_match(final double []a, final double []b) {
    double sum = 0, fit, d, best_fit = 1e100;
    int i, rotate, best_rotate = 0;
    for (i = 0; i < 26; i++)
        sum += a[i];
    for (rotate = 0; rotate < 26; rotate++) {
        fit = 0;
        for (i = 0; i < 26; i++) {
            d = a[(i + rotate) % 26] / sum - b[i];
            fit += d * d / b[i];
        }

        if (fit < best_fit) {
            best_fit = fit;
            best_rotate = rotate;
        }
    }

    return best_rotate;
}

static double freq_every_nth(final int []msg, int len, int interval, char[] key) {
    double sum, d, ret;
    double  [] accu = new double [26];
    double  [] out = new double [26];
    int i, j, rot;

    for (j = 0; j < interval; j++) {
        for (i = 0; i < 26; i++)
            out[i] = 0;
        for (i = j; i < len; i += interval)
            out[msg[i]]++;
	rot = best_match(out, freq);
	try{
            key[j] = (char)(rot + 'A');
	} catch (Exception e) {
		System.out.print(e.getMessage());
	}
        for (i = 0; i < 26; i++)
            accu[i] += out[(i + rot) % 26];
    }

    for (i = 0, sum = 0; i < 26; i++)
        sum += accu[i];

    for (i = 0, ret = 0; i < 26; i++) {
        d = accu[i] / sum - freq[i];
        ret += d * d / freq[i];
    }

    key[interval] = '\0';
    return ret;
}

}


# ciphertext block {{{1
const ciphertext = filter(isalpha, """
MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH
VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD
ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS
FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG
ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ
ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS
JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT
LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST
MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH
QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV
RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW
TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO
SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR
ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX
BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB
BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA
FWAML ZZRXJ EKAHV FASMU LVVUT TGK
""")
# }}}

# character frequencies {{{1
const letters = Dict{Char, Float32}(
    'E' => 12.702,
    'T' => 9.056,
    'A' => 8.167,
    'O' => 7.507,
    'I' => 6.966,
    'N' => 6.749,
    'S' => 6.327,
    'H' => 6.094,
    'R' => 5.987,
    'D' => 4.253,
    'L' => 4.025,
    'C' => 2.782,
    'U' => 2.758,
    'M' => 2.406,
    'W' => 2.361,
    'F' => 2.228,
    'G' => 2.015,
    'Y' => 1.974,
    'P' => 1.929,
    'B' => 1.492,
    'V' => 0.978,
    'K' => 0.772,
    'J' => 0.153,
    'X' => 0.150,
    'Q' => 0.095,
    'Z' => 0.074)
const digraphs = Dict{AbstractString, Float32}(
    "TH" => 15.2,
    "HE" => 12.8,
    "IN" => 9.4,
    "ER" => 9.4,
    "AN" => 8.2,
    "RE" => 6.8,
    "ND" => 6.3,
    "AT" => 5.9,
    "ON" => 5.7,
    "NT" => 5.6,
    "HA" => 5.6,
    "ES" => 5.6,
    "ST" => 5.5,
    "EN" => 5.5,
    "ED" => 5.3,
    "TO" => 5.2,
    "IT" => 5.0,
    "OU" => 5.0,
    "EA" => 4.7,
    "HI" => 4.6,
    "IS" => 4.6,
    "OR" => 4.3,
    "TI" => 3.4,
    "AS" => 3.3,
    "TE" => 2.7,
    "ET" => 1.9,
    "NG" => 1.8,
    "OF" => 1.6,
    "AL" => 0.9,
    "DE" => 0.9,
    "SE" => 0.8,
    "LE" => 0.8,
    "SA" => 0.6,
    "SI" => 0.5,
    "AR" => 0.4,
    "VE" => 0.4,
    "RA" => 0.4,
    "LD" => 0.2,
    "UR" => 0.2)
const trigraphs = Dict{AbstractString, Float32}(
    "THE" => 18.1,
    "AND" => 7.3,
    "ING" => 7.2,
    "ION" => 4.2,
    "ENT" => 4.2,
    "HER" => 3.6,
    "FOR" => 3.4,
    "THA" => 3.3,
    "NTH" => 3.3,
    "INT" => 3.2,
    "TIO" => 3.1,
    "ERE" => 3.1,
    "TER" => 3.0,
    "EST" => 2.8,
    "ERS" => 2.8,
    "HAT" => 2.6,
    "ATI" => 2.6,
    "ATE" => 2.5,
    "ALL" => 2.5,
    "VER" => 2.4,
    "HIS" => 2.4,
    "HES" => 2.4,
    "ETH" => 2.4,
    "OFT" => 2.2,
    "STH" => 2.1,
    "RES" => 2.1,
    "OTH" => 2.1,
    "ITH" => 2.1,
    "FTH" => 2.1,
    "ONT" => 2.0)
# 1}}}

function decrypt(enc::ASCIIString, key::ASCIIString)
    const enclen = length(enc)
    const keylen = length(key)

    if keylen < enclen
        key = (key^(div(enclen - keylen, keylen) + 2))[1:enclen]
    end

    msg = Array(Char, enclen)

    for i=1:enclen
        msg[i] = Char((Int(enc[i]) - Int(key[i]) + 26) % 26 + 65)
    end

    msg::Array{Char, 1}
end

function cryptanalyze(enc::ASCIIString; maxkeylen::Integer = 20)
    const enclen = length(enc)
    maxkey = ""
    maxdec = ""
    maxscore = 0.0

    for keylen=1:maxkeylen
        key = Array(Char, keylen)
        idx = filter(x -> x % keylen == 0, 1:enclen) - keylen + 1

        for i=1:keylen
            maxsubscore = 0.0

            for j='A':'Z'
                subscore = 0.0

                for k in decrypt(enc[idx], ascii(string(j)))
                    subscore += get(letters, k, 0.0)
                end

                if subscore > maxsubscore
                    maxsubscore = subscore
                    key[i] = j
                end
            end

            idx += 1
        end

        key = join(key)
        const dec = decrypt(enc, key)
        score = 0.0

        for i in dec
            score += get(letters, i, 0.0)
        end

        for i=1:enclen - 2
            const digraph = string(dec[i], dec[i + 1])
            const trigraph = string(dec[i], dec[i + 1], dec[i + 2])

            if haskey(digraphs, digraph)
                score += 2 * get(digraphs, digraph, 0.0)
            end

            if haskey(trigraphs, trigraph)
                score += 3 * get(trigraphs, trigraph, 0.0)
            end
        end

        if score > maxscore
            maxscore = score
            maxkey = key
            maxdec = dec
        end
    end

    (maxkey, join(maxdec))::Tuple{ASCIIString, ASCIIString}
end

key, dec = cryptanalyze(ciphertext)
println("key: ", key, "\n\n", dec)

# post-compilation profiling run
gc()
t = @elapsed cryptanalyze(ciphertext)
println("\nelapsed time: ", t, " seconds")


// version 1.1.3

val encoded =
    "MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH" +
    "VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD" +
    "ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS" +
    "FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG" +
    "ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ" +
    "ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS" +
    "JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT" +
    "LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST" +
    "MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH" +
    "QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV" +
    "RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW" +
    "TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO" +
    "SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR" +
    "ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX" +
    "BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB" +
    "BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA" +
    "FWAML ZZRXJ EKAHV FASMU LVVUT TGK"

val freq = doubleArrayOf(
    0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015,
    0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749,
    0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758,
    0.00978, 0.02360, 0.00150, 0.01974, 0.00074
)

fun bestMatch(a: DoubleArray): Int {
    val sum = a.sum()
    var bestFit = 1e100
    var bestRotate = 0
    for (rotate in 0..25) {
        var fit = 0.0
        for (i in 0..25) {
            val d = a[(i + rotate) % 26] / sum - freq[i]
            fit += d * d / freq[i]
        }
        if (fit < bestFit) {
            bestFit = fit
            bestRotate = rotate
        }
    }
    return bestRotate
}

fun freqEveryNth(msg: IntArray, key: CharArray): Double {
    val len = msg.size
    val interval = key.size
    val out = DoubleArray(26)
    val accu = DoubleArray(26)
    for (j in 0 until interval) {
        out.fill(0.0)
        for (i in j until len step interval) out[msg[i]]++
        val rot = bestMatch(out)
        key[j] = (rot + 65).toChar()
        for (i in 0..25) accu[i] += out[(i + rot) % 26]
    }
    val sum = accu.sum()
    var ret = 0.0
    for (i in 0..25) {
        val d = accu[i] / sum - freq[i]
        ret += d * d / freq[i]
    }
    return ret
}

fun decrypt(text: String, key: String): String {
    val sb = StringBuilder()
    var ki = 0
    for (c in text) {
        if (c !in 'A'..'Z') continue
        val ci = (c.toInt() - key[ki].toInt() +  26) % 26
        sb.append((ci + 65).toChar())
        ki = (ki + 1) % key.length
    }
    return sb.toString()
}

fun main(args: Array<String>) {
    val enc = encoded.replace(" ", "")
    val txt = IntArray(enc.length) { enc[it] - 'A' }
    var bestFit = 1e100
    var bestKey = ""
    val f = "%f    %2d     %s"
    println("  Fit     Length   Key")
    for (j in 1..26) {
        val key = CharArray(j)
        val fit = freqEveryNth(txt, key)
        val sKey = key.joinToString("")
        print(f.format(fit, j, sKey))
        if (fit < bestFit) {
           bestFit = fit
           bestKey = sKey
           print(" <--- best so far")
        }
        println()
    }
    println()
    println("Best key : $bestKey")
    println("\nDecrypted text:\n${decrypt(enc, bestKey)}")
}


import sequtils, strutils, sugar, tables, times

const

  CipherText = """MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH
                  VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD
                  ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS
                  FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG
                  ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ
                  ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS
                  JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT
                  LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST
                  MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH
                  QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV
                  RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW
                  TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO
                  SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR
                  ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX
                  BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB
                  BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA
                  FWAML ZZRXJ EKAHV FASMU LVVUT TGK""".splitWhitespace.join()

  FreqLetters = {'E': 12.702, 'T': 9.056, 'A': 8.167, 'O': 7.507,
                 'I':  6.966, 'N': 6.749, 'S': 6.327, 'H': 6.094,
                 'R':  5.987, 'D': 4.253, 'L': 4.025, 'C': 2.782,
                 'U':  2.758, 'M': 2.406, 'W': 2.361, 'F': 2.228,
                 'G':  2.015, 'Y': 1.974, 'P': 1.929, 'B': 1.492,
                 'V':  0.978, 'K': 0.772, 'J': 0.153, 'X': 0.150,
                 'Q':  0.095, 'Z': 0.074}.toTable

  FreqDigraphs = {"TH": 15.2, "HE": 12.8, "IN": 9.4, "ER": 9.4,
                  "AN":  8.2, "RE":  6.8, "ND": 6.3, "AT": 5.9,
                  "ON":  5.7, "NT":  5.6, "HA": 5.6, "ES": 5.6,
                  "ST":  5.5, "EN":  5.5, "ED": 5.3, "TO": 5.2,
                  "IT":  5.0, "OU":  5.0, "EA": 4.7, "HI": 4.6,
                  "IS":  4.6, "OR":  4.3, "TI": 3.4, "AS": 3.3,
                  "TE":  2.7, "ET":  1.9, "NG": 1.8, "OF": 1.6,
                  "AL":  0.9, "DE":  0.9, "SE": 0.8, "LE": 0.8,
                  "SA":  0.6, "SI":  0.5, "AR": 0.4, "VE": 0.4,
                  "RA":  0.4, "LD":  0.2, "UR": 0.2}.toTable

  FreqTrigraphs = {"THE": 18.1, "AND": 7.3, "ING": 7.2, "ION": 4.2,
                   "ENT":  4.2, "HER": 3.6, "FOR": 3.4, "THA": 3.3,
                   "NTH":  3.3, "INT": 3.2, "TIO": 3.1, "ERE": 3.1,
                   "TER":  3.0, "EST": 2.8, "ERS": 2.8, "HAT": 2.6,
                   "ATI":  2.6, "ATE": 2.5, "ALL": 2.5, "VER": 2.4,
                   "HIS":  2.4, "HES": 2.4, "ETH": 2.4, "OFT": 2.2,
                   "STH":  2.1, "RES": 2.1, "OTH": 2.1, "ITH": 2.1,
                   "FTH":  2.1, "ONT": 2.0}.toTable

func decrypt(enc, key: string): string =
  let encLen = enc.len
  let keyLen = key.len
  result.setLen(encLen)
  var k = 0
  for i in 0..<encLen:
    result[i] = chr((ord(enc[i]) - ord(key[k]) + 26) mod 26 + ord('A'))
    k = (k + 1) mod keyLen

func cryptanalyze(enc: string; maxKeyLen = 20): tuple[maxKey, maxDec: string] =
  let encLen = enc.len
  var maxScore = 0.0

  for keyLen in 1..maxKeyLen:
    var key = newString(keyLen)
    var idx = collect(newSeq):
                for i in 1..encLen:
                  if i mod keyLen == 0:
                    i - keyLen

    for  i in 0..<keyLen:
      var maxSubscore = 0.0
      for j in 'A'..'Z':
        var subscore = 0.0
        let encidx = idx.mapIt(enc[it]).join()
        for k in decrypt(encidx, $j):
          subscore += FreqLetters[k]
        if subscore > maxSubscore:
          maxSubscore = subscore
          key[i] = j
      for item in idx.mitems: inc item

    let dec = decrypt(enc, key)
    var score = 0.0
    for i in dec:
      score += FreqLetters[i]

    for i in 0..(encLen - 3):
      let digraph = dec[i..(i+1)]
      let trigraph = dec[i..(i+2)]
      score += 2 * FreqDigraphs.getOrDefault(digraph)
      score += 3 * FreqTrigraphs.getOrDefault(trigraph)

    if score > maxScore:
      maxScore = score
      result.maxKey = key
      result.maxDec = dec

let t0 = cpuTime()
let (key, dec) = CipherText.cryptanalyze()
echo "key: ", key, '\n'
echo dec, '\n'
echo "Elapsed time: ", (cpuTime() - t0).formatFloat(ffDecimal, precision = 3), " s"


(* Task : Vigenere cipher/Cryptanalysis *)

(*
	Given some text you suspect has been encrypted
	with a Vigenère cipher, extract the key and plaintext.
	Uses correlation factors similar to other solutions.
	(originally tried Friedman test, didn't produce good result)

	Coded in a way that allows non-english (by passing frequencies).
*)

(*** Helpers ***)

(* Implementation of Float.round to avoid v4.08 *)
let round (x : float) : float =
    let rem = mod_float x 1. in
    if rem >= 0.5
    then ceil x
    else floor x

(* A function that updates array element at a position *)
let array_update (arr : 'a array) (idx : int) (update : 'a -> 'a) : unit =
    let curr = Array.get arr idx in
    Array.set arr idx (update curr)

(*** Actual task at hand ***)

(* the n'th element of array is how often the n'th letter was found *)
let observe_coincidences ?(step : int = 1) ?(offset : int = 0) (text : string) : int array =
    let arr = Array.make 26 0 in
    let a_code = Char.code 'A' in
    String.iteri (fun idx c -> if idx mod step = offset then array_update arr (Char.code c - a_code) succ) text;
    arr

(* Obtain correlation factor for the observed coincidences *)
let correlation_factor ?(sort : bool = true) (coincidences : int array) (freqs : float list) : float =
    let clist = Array.to_list coincidences in
    let clist = (if sort then List.sort compare clist else clist) in
    List.fold_left2 (fun acc c f -> acc +. (float_of_int c *. f)) 0. clist freqs

(* Translation of the test used in other Rosetta Code solutions *)
let shifted_coincidences_test (freqs : float list) (text : string) : int =
    let sorted_freqs = List.sort compare freqs in
    let bestCorr = -100. in
    let max_keylen = String.length text / 20 in
    let rec helper idx (cur_len, cur_corr) (best_len, best_corr) =
        if cur_len = max_keylen then (* Finished testing everything *)
            best_len
        else if idx = cur_len then (* Finished testing this key length *)
            let (best_len, best_corr) = if cur_corr > best_corr then (cur_len, cur_corr) else (best_len, best_corr) in
            helper 0 (cur_len + 1,  ~-.0.5 *. float_of_int (cur_len + 1)) (best_len, best_corr)
        else
            let coincidences = observe_coincidences ~step:cur_len ~offset:idx text in
            let factor = correlation_factor coincidences sorted_freqs in
            helper (succ idx) (cur_len, cur_corr +. factor) (best_len, best_corr)
    in
    helper 0 (2, ~-.1.) (1, ~-.100.)

(* Returns the most likely shift value for this set *)
let break_caesar ?(step : int = 1) ?(offset : int = 0) (text : string) (freqs : float list) : int =
    let c_arr = observe_coincidences ~step ~offset text in
    let rec helper l curShift (maxShift, maxCorr) =
        if curShift = 26
        then maxShift
        else
            let corr = correlation_factor ~sort:false c_arr l in
            let l' = List.tl l @ [List.hd l] in
            if corr > maxCorr
            then helper l' (curShift + 1) (curShift, corr)
            else helper l' (curShift + 1) (maxShift, maxCorr)
    in
    helper freqs 0 (-1, -100.)

let break (keylen : int) (text : string) (freqs : float list) : key =
    let rec getCaesars idx acc =
        if idx >= keylen then acc else
        let shift = break_caesar ~step:keylen ~offset:idx text freqs in
        let new_code = if shift = 0 then Char.code 'A' else Char.code 'Z' + 1 - shift in
        getCaesars (succ idx) (acc ^ Char.(new_code |> chr |> escaped))
    in
    getCaesars 0 ""

let cryptanalyze (freqs : float list) (text : string) : key * string =
    let text = ascii_upper_letters_only text in
    let keylen = shifted_coincidences_test freqs text in
    let key = break keylen text freqs in
    let pt = decrypt key text in
    (key, pt)

(*** Output ***)

let _ =
    let long_text = "\
MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH \
VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD \
ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS \
FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG \
ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ \
ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS \
JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT \
LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST \
MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH \
QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV \
RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW \
TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO \
SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR \
ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX \
BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB \
BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA \
FWAML ZZRXJ EKAHV FASMU LVVUT TGK"
    in
    let english_freqs = [
        0.08167; 0.01492; 0.02782; 0.04253; 0.12702; 0.02228; 0.02015;
        0.06094; 0.06966; 0.00153; 0.00772; 0.04025; 0.02406; 0.06749;
        0.07507; 0.01929; 0.00095; 0.05987; 0.06327; 0.09056; 0.02758;
        0.00978; 0.02360; 0.00150; 0.01974; 0.00074
    ]
    in
    let (key, pt) = cryptanalyze english_freqs long_text in
    Printf.printf "Key:  %s\n\nText: %s" key pt
;;


use strict;
use warnings;
use feature 'say';

# from Wikipedia
my %English_letter_freq = (
     E => 12.70,  L => 4.03,  Y => 1.97,  P => 1.93,  T => 9.06,  A => 8.17,  O => 7.51,  I => 6.97,  N => 6.75,
     S =>  6.33,  H => 6.09,  R => 5.99,  D => 4.25,  C => 2.78,  U => 2.76,  M => 2.41,  W => 2.36,  F => 2.23,
     G =>  2.02,  B => 1.29,  V => 0.98,  K => 0.77,  J => 0.15,  X => 0.15,  Q => 0.10,  Z => 0.07
);
my @alphabet = sort keys %English_letter_freq;
my $max_key_lengths = 5; # number of keylengths to try

sub myguess {
    my ($text) = (@_);
    my ($seqtext, @spacing, @factors, @sortedfactors, $pos, %freq, %Keys);

    # Kasiski examination
    $seqtext = $text;
    while ($seqtext =~ /(...).*\1/) {
        $seqtext = substr($seqtext, 1+index($seqtext, $1));
        push @spacing,  1 + index($seqtext, $1);
    }

    for my $j (@spacing) {
        push @factors, grep { $j % $_ == 0 } 2..$j;
    }
    $freq{$_}++ for @factors;
    @sortedfactors = grep { $_ >= 4 } sort { $freq{$b} <=> $freq{$a} } keys %freq; # discard very short keys

    for my $keylen ( @sortedfactors[0..$max_key_lengths-1] ) {
        my $keyguess = '';
        for (my $i = 0; $i < $keylen; $i++) {
            my($mykey, %chi_values, $bestguess);
            for (my $j = 0; $j < length($text); $j += $keylen) {
                $mykey .= substr($text, ($j+$i) % length($text), 1);
            }

            for my $subkey (@alphabet) {
                my $decrypted = mycrypt($mykey, $subkey);
                my $length    = length($decrypted);
                for my $char (@alphabet) {
                    my $expected = $English_letter_freq{$char} * $length / 100;
                    my $observed;
                    ++$observed while $decrypted =~ /$char/g;
                    $chi_values{$subkey} += ($observed - $expected)**2 / $expected if $observed;
                }
            }

            $Keys{$keylen}{score} = $chi_values{'A'};
            for my $sk (sort keys %chi_values) {
                if ($chi_values{$sk} <= $Keys{$keylen}{score}) {
                    $bestguess = $sk;
                    $Keys{$keylen}{score} = $chi_values{$sk};
                }
            }
            $keyguess .= $bestguess;
        }
        $Keys{$keylen}{key} = $keyguess;
    }
    map { $Keys{$_}{key} } sort { $Keys{$a}{score} <=> $Keys{$b}{score}} keys %Keys;
}

sub mycrypt {
    my ($text, $key) = @_;
    my ($new_text, %values_numbers);

    my $keylen = length($key);
    @values_numbers{@alphabet} = 0..25;
    my %values_letters = reverse %values_numbers;

    for (my $i = 0; $i < length($text); $i++) {
        my $val =  -1 * $values_numbers{substr( $key, $i%$keylen, 1)} # negative shift for decode
                 +      $values_numbers{substr($text, $i,         1)};
        $new_text .= $values_letters{ $val % 26 };
    }
    return $new_text;
}

my $cipher_text = <<~'EOD';
    MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH
    VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD
    ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS
    FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG
    ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ
    ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS
    JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT
    LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST
    MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH
    QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV
    RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW
    TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO
    SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR
    ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX
    BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB
    BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA
    FWAML ZZRXJ EKAHV FASMU LVVUT TGK
EOD

my $text = uc($cipher_text) =~ s/[^@{[join '', @alphabet]}]//gr;

for my $key ( myguess($text) ) {
    say "Key        $key\n" .
        "Key length " . length($key) . "\n" .
        "Plaintext  " . substr(mycrypt($text, $key), 0, 80) . "...\n";
}


--
-- demo\rosetta\Cryptanalysis.exw
--
with javascript_semantics
atom t0 = time()
constant ciphertext = substitute_all("""
MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH
VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD
ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS
FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG
ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ
ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS
JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT
LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST
MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH
QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV
RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW
TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO
SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR
ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX
BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB
BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA
FWAML ZZRXJ EKAHV FASMU LVVUT TGK""",{" ","\n"},{"",""})

constant letters = new_dict(
   {{'E',12.702},
    {'T',9.056},
    {'A',8.167},
    {'O',7.507},
    {'I',6.966},
    {'N',6.749},
    {'S',6.327},
    {'H',6.094},
    {'R',5.987},
    {'D',4.253},
    {'L',4.025},
    {'C',2.782},
    {'U',2.758},
    {'M',2.406},
    {'W',2.361},
    {'F',2.228},
    {'G',2.015},
    {'Y',1.974},
    {'P',1.929},
    {'B',1.492},
    {'V',0.978},
    {'K',0.772},
    {'J',0.153},
    {'X',0.150},
    {'Q',0.095},
    {'Z',0.074}})
constant digraphs = new_dict(
   {{"TH",15.2},
    {"HE",12.8},
    {"IN",9.4},
    {"ER",9.4},
    {"AN",8.2},
    {"RE",6.8},
    {"ND",6.3},
    {"AT",5.9},
    {"ON",5.7},
    {"NT",5.6},
    {"HA",5.6},
    {"ES",5.6},
    {"ST",5.5},
    {"EN",5.5},
    {"ED",5.3},
    {"TO",5.2},
    {"IT",5.0},
    {"OU",5.0},
    {"EA",4.7},
    {"HI",4.6},
    {"IS",4.6},
    {"OR",4.3},
    {"TI",3.4},
    {"AS",3.3},
    {"TE",2.7},
    {"ET",1.9},
    {"NG",1.8},
    {"OF",1.6},
    {"AL",0.9},
    {"DE",0.9},
    {"SE",0.8},
    {"LE",0.8},
    {"SA",0.6},
    {"SI",0.5},
    {"AR",0.4},
    {"VE",0.4},
    {"RA",0.4},
    {"LD",0.2},
    {"UR",0.2}})
constant trigraphs = new_dict(
   {{"THE",18.1},
    {"AND",7.3},
    {"ING",7.2},
    {"ION",4.2},
    {"ENT",4.2},
    {"HER",3.6},
    {"FOR",3.4},
    {"THA",3.3},
    {"NTH",3.3},
    {"INT",3.2},
    {"TIO",3.1},
    {"ERE",3.1},
    {"TER",3.0},
    {"EST",2.8},
    {"ERS",2.8},
    {"HAT",2.6},
    {"ATI",2.6},
    {"ATE",2.5},
    {"ALL",2.5},
    {"VER",2.4},
    {"HIS",2.4},
    {"HES",2.4},
    {"ETH",2.4},
    {"OFT",2.2},
    {"STH",2.1},
    {"RES",2.1},
    {"OTH",2.1},
    {"ITH",2.1},
    {"FTH",2.1},
    {"ONT",2.0}})

function decrypt(string enc, string key)
    integer keylen = length(key), k = 1
    string msg = repeat(' ', length(enc))
    for i=1 to length(enc) do
        msg[i] = mod(enc[i]-key[k]+26,26)+'A'
        k = mod(k,keylen)+1
    end for
    return msg
end function

function cryptanalyze(string enc, integer maxkeylen=20)
    integer enclen = length(enc)
    string maxkey = "",
           maxdec = "",
           k1 = " "
    atom maxscore = 0.0

    for keylen=1 to maxkeylen do
        string key = repeat(' ',keylen)
        sequence idx = {}
        for i=1 to enclen do
            if mod(i,keylen)=0 then
                idx &= i-keylen+1
            end if
        end for

        for i=1 to keylen do
            atom maxsubscore = 0.0

            for j='A' to 'Z' do
                atom subscore = 0.0

                k1[1] = j
                string encidx = ""
                for ii=1 to length(idx) do
                    encidx &= enc[idx[ii]]
                end for
                string dec = decrypt(encidx,k1)
                for di=1 to length(dec) do
                    subscore += getd(dec[di],letters)
                end for

                if subscore > maxsubscore then
                    maxsubscore = subscore
                    key[i] = j
                end if
            end for

            idx = sq_add(idx,1)
        end for

        string dec = decrypt(enc, key)
        atom score = 0.0

        for i=1 to length(dec) do
            score += getd(dec[i],letters)
        end for

        for i=1 to enclen - 2 do
            string digraph = dec[i..i+1]
            string trigraph = dec[i..i + 2]
            score += 2 * getd(digraph,digraphs)
            score += 3 * getd(trigraph,trigraphs)
        end for

        if score > maxscore then
            maxscore = score
            maxkey = key
            maxdec = dec
        end if
    end for

    return {maxkey,maxdec}
end function

function fold(string s, integer w)
    for i=w to length(s) by w do
        s[i..i-1] = "\n"
    end for
    return s
end function

string {key, dec} = cryptanalyze(ciphertext)
printf(1,"key: %s\n\n%s\n\n", {key, fold(dec,80)})

printf(1,"elapsed time: %3.2f seconds",{time()-t0})

{} = wait_key()


from string import uppercase
from operator import itemgetter

def vigenere_decrypt(target_freqs, input):
    nchars = len(uppercase)
    ordA = ord('A')
    sorted_targets = sorted(target_freqs)

    def frequency(input):
        result = [[c, 0.0] for c in uppercase]
        for c in input:
            result[c - ordA][1] += 1
        return result

    def correlation(input):
        result = 0.0
        freq = frequency(input)
        freq.sort(key=itemgetter(1))

        for i, f in enumerate(freq):
            result += f[1] * sorted_targets[i]
        return result

    cleaned = [ord(c) for c in input.upper() if c.isupper()]
    best_len = 0
    best_corr = -100.0

    # Assume that if there are less than 20 characters
    # per column, the key's too long to guess
    for i in xrange(2, len(cleaned) // 20):
        pieces = [[] for _ in xrange(i)]
        for j, c in enumerate(cleaned):
            pieces[j % i].append(c)

        # The correlation seems to increase for smaller
        # pieces/longer keys, so weigh against them a little
        corr = -0.5 * i + sum(correlation(p) for p in pieces)

        if corr > best_corr:
            best_len = i
            best_corr = corr

    if best_len == 0:
        return ("Text is too short to analyze", "")

    pieces = [[] for _ in xrange(best_len)]
    for i, c in enumerate(cleaned):
        pieces[i % best_len].append(c)

    freqs = [frequency(p) for p in pieces]

    key = ""
    for fr in freqs:
        fr.sort(key=itemgetter(1), reverse=True)

        m = 0
        max_corr = 0.0
        for j in xrange(nchars):
            corr = 0.0
            c = ordA + j
            for frc in fr:
                d = (ord(frc[0]) - c + nchars) % nchars
                corr += frc[1] * target_freqs[d]

            if corr > max_corr:
                m = j
                max_corr = corr

        key += chr(m + ordA)

    r = (chr((c - ord(key[i % best_len]) + nchars) % nchars + ordA)
         for i, c in enumerate(cleaned))
    return (key, "".join(r))

def main():
    encoded = """
        MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH
        VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD
        ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS
        FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG
        ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ
        ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS
        JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT
        LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST
        MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH
        QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV
        RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW
        TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO
        SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR
        ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX
        BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB
        BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA
        FWAML ZZRXJ EKAHV FASMU LVVUT TGK"""

    english_frequences = [
        0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015,
        0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749,
        0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758,
        0.00978, 0.02360, 0.00150, 0.01974, 0.00074]

    (key, decoded) = vigenere_decrypt(english_frequences, encoded)
    print "Key:", key
    print "\nText:", decoded

main()


#lang at-exp racket

(define max-keylen 30)

(define text
  @~a{MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH
      VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD
      ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS
      FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG
      ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ
      ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS
      JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT
      LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST
      MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH
      QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV
      RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW
      TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO
      SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR
      ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX
      BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB
      BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA
      FWAML ZZRXJ EKAHV FASMU LVVUT TGK})

(define first-char (char->integer #\A))
(define chars# (- (char->integer #\Z) first-char -1))

(define freqs ; english letter frequencies from wikipedia
  ((compose1 list->vector (curry map (curryr / 100000.0)))
   '(8167 1492 2782 4253 12702 2228 2015 6094 6966 153 772 4025 2406
     6749 7507 1929 95 5987 6327 9056 2758 978 2360 150 1974 74)))

(define text* (for/vector ([c (regexp-replace* #px"\\s+" text "")])
                (- (char->integer c) first-char)))
(define N (vector-length text*))

(define (col-guesses len)
  (for/list ([ofs len])
    (define text (for/list ([i (in-range ofs N len)]) (vector-ref text* i)))
    (define cN (length text))
    (define cfreqs (make-vector chars# 0))
    (for ([c (in-list text)])
      (vector-set! cfreqs c (add1 (vector-ref cfreqs c))))
    (for ([i chars#]) (vector-set! cfreqs i (/ (vector-ref cfreqs i) cN)))
    (argmin car
      (for/list ([d chars#])
        (cons (for/sum ([i chars#])
                (expt (- (vector-ref freqs i)
                         (vector-ref cfreqs (modulo (+ i d) chars#)))
                      2))
              d)))))

(define best-key
  (cdr (argmin car
         (for/list ([len (range 1 (add1 max-keylen))])
           (define guesses (col-guesses len))
           (cons (/ (apply + (map car guesses)) len) (map cdr guesses))))))

(printf "Best key found: ")
(for ([c best-key]) (display (integer->char (+ c first-char))))
(newline)

(printf "Decoded text:\n")
(define decode-num
  (let ([cur '()])
    (λ(n) (when (null? cur) (set! cur best-key))
          (begin0 (modulo (- n (car cur)) chars#) (set! cur (cdr cur))))))
(for ([c text])
  (define n (- (char->integer c) first-char))
  (if (not (< -1 n chars#)) (display c)
      (display (integer->char (+ first-char (decode-num n))))))
(newline)


# from Wikipedia
constant %English-letter-freq = (
     E => 12.70,  L => 4.03,  Y => 1.97,  P => 1.93,  T => 9.06,  A => 8.17,  O => 7.51,  I => 6.97,  N => 6.75,
     S =>  6.33,  H => 6.09,  R => 5.99,  D => 4.25,  C => 2.78,  U => 2.76,  M => 2.41,  W => 2.36,  F => 2.23,
     G =>  2.02,  B => 1.29,  V => 0.98,  K => 0.77,  J => 0.15,  X => 0.15,  Q => 0.10,  Z => 0.07
);
constant @alphabet = %English-letter-freq.keys.sort;
constant max_key_lengths = 5; # number of keylengths to try

sub myguess ($text) {
    my ($seqtext, @spacing, @factors, $pos, %freq, %Keys);

    # Kasiski examination
    $seqtext = $text;
    while ($seqtext ~~ /$<sequence>=[...].*$<sequence>/) {
        $seqtext = substr($seqtext, 1+index($seqtext, $<sequence>));
        push @spacing, 1 + index($seqtext, $<sequence>);
    }
    for @spacing -> $j {
        %freq{$_}++ for grep { $j %% $_ }, 2..$j;
    }

    # discard very short keys, and test only the most likely remaining key lengths
    (%freq.keys.grep(* > 3).sort({%freq{$_}}).tail(max_key_lengths)).race(:1batch).map: -> $keylen {
        my $key-guess = '';
        loop (my $i = 0; $i < $keylen; $i++) {
            my ($mykey, %chi-square, $best-guess);
            loop (my $j = 0; $j < $text.chars; $j += $keylen) {
                $mykey ~= substr($text, ($j+$i) % $text.chars, 1);
            }

            for @alphabet -> $subkey {
                my $decrypted = mycrypt($mykey, $subkey);
                my $length    = $decrypted.chars;
                for @alphabet -> $char {
                    my $expected = %English-letter-freq{$char} * $length / 100;
                    my $observed = $decrypted.comb.grep(* eq $char).elems;
                    %chi-square{$subkey} += ($observed - $expected)² / $expected if $observed;
                }
            }
            %Keys{$keylen}{'score'} = %chi-square{@alphabet[0]};
            for %chi-square.keys.sort -> $sk {
                if (%chi-square{$sk} <= %Keys{$keylen}{'score'}) {
                    $best-guess = $sk;
                    %Keys{$keylen}{'score'} = %chi-square{$sk};
                }
            }
            $key-guess ~= $best-guess;
        }
        %Keys{$keylen}{'key'} = $key-guess;
    }
    %Keys.keys.sort({ %Keys{$_}{'score'} }).map:{ %Keys{$_}{'key'} };
}

sub mycrypt ($text, $key) {
    constant %values-numbers = @alphabet Z=> ^@alphabet;
    constant %values-letters = %values-numbers.invert;

    my ($new-text);
    my $keylen = $key.chars;
    loop (my $i = 0; $i < $text.chars; $i++) {
        my $val =  -1 * %values-numbers{substr( $key, $i%$keylen, 1)} # negative shift for decode
                 +      %values-numbers{substr($text, $i,         1)};
        $new-text ~= %values-letters{ $val % @alphabet };
    }
    return $new-text;
}

my $cipher-text = .uc.trans(@alphabet => '', :c) given q:to/EOD/;
    MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH
    VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD
    ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS
    FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG
    ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ
    ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS
    JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT
    LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST
    MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH
    QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV
    RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW
    TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO
    SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR
    ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX
    BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB
    BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA
    FWAML ZZRXJ EKAHV FASMU LVVUT TGK
EOD

for myguess($cipher-text) -> $key {
    say "Key        $key\n" ~
        "Key length {$key.chars}\n" ~
        "Plaintext  {substr(mycrypt($cipher-text, $key), 0, 80)}...\n";
}


use std::iter::FromIterator;

const CRYPTOGRAM: &str = "MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH
VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD
ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS
FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG
ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ
ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS
JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT
LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST
MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH
QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV
RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW
TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO
SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR
ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX
BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB
BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA
FWAML ZZRXJ EKAHV FASMU LVVUT TGK";

const FREQUENCIES: [f32; 26] = [
    0.08167, 0.01492, 0.02202, 0.04253, 0.12702, 0.02228, 0.02015, 0.06094, 0.06966, 0.00153,
    0.01292, 0.04025, 0.02406, 0.06749, 0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09356,
    0.02758, 0.00978, 0.02560, 0.00150, 0.01994, 0.00077,
];

fn best_match(a: &[f32]) -> u8 {
    let sum: f32 = a.iter().sum();
    let mut best_fit = std::f32::MAX;
    let mut best_rotate = 0;
    for rotate in 0..=25 {
        let mut fit = 0.;
        for i in 0..=25 {
            let char_freq = FREQUENCIES[i];
            let idx = (i + rotate as usize) % 26 as usize;
            let d = a[idx] / sum - char_freq;
            fit += d * d / char_freq;
        }
        if fit < best_fit {
            best_fit = fit;
            best_rotate = rotate;
        }
    }

    best_rotate
}

fn freq_every_nth(msg: &[u8], key: &mut [char]) -> f32 {
    let len = msg.len();
    let interval = key.len();
    let mut accu = [0.; 26];
    for j in 0..interval {
        let mut out = [0.; 26];
        for i in (j..len).step_by(interval) {
            let idx = msg[i] as usize;
            out[idx] += 1.;
        }
        let rot = best_match(&out);
        key[j] = char::from(rot + b'A');
        for i in 0..=25 {
            let idx: usize = (i + rot as usize) % 26;
            accu[i] += out[idx];
        }
    }
    let sum: f32 = accu.iter().sum();
    let mut ret = 0.;
    for i in 0..=25 {
        let char_freq = FREQUENCIES[i];
        let d = accu[i] / sum - char_freq;
        ret += d * d / char_freq;
    }
    ret
}

fn decrypt(text: &str, key: &str) -> String {
    let key_chars_cycle = key.as_bytes().iter().map(|b| *b as i32).cycle();
    let is_ascii_uppercase = |c: &u8| (b'A'..=b'Z').contains(c);
    text.as_bytes()
        .iter()
        .filter(|c| is_ascii_uppercase(c))
        .map(|b| *b as i32)
        .zip(key_chars_cycle)
        .fold(String::new(), |mut acc, (c, key_char)| {
            let ci: u8 = ((c - key_char + 26) % 26) as u8;
            acc.push(char::from(b'A' + ci));
            acc
        })
}
fn main() {
    let enc = CRYPTOGRAM
        .split_ascii_whitespace()
        .collect::<Vec<_>>()
        .join("");
    let cryptogram: Vec<u8> = enc.as_bytes().iter().map(|b| u8::from(b - b'A')).collect();
    let mut best_fit = std::f32::MAX;
    let mut best_key = String::new();
    for j in 1..=26 {
        let mut key = vec!['\0'; j];
        let fit = freq_every_nth(&cryptogram, &mut key);
        let s_key = String::from_iter(key); // 'from_iter' is imported from std::iter::FromIterator;
        if fit < best_fit {
            best_fit = fit;
            best_key = s_key;
        }
    }

    println!("best key: {}", &best_key);
    println!("\nDecrypted text:\n{}", decrypt(&enc, &best_key));
}


package require Tcl 8.6

oo::class create VigenereAnalyzer {
    variable letterFrequencies sortedTargets
    constructor {{frequencies {
 	0.08167 0.01492 0.02782 0.04253 0.12702 0.02228 0.02015
	0.06094 0.06966 0.00153 0.00772 0.04025 0.02406 0.06749
	0.07507 0.01929 0.00095 0.05987 0.06327 0.09056 0.02758
	0.00978 0.02360 0.00150 0.01974 0.00074
    }}} {
	set letterFrequencies $frequencies
	set sortedTargets [lsort -real $frequencies]
	if {[llength $frequencies] != 26} {
	    error "wrong length of frequency table"
	}
    }

    ### Utility methods
    # Find the value of $idxvar in the range [$from..$to) that maximizes the value
    # in $scorevar (which is computed by evaluating $body)
    method Best {idxvar from to scorevar body} {
	upvar 1 $idxvar i $scorevar s
	set bestI $from
	for {set i $from} {$i < $to} {incr i} {
	    uplevel 1 $body
	    if {![info exist bestS] || $bestS < $s} {
		set bestI $i
		set bestS $s
	    }
	}
	return $bestI
    }
    # Simple list map
    method Map {var list body} {
	upvar 1 $var v
	set result {}
	foreach v $list {lappend result [uplevel 1 $body]}
	return $result
    }
    # Simple partition of $list into $groups groups; thus, the partition of
    # {a b c d e f} into 3 produces {a d} {b e} {c f}
    method Partition {list groups} {
	set i 0
	foreach val $list {
	    dict lappend result $i $val
	    if {[incr i] >= $groups} {
		set i 0
	    }
	}
	return [dict values $result]
    }

    ### Helper methods
    # Get the actual counts of different types of characters in the given list
    method Frequency cleaned {
	for {set i 0} {$i < 26} {incr i} {
	    dict set tbl $i 0
	}
	foreach ch $cleaned {
	    dict incr tbl [expr {[scan $ch %c] - 65}]
	}
	return $tbl
    }

    # Get the correlation factor of the characters in a given list with the
    # class-specified language frequency corpus
    method Correlation cleaned {
	set result 0.0
	set freq [lsort -integer [dict values [my Frequency $cleaned]]]
	foreach f $freq s $sortedTargets {
	    set result [expr {$result + $f * $s}]
	}
	return $result
    }

    # Compute an estimate for the key length
    method GetKeyLength {cleaned {required 20}} {
	# Assume that we need at least 20 characters per column to guess
	set bestLength [my Best i 2 [expr {[llength $cleaned] / $required}] corr {
	    set corr [expr {-0.5 * $i}]
	    foreach chars [my Partition $cleaned $i] {
		set corr [expr {$corr + [my Correlation $chars]}]
	    }
	}]
	if {$bestLength == 0} {
	    error "text is too short to analyze"
	}
	return $bestLength
    }

    # Compute the key from the given frequency tables and the class-specified
    # language frequency corpus
    method GetKeyFromFreqs freqs {
	foreach f $freqs {
	    set m [my Best i 0 26 corr {
		set corr 0.0
		foreach {ch count} $f {
		    set d [expr {($ch - $i) % 26}]
		    set corr [expr {$corr + $count*[lindex $letterFrequencies $d]}]
		}
	    }]
	    append key [format %c [expr {65 + $m}]]
	}
	return $key
    }

    ##### The main analyzer method #####
    method analyze input {
	# Turn the input into a clean letter sequence
	set cleaned [regexp -all -inline {[A-Z]} [string toupper $input]]
	# Get the (estimated) key length
	set bestLength [my GetKeyLength $cleaned]
	# Get the frequency mapping for the partitioned input text
	set freqs [my Map p [my Partition $cleaned $bestLength] {my Frequency $p}]
	# Get the key itself
	return [my GetKeyFromFreqs $freqs]
    }
}


// (1) Copy text into tmp buffer and remove non-alpha chars.

Chdir(PATH_ONLY)
BOF
Reg_Copy(10, ALL)                       // copy text to new buffer
Buf_Switch(Buf_Free)
Reg_Ins(10)
BOF
Replace ("|!|A", "", BEGIN+ALL+NOERR)   // remove non-alpha chars
Reg_Copy_Block(10,0,EOB_pos)            // @10 = text to be analysed

#20 = Buf_Num                           // buffer for text being analyzed
#21 = Buf_Free                          // buffer for English frequency list (A-Z)
Buf_Switch(#21)
Ins_Text("8167 1492 2782 4253 12702 2228 2015 6094 6966 153 772 4025 2406 6749 7507 1929 95 5987 6327 9056 2758 978 2360 150 1974 74")
File_Open("unixdict.txt")               // or use "|(MACRO_DIR)\scribe\english.vdf"
#23 = Buf_Num                           // buffer for dictionary
#24 = Buf_Free                          // buffer for key canditates

Buf_Switch(#24)
for (#1=0; #1<5; #1++) {                // Fill table for 5 keys of 50 chars
    Ins_Char('.', COUNT, 50)
    Ins_Newline
}
#22 = Buf_Free                          // buffer for results

#25 = Reg_Size(10)                      // number of letters in the text
#26 = 26                                // number of characters in the alphabet
#61 = min(#25/10, 50)                   // max key length to try

// (2) Check Index of coincidence (or Kp) for each key length

Buf_Switch(#22)                         // buffer for results
Ins_Text("KeyLen Kp   dist ") Ins_Newline
Ins_Text("-----------------") Ins_Newline
#13 = Cur_Pos
#7 = 0                                  // no Caesar encryption
for (#5=1; #5<=#61; #5++) {
    Buf_Switch(#20)                     // text being analyzed
    BOF
    #54 = 0;                            // sum of Kp's
    for (#6=0; #6<#5; #6++) {           // for each slide
        Goto_Pos(#6)
        Call("CHARACTER_FREQUENCIES")
        Call("INDEX_OF_COINCIDENCE")    // #51 = Kp * 10000
        #54 += #51
    }
    #54 /= #5                           // average of Kp's
    Buf_Switch(#22)
    Num_Ins(#5, COUNT, 3)               // write key length
    IT(": ")
    Num_Ins(#54, NOCR)                  // average Kp
    Num_Ins(670-#54)                    // distance to English Kp
}
Buf_Switch(#22)
Sort_Merge("5,12", #13, Cur_Pos, REVERSE)  // sort the results by Kp value
Ins_Newline

// (3) Check the best 4 key lengths to find which one gives the best decrypt result

#38 = 0                                 // max number of correct characters found
#19 = 1                                 // best key length
for (#14 = 0; #14<4; #14++) {           // try 4 best key lengths
    Buf_Switch(#22)                     // results buffer
    Goto_Pos(#13) Line(#14)
    #5 = Num_Eval(SUPPRESS)             // #5 = key length
    Call("FIND_KEYS")                   // find Caesar key for each key character
    #4 = -1                             // try best match key chars only
    Call("BUILD_KEY")
    EOF
    Ins_Text("Key length ")
    Num_Ins(#5, LEFT)
    Reg_Ins(10)                         // encrypted text
    BOL
    Call("DECRYPT_LINE")
    BOL
    Call("FIND_ENGLISH_WORDS")          // #37 = number of English chars
    EOL Ins_Newline
    Ins_Text("Correct chars: ")
    Num_Ins(#37)
    if (#37 > #38) {
        #38 = #37
        #19 = #5
    }
    Update()
}

Ins_Text("Using key length: ") Num_Ins(#19) Ins_Newline
#5 = #19
Call("FIND_KEYS")                       // find Caesar key for each key character

// (4) Decrypt with different key combinations and try to find English words.
//     Try key combinations where max one char is taken from 2nd best Caesar key.

#38 = 0                                 // max number of chars in English words found
#39 = -1                                // best key number found
for (#4 = -1; #4 < #19; #4++)
{
    Call("BUILD_KEY")
    Buf_Switch(#22)                     // results
    Reg_Ins(10)                         // encrypted text
    BOL
    Call("DECRYPT_LINE")
    BOL
    Update()
    Call("FIND_ENGLISH_WORDS")          // #37 := number of correct letters in text
    if (#37 > #38) {
        #38 = #37                       // new highest number of correct chars
        #39 = #4                        // new best key
    }

    EOL IT(" -- ")                      // display results
    Num_Ins(#4, COUNT, 3)               // key number
    Ins_Text(": ")
    for (#6=0; #6<#19; #6++) {          // display key
        #9 = 130 + #6
        Ins_Char(#@9)
    }
    Ins_Text("  correct chars =")
    Num_Ins(#37)
}
Ins_Text("Best key = ")
Num_Ins(#39, LEFT)
#4 = #39
Ins_Newline

// Display results
//
Buf_Switch(#24)                         // table for key canditates
BOF
Reg_Copy_Block(14, Cur_Pos, Cur_Pos+#19)  // best Caesar key chars
Line(1)
Reg_Copy_Block(15, Cur_Pos, Cur_Pos+#19)  // 2nd best Caesar key chars
Call("BUILD_KEY")
Buf_Switch(#22)
Ins_Text("Key 1: ") Reg_Ins(14) Ins_Newline
Ins_Text("Key 2: ") Reg_Ins(15) Ins_Newline
Ins_Text("Key:   ")
for (#6=0; #6 < #19; #6++) {
    #9 = #6+130
    Ins_Char(#@9)
}
Ins_Newline
Ins_Newline

// decrypt the text with selected key
Ins_Text("Decrypted text:") Ins_Newline
Reg_Ins(10)
BOL
Call("DECRYPT_LINE")
BOL Reg_Copy(13,1)
EOL Ins_Newline

// Find English words from the text
Reg_Ins(13)
Call("FIND_ENGLISH_WORDS")
EOL
Ins_Newline
Num_Ins(#37, NOCR) IT(" of ")
Num_Ins(#25, NOCR) IT(" characters are English words. ")
Ins_Newline

Buf_Switch(#20) Buf_Quit(OK)
Buf_Switch(#21) Buf_Quit(OK)
Buf_Switch(#23) Buf_Quit(OK)
Buf_Switch(#24) Buf_Quit(OK)

Statline_Message("Done!")
Return

/////////////////////////////////////////////////////////////////////////////
//
// Caesar decrypt current line and count character frequencies.
//   in: #5 = step size, #7 = encryption key, #26 = num of chars in alphabet
//  out: #65...#90 = frequencies, #60 = number of chars

:CHARACTER_FREQUENCIES:
    Save_Pos
    for (#8 = 'A'; #8<='Z'; #8++) {
        #@8 = 0                         // reset frequency counters
    }
    #60 = 0                             // total number of chars
    while (!At_EOL) {
        if (Cur_Char >= 'A' && Cur_Char <= 'Z') {
            #8 = (Cur_Char-'A'+#26-#7) % #26 + 'A'  // decrypted char
            #@8++
            #60++
        }
        Char(#5)
    }
    Restore_Pos
Return

// Calculate Index of Coincidence (Kp).
//   in: character frequencies in #65...#90, #60 = num of chars
//  out: #51 = IC * 10000
//
:INDEX_OF_COINCIDENCE:
    Num_Push(10,15)
    #10 = 0
    for (#11 = 'A'; #11<='Z'; #11++) {
        #10 += (#@11 * (#@11-1))        // Calculate sigma{ni * (ni-1)}
    }
    #12 = #60 * (#60-1)                 // #12 = N * (N-1)
    #51 = #10 * 10000 / #12             // #51 = Kp * 10000
    Num_Pop(10,15)
Return

// Find best and 2nd best Caesar key for each character position of Vigenère key.
//   in: #5=step size (key length)
//  out: keys in buffer #24
//
:FIND_KEYS:
    for (#6 = 0; #6 < #5; #6++) {               // for each char position in the key
        #30 = -1                                // best key char found so far
        #31 = -1                                // 2nd best key char
        #32 = MAXNUM                            // smallest error found so far
        #33 = MAXNUM                            // 2nd smallest error found so far
        for (#7 = 0; #7 < #26; #7++) {          // for each possible key value
            #35 = 0                             // total frequency error compared to English
            Buf_Switch(#20)                     // text being analyzed
            Goto_Pos(#6)
            Call("CHARACTER_FREQUENCIES")
            Buf_Switch(#21)                     // English frequency table
            BOF
            for (#8 = 'A'; #8<='Z'; #8++) {     // calculate total frequency error
                #34 = Num_Eval(SUPPRESS+ADVANCE)
                #35 += abs((#@8*100000+50000)/#60-#34)
            }

            if (#35 < #32) {                    // found better match?
                #33 = #32
                #32 = #35
                #31 = #30
                #30 = #7
            } else {
                if (#35 < #33) {                // 2nd best match?
                    #33 = #35
                    #31 = #7
                }
            }
        }
        Buf_Switch(#24)                         // table for key canditates
        BOF
        Goto_Col(#6+1)
        Ins_Char(#30+'A', OVERWRITE)            // save the best match
        Line(1)
        Goto_Col(#6+1)
        Ins_Char(#31+'A', OVERWRITE)            // save 2nd best match
    }
    Buf_Switch(#22)                             // results buffer
Return

// Combine actual key from 1st and 2nd best Caesar key characters
// Use 1st key chars and (possibly) one character from 2nd key.
//  #4 = index of the char to be picked from 2nd key, -1 = none.
//  #5 = key length
//
:BUILD_KEY:
    Buf_Switch(#24)                     // table for key canditates
    BOF
    for (#6=0; #6<#5; #6++) {           // copy 1st key
        #8 = 130 + #6
        #@8 = Cur_Char
        Char(1)
    }
    if (#4 >= 0) {
        #8 = 130 + #4                   // pick one char from 2st key
        Line(1)
        Goto_Col(#4+1)
        #@8 = Cur_Char
    }
    Buf_Switch(#22)                     // results buffer
Return

// Decrypt text on current line
//   in: #5 = key length, #130...#189 = key
//
:DECRYPT_LINE:
    Num_Push(6,9)
    #6 = 0
    While (!At_EOL) {
       #9 = #6+130
       #7 = #@9
       #8 = (Cur_Char - #7 + #26) % #26 + 'A'   // decrypted char
       Ins_Char(#8, OVERWRITE)
       #6++
       if (#6 >= #5) {
           #6 = 0
       }
    }
    Num_Pop(6,9)
Return

// Find English words from text on current line
//  out: #37 = number of chars matched
//
:FIND_ENGLISH_WORDS:
    Buf_Switch(#23)                     // dictionary
    BOF
    While (!At_EOF) {
        Reg_Copy_Block(12, Cur_Pos, EOL_Pos)
        if (Reg_Size(12) > 2) {
            Buf_Switch(#22)             // buffer for results
            BOL
            while (Search_Block(@12, Cur_Pos, EOL_Pos, NOERR)) {
                Reg_Ins(12, OVERWRITE)
            }
            Buf_Switch(#23)
        }
        Line(1, ERRBREAK)
    }

    Buf_Switch(#22)
    BOL
    #37 = Search_Block("|V", Cur_Pos, EOL_Pos, ALL+NOERR)
Return


import strings
const encoded =
    "MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH" +
    "VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD" +
    "ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS" +
    "FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG" +
    "ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ" +
    "ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS" +
    "JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT" +
    "LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST" +
    "MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH" +
    "QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV" +
    "RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW" +
    "TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO" +
    "SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR" +
    "ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX" +
    "BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB" +
    "BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA" +
    "FWAML ZZRXJ EKAHV FASMU LVVUT TGK"

const freq = [
    0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015,
    0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749,
    0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758,
    0.00978, 0.02360, 0.00150, 0.01974, 0.00074,
]

fn sum(a []f64) f64 {
    mut s := 0.0
    for f in a {
        s += f
    }
    return s
}

fn best_match(a []f64) int {
    s := sum(a)
    mut best_fit, mut best_rotate := 1e100, 0
    for rotate in 0..26 {
        mut fit := 0.0
        for i in 0..26 {
            d := a[(i+rotate)%26]/s - freq[i]
            fit += d * d / freq[i]
        }
        if fit < best_fit {
            best_fit, best_rotate = fit, rotate
        }
    }
    return best_rotate
}

fn freq_every_nth(msg []int, mut key []u8) f64 {
    l := msg.len
    interval := key.len
    mut out := []f64{len: 26}
    mut accu := []f64{len: 26}
    for j in 0..interval {
        for z in 0..26 {
            out[z] = 0.0
        }
        for i := j; i < l; i += interval {
            out[msg[i]]++
        }
        rot := best_match(out)
        key[j] = u8(rot + 65)
        for i := 0; i < 26; i++ {
            accu[i] += out[(i+rot)%26]
        }
    }
    s := sum(accu)
    mut ret := 0.0
    for i := 0; i < 26; i++ {
        d := accu[i]/s - freq[i]
        ret += d * d / freq[i]
    }
    return ret
}

fn decrypt(text string, key string) string {
    mut sb := strings.new_builder(128)
    mut ki := 0
    for c in text {
        if c < 'A'[0] || c > 'Z'[0] {
            continue
        }
        ci := (c - key[ki] + 26) % 26
        sb.write_rune(ci + 65)
        ki = (ki + 1) % key.len
    }
    return sb.str()
}

fn main() {
    enc := encoded.replace(" ", "")
    mut txt := []int{len: enc.len}
    for i in 0..txt.len {
        txt[i] = int(enc[i] - 'A'[0])
    }
    mut best_fit, mut best_key := 1e100, ""
    println("  Fit     Length   Key")
    for j := 1; j <= 26; j++ {
        mut key := []u8{len: j}
        fit := freq_every_nth(txt, mut key)
        s_key := key.bytestr()
        print("${fit:.6}    ${j:2}     $s_key")
        if fit < best_fit {
            best_fit, best_key = fit, s_key
            print(" <--- best so far")
        }
        println('')
    }
    println("\nBest key : $best_key")
    println("\nDecrypted text:\n${decrypt(enc, best_key)}")
}


import "/math" for Nums
import "/trait" for Stepped
import "/str" for Char, Str
import "/fmt" for Fmt

var encoded =
    "MOMUD EKAPV TQEFM OEVHP AJMII CDCTI FGYAG JSPXY ALUYM NSMYH" +
    "VUXJE LEPXJ FXGCM JHKDZ RYICU HYPUS PGIGM OIYHF WHTCQ KMLRD" +
    "ITLXZ LJFVQ GHOLW CUHLO MDSOE KTALU VYLNZ RFGBX PHVGA LWQIS" +
    "FGRPH JOOFW GUBYI LAPLA LCAFA AMKLG CETDW VOELJ IKGJB XPHVG" +
    "ALWQC SNWBU BYHCU HKOCE XJEYK BQKVY KIIEH GRLGH XEOLW AWFOJ" +
    "ILOVV RHPKD WIHKN ATUHN VRYAQ DIVHX FHRZV QWMWV LGSHN NLVZS" +
    "JLAKI FHXUF XJLXM TBLQV RXXHR FZXGV LRAJI EXPRV OSMNP KEPDT" +
    "LPRWM JAZPK LQUZA ALGZX GVLKL GJTUI ITDSU REZXJ ERXZS HMPST" +
    "MTEOE PAPJH SMFNB YVQUZ AALGA YDNMP AQOWT UHDBV TSMUE UIMVH" +
    "QGVRW AEFSP EMPVE PKXZY WLKJA GWALT VYYOB YIXOK IHPDS EVLEV" +
    "RVSGB JOGYW FHKBL GLXYA MVKIS KIEHY IMAPX UOISK PVAGN MZHPW" +
    "TTZPV XFCCD TUHJH WLAPF YULTB UXJLN SIJVV YOVDJ SOLXG TGRVO" +
    "SFRII CTMKO JFCQF KTINQ BWVHG TENLH HOGCS PSFPV GJOKM SIFPR" +
    "ZPAAS ATPTZ FTPPD PORRF TAXZP KALQA WMIUD BWNCT LEFKO ZQDLX" +
    "BUXJL ASIMR PNMBF ZCYLV WAPVF QRHZV ZGZEF KBYIO OFXYE VOWGB" +
    "BXVCB XBAWG LQKCM ICRRX MACUO IKHQU AJEGL OIJHH XPVZW JEWBA" +
    "FWAML ZZRXJ EKAHV FASMU LVVUT TGK"

var freq = [
    0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015,
    0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749,
    0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758,
    0.00978, 0.02360, 0.00150, 0.01974, 0.00074
]

var bestMatch = Fn.new { |a|
    var sum = Nums.sum(a)
    var bestFit = 1e100
    var bestRotate = 0
    for (rotate in 0..25) {
        var fit = 0
        for (i in 0..25) {
            var d = a[(i + rotate) % 26] / sum - freq[i]
                fit = fit + d * d / freq[i]
        }
        if (fit < bestFit) {
            bestFit = fit
            bestRotate = rotate
        }
    }
    return bestRotate
}

var freqEveryNth = Fn.new { |msg, key|
    var len = msg.count
    var interval = key.count
    var out = List.filled(26, 0)
    var accu = List.filled(26, 0)
    for (j in 0...interval) {
        for (i in 0..25) out[i] = 0
        for (i in Stepped.new(j...len, interval)) out[msg[i]] = out[msg[i]] + 1
        var rot = bestMatch.call(out)
        key[j] = Char.fromCode(rot + 65)
        for (i in 0..25) accu[i] = accu[i] + out[(i + rot) % 26]
    }
    var sum = Nums.sum(accu)
    var ret = 0
    for (i in 0..25) {
        var d = accu[i] / sum - freq[i]
        ret = ret + d * d / freq[i]
    }
    return ret
}

var decrypt = Fn.new { |text, key|
    var sb = ""
    var ki = 0
    for (c in text) {
        if (Char.isAsciiUpper(c)) {
            var ci = (c.bytes[0] - key[ki].bytes[0] +  26) % 26
            sb = sb + Char.fromCode(ci + 65)
            ki = (ki + 1) % key.count
        }
    }
    return sb
}

var enc = encoded.replace(" ", "")
var txt = List.filled(enc.count, 0)
for (i in 0...txt.count) txt[i] = Char.code(enc[i]) - 65
var bestFit = 1e100
var bestKey = ""
var f = "$f    $2d     $s"
System.print("  Fit     Length   Key")
for (j in 1..26) {
    var key = List.filled(j, "")
    var fit = freqEveryNth.call(txt, key)
    var sKey = key.join("")
    Fmt.write(f, fit, j, sKey)
    if (fit < bestFit) {
       bestFit = fit
       bestKey = sKey
       System.write(" <--- best so far")
    }
    System.print()
}
System.print()
System.print("Best key : %(bestKey)")
System.print("\nDecrypted text:\n%(decrypt.call(enc, bestKey))")


var[const] uppercase=["A".."Z"].pump(String),
   english_frequences=T( // A..Z
        0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015,
        0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749,
        0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758,
        0.00978, 0.02360, 0.00150, 0.01974, 0.00074);

fcn vigenere_decrypt(target_freqs, input){ // ( (float,...), string)
   nchars,ordA   :=uppercase.len(),"A".toAsc();
   sorted_targets:=target_freqs.sort();

   frequency:='wrap(input){  // (n,n,n,n,...), n is ASCII index ("A"==65)
      result:=uppercase.pump(List(),List.fp1(0)); // ( ("A",0),("B",0) ...)
      foreach c in (input){ result[c - ordA][1] += 1 }
      result   // --> mutable list of mutable lists ( ("A",Int)...("Z",Int) )
   };
   correlation:='wrap(input){  // (n,n,n,n,...), n is ASCII index ("A"==65)
      result,freq:=0.0, frequency(input);
      freq.sort(fcn([(_,a)],[(_,b)]){ a<b });  // sort letters by frequency
      foreach i,f in (freq.enumerate()){ result+=sorted_targets[i]*f[1] }
      result	// -->Float
   };

   cleaned:=input.toUpper().pump(List,uppercase.holds,Void.Filter,"toAsc");

   best_len,best_corr := 0,-100.0;
    # Assume that if there are less than 20 characters
    # per column, the key's too long to guess
   foreach i in ([2..cleaned.len()/20]){
      pieces:=(i).pump(List,List.copy);		// ( (),() ... )
      foreach c in (cleaned){ pieces[__cWalker.idx%i].append(c) }

        # The correlation seems to increase for smaller
        # pieces/longer keys, so weigh against them a little
      corr:=-0.5*i + pieces.apply(correlation).sum(0.0);
      if(corr>best_corr) best_len,best_corr=i,corr;
   }
   if(best_len==0) return("Text is too short to analyze", "");

   pieces:=best_len.pump(List,List.copy);
   foreach c in (cleaned){ pieces[__cWalker.idx%best_len].append(c) }

   key,freqs := "",pieces.apply(frequency);
   foreach fr in (freqs){
      fr.sort(fcn([(_,a)],[(_,b)]){ a>b });  // reverse sort by freq
      m,max_corr := 0,0.0;
      foreach j in (nchars){
         corr,c := 0.0,ordA + j;
	 foreach frc in (fr){
	    d:=(frc[0].toAsc() - c + nchars) % nchars;
 	    corr+=target_freqs[d]*frc[1];
	    if(corr>max_corr) m,max_corr=j,corr;
	 }
      }
      key+=(m + ordA).toChar();
   }

   cleaned.enumerate().apply('wrap([(i,c])){
      ( (c - (key[i%best_len]).toAsc() + nchars)%nchars + ordA ).toChar()
   }).concat() :
   T(key,_);
}


PROC ShowMode(BYTE m,split,gr
              CARD w, BYTE h,
              CARD size,
              CHAR ARRAY descr)
  BYTE CH=$02FC
  CARD i
  BYTE POINTER ptr

  Graphics(0)
  PrintF("Next video mode: %B%E",m)
  IF split THEN
    PrintF("Split video mode%E%EUpper part:%E")
  FI

  IF gr THEN
    Print("Graphics")
  ELSE
    Print("Text")
  FI
  PrintF(" mode, %Ux%B, %S%E",w,h,descr)

  IF split THEN
    PrintF("%ELower part:%EText mode 40x4, 2 luminances%E")
  FI
  PrintF("%EPress any key to change video mode.")

  DO UNTIL CH#$FF OD
  CH=$FF

  Graphics(m)
  ptr=PeekC(88)

  FOR i=1 TO size
  DO
    ptr^=Rand(0)
    ptr==+1
  OD

  DO UNTIL CH#$FF OD
  CH=$FF
RETURN

PROC Main()
  ShowMode(0,0,0,40,24,960,"2 luminances")
  ShowMode(1,1,0,20,20,640,"5 colors")
  ShowMode(2,1,0,20,10,400,"5 colors")
  ShowMode(3,1,1,40,20,400,"4 colors")
  ShowMode(4,1,1,80,40,640,"2 colors")
  ShowMode(5,1,1,80,40,1120,"4 colors")
  ShowMode(6,1,1,160,80,2080,"2 colors")
  ShowMode(7,1,1,160,80,4000,"4 colors")
  ShowMode(8,1,1,320,160,7856,"2 luminances")
  ShowMode(9,0,1,80,192,7680,"16 luminances")
  ShowMode(10,0,1,80,192,7680,"9 colors")
  ShowMode(11,0,1,80,192,7680,"16 hues")
  ShowMode(12,1,0,40,20,1120,"5 colors")
  ShowMode(13,1,0,40,10,640,"5 colors")
  ShowMode(14,1,1,160,160,4000,"2 colors")
  ShowMode(15,1,1,160,160,7856,"4 colors")
  ShowMode(17,0,0,20,24,480,"5 colors")
  ShowMode(18,0,0,20,12,240,"5 colors")
  ShowMode(19,0,1,40,24,240,"4 colors")
  ShowMode(20,0,1,80,48,480,"2 colors")
  ShowMode(21,0,1,80,48,960,"4 colors")
  ShowMode(22,0,1,160,96,1920,"2 colors")
  ShowMode(23,0,1,160,96,3840,"4 colors")
  ShowMode(24,0,1,320,192,7680,"2 luminances")
  ShowMode(28,0,0,40,24,960,"5 colors")
  ShowMode(29,0,0,40,12,480,"5 colors")
  ShowMode(30,0,1,160,192,3840,"2 colors")
  ShowMode(31,0,1,160,192,7680,"4 colors")
RETURN


TEXT, page 1, 40 x 24
GR, page 1, 40 x 40, 16 colors, mixed with four lines of text
HGR, page 1, 280 x 160, 6 colors, mixed with four lines of text
HGR2, page 2, 280 x 192, 6 colors, full screen
text, page 2, 40 x 24
gr, page 1, 40 x 48, 16 colors, full screen
gr, page 2, 40 x 40, 16 colors, mixed with four lines of text
gr, page 2, 40 x 48, 16 colors, full screen
hgr, page 1, 280 x 192, 6 colors, full screen
hgr, page 2, 280 x 160, 6 colors, mixed with four lines of text


MOV R1,#0x04000000
MOV R0,#0x403
STR r0,[r1]      ;the game boy advance is little-endian, so I would have expected this not to work. However it does indeed work.


10 MODE 1: REM 320x256 4 colour graphics


10 rem video modes - c64
15 rem rosetta code
20 print chr$(147);chr$(14):poke 53280,0:poke 53281,0:poke 646,1
25 poke 53282,2:poke 53283,11:poke 53284,9:rem set extended and multi colors
30 if peek(12288)=60 and peek(12289)=102 then goto 100
35 poke 52,32:poke 56,32:clr
40 print "Initializing - Please wait..."
45 poke 56334,peek(56334) and 254:poke1,peek(1) and 251
50 for i=0 to 4096:poke i+12288,peek(i+53248):next
55 poke1,peek(1) or 4:poke56334,peek(56334) or 1
60 for i=0 to 31:read d:poke 15368+i,d:next i
65 x=0:for i=8192 to 10239:poke i,2^x:x=(x+1) and 7:next
70 for i=10240 to 12287:poke i,228:next
100 data 60,66,165,129,165,153,66,60
105 data 60,66,165,129,153,165,66,60
110 data 245,245,245,245,10,10,10,10
115 data 10,10,10,10,245,245,245,245
480 print chr$(147);"Demonstration of Video Modes"
485 print
490 print "The video modes described at Rosetta "
495 print "Code will be demonstrated in order. "
500 print "Simply press a key to advance to the"
505 print "next video mode."
510 print
515 print "See rosettacode.org for description."
516 print
517 print "http://www.rosettacode.org/wiki/";
518 print "Video";chr$(164);"display";chr$(164);"modes#";
519 print "Commodore";chr$(164);"BASIC"
520 print
525 print "Press any key to begin."
530 gosub 9010
600 print chr$(147);"Standard Character Mode"
605 print " - ROM Characters"
610 print:gosub 1000:print:gosub 9000:print chr$(147)
615 gosub 1210
620 print chr$(147);"Multicolor Character Mode"
625 print " - ROM Characters"
630 print:gosub 1000:print:gosub 9000:print chr$(147)
635 gosub 1220
640 gosub 1310
645 print chr$(147);"Extended Color Character Mode"
650 print " - ROM Characters"
655 print:gosub 1000:print:gosub 9000:print chr$(147)
660 gosub 1320
665 gosub 1100
670 print chr$(147);"Standard Character Mode"
675 print " - Programmed Characters"
680 print:gosub 1000:print:gosub 9000:print chr$(147)
685 gosub 1210
690 print chr$(147);"Multicolor Character Mode"
695 print " - Programmed Characters"
700 print:gosub 1000:print:gosub 9000:print chr$(147)
705 gosub 1220
710 gosub 1310
715 print chr$(147);"Extended Color Character Mode"
720 print " - Programmed Characters"
725 print:gosub 1000:print:gosub 9000:print chr$(147)
730 gosub 1320
735 print chr$(147);"The next screen will be the"
740 print "High Resolution Bit Map Mode"
745 print
750 gosub 9000
755 gosub 1430:gosub 1410
760 print:gosub 1050:print:gosub 9010:print chr$(147)
765 gosub 1420:gosub 1120
770 print chr$(147);"The next screen will be the"
775 print "Multicolor High Resolution Bit Map Mode"
780 print
785 gosub 9000
790 gosub 1430:gosub 1410:gosub 1210
795 print:gosub 1050:print:gosub 9010:print chr$(147)
800 gosub 1420:gosub 1220:gosub 1120
805 print chr$(147);"End of demonstration."
810 end
1000 rem put some characters up for demo
1005 for i=0 to 15:poke 646,i
1010 print" a b c d ";
1011 print chr$(160);"A";chr$(160);"B";chr$(160);"C";chr$(160);"D";chr$(160);
1012 print chr$(18);" a b c d ";
1013 print chr$(160);"A";chr$(160);"B";chr$(160);"C";chr$(160);"D";chr$(160);
1014 print chr$(146)
1015 next i:poke 646,1
1020 return
1050 rem show color variety for hi-res modes
1051 print chr$(147)
1055 for i=0 to 255:poke 1024+i,i:poke 55296+i,1:next
1060 for i=0 to 255:poke 1280+i,i:poke 55552+i,int(rnd(1)*16):next
1065 return
1100 rem programmable character mode
1110 poke 53272,(peek(53272) and 240)+14:return:rem on
1120 poke 53272,(peek(53272) and 240)+6:return:rem off
1200 rem multicolor mode
1210 poke 53270,peek(53270) or 16:return:rem on
1220 poke 53270,peek(53270) and 239:return:rem off
1300 rem extended color mode
1310 poke 53265,peek(53265) or 64:return:rem on
1320 poke 53265,peek(53265) and 191:return:rem off
1400 rem hi res mode
1410 poke 53265,(peek(53265) or 32):return:rem on
1420 poke 53265,(peek(53265) and 223):return:rem off
1430 poke 53272,peek(53272) or 8:return:rem place bitmap at 8192
9000 print "Press any key for next screen.";
9010 get k$:if k$="" then 9010
9020 return


dim as integer i, w, h, d

for i = 0 to 21
    if i>2 and i<7 then continue for    'screens 3-6 are not defined
    screen i
    screeninfo w, h, d
    print "Screen ";i
    print using "#### x ####, color depth ##";w;h;d
    sleep
next i

'a more flexible alternative is ScreenRes

'this sets up a window of 1618x971 pixels, colour depth 8, and 2 pages
screenres 1618, 971, 8, 2
windowtitle "Foo bar baz"
sleep

'see https://documentation.help/FreeBASIC/KeyPgScreengraphics.html
'for more information


package main

import (
    "fmt"
    "log"
    "os/exec"
    "time"
)

func main() {
    // query supported display modes
    out, err := exec.Command("xrandr", "-q").Output()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(string(out))
    time.Sleep(3 * time.Second)

    // change display mode to 1024x768 say (no text output)
    err = exec.Command("xrandr", "-s", "1024x768").Run()
    if err != nil {
        log.Fatal(err)
    }
    time.Sleep(3 * time.Second)

    // change it back again to 1366x768 (or whatever is optimal for your system)
    err = exec.Command("xrandr", "-s", "1366x768").Run()
    if err != nil {
        log.Fatal(err)
    }
}


def invoke(String cmd) { println(cmd.execute().text) }

invoke("xrandr -q")
Thread.sleep(3000)

invoke("xrandr -s 1024x768")
Thread.sleep(3000)

invoke("xrandr -s 1366x768")


10 REM GW Basic can switch VGA modes
20 SCREEN 18: REM Mode 12h 640x480 16 colour graphics


procedure main(A)
    mode := A[1]
    if \mode then system("xrandr -s " || \mode || " >/dev/null")
    else system("xrandr -q")    # Display available modes
end


if Base.Sys.islinux()
    run(`xrandr -s 640x480`)
    sleep(3)
    run(`xrandr -s 1280x960`)
else # windows
    run(`mode CON: COLS=40 LINES=100`)
    sleep(3)
    run(`mode CON: COLS=100 LINES=50`)
end


// version 1.1.51

import java.util.Scanner

fun runSystemCommand(command: String) {
    val proc = Runtime.getRuntime().exec(command)
    Scanner(proc.inputStream).use {
        while (it.hasNextLine()) println(it.nextLine())
    }
    proc.waitFor()
    println()
}

fun main(args: Array<String>) {
    // query supported display modes
    runSystemCommand("xrandr -q")
    Thread.sleep(3000)

    // change display mode to 1024x768 say (no text output)
    runSystemCommand("xrandr -s 1024x768")
    Thread.sleep(3000)

    // change it back again to 1366x768 (or whatever is optimal for your system)
    runSystemCommand("xrandr -s 1366x768")
}


print("\33[?3h") -- 132-column text
print("\33[?3l") -- 80-column text


import os, osproc, strformat, strscans

# Retrieve video modes.
let p = startProcess("xrandr", "", ["-q"], nil, {poUsePath})
var currWidth, currHeight = 0   # Current video mode.
var width, height = 0           # Some other video mode.
for line in p.lines:
  echo line
  # Find current display mode, marked by an asterisk.
  var f: float
  if currWidth == 0:
    # Find current width and height.
    discard line.scanf(" $s$ix$i $s$f*", currWidth, currHeight, f)
  elif width == 0:
    # Find another width and height.
    discard line.scanf(" $s$ix$i $s$f", width, height, f)
p.close()

# Change video mode.
let newMode = &"{width}x{height}"
sleep 1000
echo "\nSwitching to ", newMode
sleep 2000
discard execProcess("xrandr", "", ["-s", newMode], nil, {poUsePath})

# Restore previous video mode.
let prevMode = &"{currWidth}x{currHeight}"
sleep 1000
echo "\nSwitching back to ", prevMode
sleep 2000
discard execProcess("xrandr", "", ["-s", prevMode], nil, {poUsePath})


;------------------------------------------------------------------------
Gui, 1: +E0x20 +Caption +E0x80000 +LastFound +AlwaysOnTop +ToolWindow +OwnDialogs
Gui, 1: Show, NA
hwnd1 := WinExist()
OnExit, Exit
If !pToken := Gdip_Startup()
{
	MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system
	ExitApp
}
Width :=1400, Height := 1050
hbm := CreateDIBSection(Width, Height)
hdc := CreateCompatibleDC()
obm := SelectObject(hdc, hbm)
G := Gdip_GraphicsFromHDC(hdc)
Gdip_SetSmoothingMode(G, 4)
;------------------------------------------------------------------------
w := 300, h := 200
xmin := A_ScreenWidth/2-w/2	, xmax := A_ScreenWidth/2+w/2
ymin := A_ScreenHeight/2-h/2 , ymax := A_ScreenHeight/2+h/2
colors := ["C0C0C0","808080","FFFFFF","800000","FF0000","800080","FF00FF","008000"
		,"00FF00","808000","FFFF00","000080","0000FF","008080","00FFFF"]
site := []
loop, 15
{
	Random, x, % xmin, % xmax
	Random, y, % ymin, % ymax
	site[A_Index, "x"] := x
	site[A_Index, "y"] := y
}
y:= ymin
while (y<=ymax)
{
	x:=xmin
	while (x<=xmax)
	{
		distance := []
		for S, coord in site
			distance[dist(x, y, coord.x, coord.y)] := S
		CS := Closest_Site(distance)
		pBrush := Gdip_BrushCreateSolid("0xFF" . colors[CS])
		Gdip_FillEllipse(G, pBrush, x, y, 2, 2)
		x++
	}
	UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)
	y++
}
pBrush 	:= Gdip_BrushCreateSolid(0xFF000000)
for S, coord in site
	Gdip_FillEllipse(G, pBrush, coord.x, coord.y, 4, 4)
UpdateLayeredWindow(hwnd1, hdc, 0, 0, Width, Height)
;------------------------------------------------------------------------
Gdip_DeleteBrush(pBrush)
SelectObject(hdc, obm)
DeleteObject(hbm)
DeleteDC(hdc)
Gdip_DeleteGraphics(G)
return
;------------------------------------------------------------------------
Dist(x1,y1,x2,y2){
	return Sqrt((x2-x1)**2 + (y2-y1)**2)
}
;------------------------------------------------------------------------
Closest_Site(distance){
	for d, i in distance
		if A_Index = 1
			min := d, site := i
		else
			min := min < d ? min : d
			, site := min < d ? site : i
	return site
}
;------------------------------------------------------------------------
Exit:
Gdip_Shutdown(pToken)
ExitApp
Return
;------------------------------------------------------------------------


$| = 1;

my @info = `xrandr -q`;
$info[0] =~ /current (\d+) x (\d+)/;
my $current = "$1x$2";

my @resolutions;
for (@info) {
    push @resolutions, $1 if /^\s+(\d+x\d+)/
}

system("xrandr -s $resolutions[-1]");
print "Current resolution $resolutions[-1].\n";
for (reverse 1 .. 9) {
    print "\rChanging back in $_ seconds...";
    sleep 1;
}
system("xrandr -s $current");
print "\rResolution returned to $current.\n";


without js -- (system, system_exec, sleep)
if platform()=LINUX then
    {} = system_exec("xrandr -s 640x480")
    sleep(3)
    {} = system_exec("xrandr -s 1280x960")
else -- WINDOWS
    puts(1,"") -- (ensure console exists)
    system("mode CON: COLS=40 LINES=25")
    sleep(3)
    system("mode CON: COLS=80 LINES=25")
end if


import win32api
import win32con
import pywintypes
devmode=pywintypes.DEVMODEType()
devmode.PelsWidth=640
devmode.PelsHeight=480
devmode.Fields=win32con.DM_PELSWIDTH | win32con.DM_PELSHEIGHT
win32api.ChangeDisplaySettings(devmode,0)


'QBasic can switch VGA modes
SCREEN 18 'Mode 12h 640x480 16 colour graphics


my @info = QX('xrandr -q').lines;

@info[0] ~~ /<?after 'current '>(\d+) ' x ' (\d+)/;
my $current = "$0x$1";

my @resolutions;
@resolutions.push: $0 if $_ ~~ /^\s+(\d+'x'\d+)/ for @info;

QX("xrandr -s @resolutions[*-1]");
say "Current resolution {@resolutions[*-1]}.";
for 9 ... 1 {
    print "\rChanging back in $_ seconds...";
    sleep 1;
}
QX("xrandr -s $current");
say "\rResolution returned to {$current}.     ";


global ancho, alto
ancho = 500 : alto = 500

clg
graphsize ancho, alto

function hypot(a, b)
	return sqr(a^2+b^2)
end function

subroutine Generar_diagrama_Voronoi(ancho, alto, num_celdas)
	dim nx(num_celdas+1)
	dim ny(num_celdas+1)
	dim nr(num_celdas+1)
	dim ng(num_celdas+1)
	dim nb(num_celdas+1)

	for i = 0 to num_celdas
		nx[i] = int(rand * ancho)
		ny[i] = int(rand * alto)
		nr[i] = int(rand * 256) + 1
		ng[i] = int(rand * 256) + 1
		nb[i] = int(rand * 256) + 1
	next i
	for y = 1 to alto
		for x = 1 to ancho
			dmin = hypot(ancho-1, alto-1)
			j = -1
			for i = 1 to num_celdas
				d = hypot(nx[i]-x, ny[i]-y)
				if d < dmin then dmin = d : j = i
			next i
			color rgb(nr[j], ng[j], nb[j])
			plot (x, y)
		next x
	next y
end subroutine

call Generar_diagrama_Voronoi(ancho, alto, 25)
refresh
imgsave "Voronoi_diagram.jpg", "jpg"
end


#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define N_SITES 150
double site[N_SITES][2];
unsigned char rgb[N_SITES][3];

int size_x = 640, size_y = 480;

inline double sq2(double x, double y)
{
	return x * x + y * y;
}

#define for_k for (k = 0; k < N_SITES; k++)
int nearest_site(double x, double y)
{
	int k, ret = 0;
	double d, dist = 0;
	for_k {
		d = sq2(x - site[k][0], y - site[k][1]);
		if (!k || d < dist) {
			dist = d, ret = k;
		}
	}
	return ret;
}

/* see if a pixel is different from any neighboring ones */
int at_edge(int *color, int y, int x)
{
	int i, j, c = color[y * size_x + x];
	for (i = y - 1; i <= y + 1; i++) {
		if (i < 0 || i >= size_y) continue;

		for (j = x - 1; j <= x + 1; j++) {
			if (j < 0 || j >= size_x) continue;
			if (color[i * size_x + j] != c) return 1;
		}
	}
	return 0;
}

#define AA_RES 4 /* average over 4x4 supersampling grid */
void aa_color(unsigned char *pix, int y, int x)
{
	int i, j, n;
	double r = 0, g = 0, b = 0, xx, yy;
	for (i = 0; i < AA_RES; i++) {
		yy = y + 1. / AA_RES * i + .5;
		for (j = 0; j < AA_RES; j++) {
			xx = x + 1. / AA_RES * j + .5;
			n = nearest_site(xx, yy);
			r += rgb[n][0];
			g += rgb[n][1];
			b += rgb[n][2];
		}
	}
	pix[0] = r / (AA_RES * AA_RES);
	pix[1] = g / (AA_RES * AA_RES);
	pix[2] = b / (AA_RES * AA_RES);
}

#define for_i for (i = 0; i < size_y; i++)
#define for_j for (j = 0; j < size_x; j++)
void gen_map()
{
	int i, j, k;
	int *nearest = malloc(sizeof(int) * size_y * size_x);
	unsigned char *ptr, *buf, color;

	ptr = buf = malloc(3 * size_x * size_y);
	for_i for_j nearest[i * size_x + j] = nearest_site(j, i);

	for_i for_j {
		if (!at_edge(nearest, i, j))
			memcpy(ptr, rgb[nearest[i * size_x + j]], 3);
		else	/* at edge, do anti-alias rastering */
			aa_color(ptr, i, j);
		ptr += 3;
	}

	/* draw sites */
	for (k = 0; k < N_SITES; k++) {
		color = (rgb[k][0]*.25 + rgb[k][1]*.6 + rgb[k][2]*.15 > 80) ? 0 : 255;

		for (i = site[k][1] - 1; i <= site[k][1] + 1; i++) {
			if (i < 0 || i >= size_y) continue;

			for (j = site[k][0] - 1; j <= site[k][0] + 1; j++) {
				if (j < 0 || j >= size_x) continue;

				ptr = buf + 3 * (i * size_x + j);
				ptr[0] = ptr[1] = ptr[2] = color;
			}
		}
	}

	printf("P6\n%d %d\n255\n", size_x, size_y);
	fflush(stdout);
	fwrite(buf, size_y * size_x * 3, 1, stdout);
}

#define frand(x) (rand() / (1. + RAND_MAX) * x)
int main()
{
	int k;
	for_k {
		site[k][0] = frand(size_x);
		site[k][1] = frand(size_y);
		rgb [k][0] = frand(256);
		rgb [k][1] = frand(256);
		rgb [k][2] = frand(256);
	}

	gen_map();
	return 0;
}


/*REXX program to switch video display modes based on columns and lines.*/

parse arg cols lines .
'MODE'   "CON:       COLS="cols     'LINES='lines
                                       /*stick a fork in it, we're done.*/


object VideoDisplayModes extends App {

  import java.util.Scanner

  def runSystemCommand(command: String) {
    val proc = Runtime.getRuntime.exec(command)

    val a: Unit = {
      val a = new Scanner(proc.getInputStream)
      while (a.hasNextLine) println(a.nextLine())
    }
    proc.waitFor()
    println()
  }

  // query supported display modes
  runSystemCommand("xrandr -q")
  Thread.sleep(3000)

  // change display mode to 1024x768 say (no text output)
  runSystemCommand("xrandr -s 1024x768")
  Thread.sleep(3000)

  // change it back again to 1366x768 (or whatever is optimal for your system)
  runSystemCommand("xrandr -s 1366x768")

}


GRAPHICS
FILL RECT 50,50 SIZE 50
GRAPHICS MODE CLEAR
FILL RECT 50,50 SIZE 25


/* video_display_modes.wren */

class C {
    foreign static xrandr(args)

    foreign static usleep(usec)
}

// query supported display modes
C.xrandr("-q")

C.usleep(3000)

// change display mode to 1368x768
System.print("\nChanging to 1368 x 768 mode.")
C.xrandr("-s 1368x768")

C.usleep(3000)

// change it back again to 1920x1080
System.print("\nReverting to 1920 x 1080 mode.")
C.xrandr("-s 1920x1080")