(* The Rosetta Code integer square root task, in OCaml, using Zarith
   for large integers.

   Compile with, for example:

      ocamlfind ocamlc -package zarith -linkpkg -o isqrt isqrt.ml

   Translated from the Scheme. *)

let find_a_power_of_4_greater_than_x x =
  let open Z in
  let rec loop q =
    if x < q then q else loop (q lsl 2)
  in
  loop one

let isqrt x =
  let open Z in
  let rec loop q z r =
    if q = one then
      r
    else
      let q = q asr 2 in
      let t = z - r - q in
      let r = r asr 1 in
      if t < zero then
        loop q z r
      else
        loop q t (r + q)
  in
  let q0 = find_a_power_of_4_greater_than_x x in
  let z0 = x in
  let r0 = zero in
  loop q0 z0 r0

let insert_separators s sep =
  let rec loop revchars i newchars =
    match revchars with
    | [] -> newchars
    | revchars when i = 3 -> loop revchars 0 (sep :: newchars)
    | c :: tail -> loop tail (i + 1) (c :: newchars)
  in
  let revchars = List.rev (List.of_seq (String.to_seq s)) in
  String.of_seq (List.to_seq (loop revchars 0 []))

let commas s = insert_separators s ','

let main () =
  Printf.printf "isqrt(i) for 0 <= i <= 65:\n\n";
  for i = 0 to 64 do
    Printf.printf "%s " Z.(to_string (isqrt (of_int i)))
  done;
  Printf.printf "%s\n" Z.(to_string (isqrt (of_int 65)));
  Printf.printf "\n\n";
  Printf.printf "isqrt(7**i) for 1 <= i <= 73, i odd:\n\n";
  Printf.printf "%2s %84s %43s\n" "i" "7**i" "isqrt(7**i)";
  for i = 1 to 131 do Printf.printf "-" done;
  Printf.printf "\n";
  for j = 0 to 36 do
    let i = j + j + 1 in
    let power = Z.(of_int 7 ** i) in
    let root = isqrt power in
    Printf.printf "%2d %84s %43s\n"
      i (commas (Z.to_string power)) (commas (Z.to_string root))
  done
;;

main ()


(print "Integer square roots of 0..65")
(for-each (lambda (x)
      (display (isqrt x))
      (display " "))
   (iota 66))
(print)

(print "Integer square roots of 7^n")
(for-each (lambda (x)
      (print "x: " x ", isqrt: " (isqrt x)))
   (map (lambda (i)
         (expt 7 i))
      (iota 73 1)))
(print)


//************************************************//
//                                                //
//  Thanks for rvelthuis for BigIntegers library  //
//  https://github.com/rvelthuis/DelphiBigNumbers //
//                                                //
//************************************************//

program IsqrtTask;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Velthuis.BigIntegers;

function isqrt(x: BigInteger): BigInteger;
var
  q, r, t: BigInteger;
begin
  q := 1;
  r := 0;
  while (q <= x) do
    q := q shl 2;

  while (q > 1) do
  begin
    q := q shr 2;
    t := x - r - q;
    r := r shr 1;
    if (t >= 0) then
    begin
      x := t;
      r := r + q;
    end;
  end;
  Result := r;
end;

function commatize(const n: BigInteger; size: Integer): string;
var
  str: string;
  digits: Integer;
  i: Integer;
begin
  Result := '';
  str := n.ToString;
  digits := str.Length;

  for i := 1 to digits do
  begin
    if ((i > 1) and (((i - 1) mod 3) = (digits mod 3))) then
      Result := Result + ',';
    Result := Result + str[i];
  end;

  if Result.Length < size then
    Result := string.Create(' ', size - Result.Length) + Result;
end;

const
  POWER_WIDTH = 83;
  ISQRT_WIDTH = 42;

var
  n, i: Integer;
  f: TextFile;
  p: BigInteger;

begin
  AssignFile(f, 'output.txt');
  rewrite(f);

  Writeln(f, 'Integer square root for numbers 0 to 65:');
  for n := 0 to 65 do
    Write(f, isqrt(n).ToString, ' ');

  Writeln(f, #10#10'Integer square roots of odd powers of 7 from 1 to 73:');

  Write(f, ' n |', string.Create(' ', 78), '7 ^ n |', string.Create(' ', 30),
    'isqrt(7 ^ n)'#10);

  Writeln(f, string.Create('-', 17 + POWER_WIDTH + ISQRT_WIDTH));

  p := 7;
  n := 1;
  repeat
    Writeln(f, Format('%2d', [n]), ' |', commatize(p, power_width), ' |',
      commatize(isqrt(p), isqrt_width));
    inc(n, 2);
    p := p * 49;
  until (n > 73);

  CloseFile(f);
end.


# 20201029 added Perl programming solution

use strict;
use warnings;
use bigint;

use CLDR::Number 'decimal_formatter';

sub integer_sqrt {
   ( my $x = $_[0] ) >= 0 or die;
   my $q = 1;
   while ($q <= $x) {
      $q <<= 2
   }
   my ($z, $r) = ($x, 0);
   while ($q > 1) {
      $q >>= 2;
      my $t = $z - $r - $q;
      $r >>= 1;
      if ($t >= 0) {
         $z  = $t;
         $r += $q;
      }
   }
   return $r
}

print "The integer square roots of integers from 0 to 65 are:\n";
print map { ( integer_sqrt $_ ) . ' ' } (0..65);

my $cldr = CLDR::Number->new();
my $decf = $cldr->decimal_formatter;

print "\nThe integer square roots of odd powers of 7 from 7^1 up to 7^73 are:\n";
print "power", " "x36, "7 ^ power", " "x60, "integer square root\n";
print "----- ", "-"x79, "  ------------------------------------------\n";

for (my $i = 1; $i < 74; $i += 2) {
   printf("%2s ", $i);
   printf("%82s", $decf->format( 7**$i ) );
   printf("%44s", $decf->format( integer_sqrt(7**$i) ) ) ;
   print "\n";
}


with javascript_semantics
include mpfr.e

function isqrt(mpz x)
    if mpz_cmp_si(x,0)<0 then
        crash("Argument cannot be negative.")
    end if
    mpz q = mpz_init(1),
        r = mpz_init(0),
        t = mpz_init(),
        z = mpz_init_set(x)
    while mpz_cmp(q,x)<= 0 do
        mpz_mul_si(q,q,4)
    end while
    while mpz_cmp_si(q,1)>0 do
        assert(mpz_fdiv_q_ui(q, q, 4)=0)
        mpz_sub(t,z,r)
        mpz_sub(t,t,q)
        assert(mpz_fdiv_q_ui(r, r, 2)=0)
        if mpz_cmp_si(t,0) >= 0 then
            mpz_set(z,t)
            mpz_add(r,r,q)
        end if
    end while
    string star = iff(mpz_cmp_si(z,0)=0?"*":" ")
    return shorten(mpz_get_str(r,10,true))&star
end function

printf(1,"The integer square roots of integers from 0 to 65 are:\n")
for i=0 to 65 do
    printf(1,"%s ", {trim(isqrt(mpz_init(i)))})
end for
printf(1,"\n\npower                          7 ^ power                                               integer square root\n")
printf(1,"-----  ---------------------------------------------------------   ----------------------------------------------------------\n")
mpz pow7 = mpz_init(7)
for i=1 to 9000 do
    if (i<=73  and remainder(i,2)=1)
    or (i<100  and remainder(i,10)=5)
    or (i<1000 and remainder(i,100)=0)
    or             remainder(i,1000)=0 then
        printf(1,"%4d  %58s %61s\n", {i, shorten(mpz_get_str(pow7,10,true)),isqrt(pow7)})
    end if
    mpz_mul_si(pow7,pow7,7)
end for


def isqrt ( x ):
    q = 1
    while q <= x :
        q *= 4
    z,r = x,0
    while q > 1 :
        q  /= 4
        t,r = z-r-q,r/2
        if t >= 0 :
            z,r = t,r+q
    return r

print ' '.join( '%d'%isqrt( n ) for n in xrange( 66 ))
print '\n'.join( '{0:114,} = isqrt( 7^{1:3} )'.format( isqrt( 7**n ),n ) for n in range( 1,204,2 ))


  [ dup size 3 / times
      [ char , swap
        i 1+ -3 * stuff ]
    dup 0 peek char , =
    if [ behead drop ] ]  is +commas (   $ --> $   )

  [ over size -
    space swap of
    swap join ]           is justify ( $ n --> $   )

  [ 1
    [ 2dup < not while
      2 << again ]
    0
    [ over 1 > while
      dip
        [ 2 >>
          2dup - ]
      dup 1 >>
      unrot -
      dup 0 < iff drop
      else
        [ 2swap nip
          rot over + ]
      again ]
    nip swap ]            is sqrt    (   n --> n n )

( sqrt returns the integer square root and remainder )
( i.e. isqrt of 28 is 5 remainder 3 as (5^2)+3 = 28  )
( To make it task compliant change the last line to  )
( "nip nip ]             is sqrt    (   n --> n   )" )

  66 times [ i^ sqrt drop echo sp ] cr cr

  73 times
    [ 7 i^ 1+ ** sqrt drop
      number$ +commas 41 justify
      echo$ cr
      2 step ]


#lang racket

;; Integer Square Root (using Quadratic Residue)
(define (isqrt x)
  (define q-init       ; power of 4 greater than x
    (let loop ([acc 1])
      (if (<= acc x) (loop (* acc 4)) acc)))

  (define-values (z r q)
    (let loop ([z x] [r 0] [q q-init])
      (if (<= q 1)
          (values z r q)
          (let* ([q (/ q 4)]
                 [t (- z r q)]
                 [r (/ r 2)])
            (if (>= t 0)
                (loop t (+ r q) q)
                (loop z r q))))))

  r)

(define (format-with-commas str #:chunk-size [size 3])
  (define len (string-length str))
  (define len-mod (modulo len size))
  (define chunks
    (for/list ([i (in-range len-mod len size)])
           (substring str i (+ i size))))
  (string-join (if (= len-mod 0)
                   chunks
                   (cons (substring str 0 len-mod) chunks))
               ","))

(displayln "Isqrt of integers (0 -> 65):")
(for ([i 66])
  (printf "~a " (isqrt i)))

(displayln "\n\nIsqrt of odd powers of 7 (7 -> 7^73):")
(for/fold ([num 7]) ([i (in-range 1 74 2)])
  (printf "Isqrt(7^~a) = ~a\n"
          i
          (format-with-commas (number->string (isqrt num))))
  (* num 49))


/*REXX program computes and displays the Isqrt  (integer square root)  of some integers.*/
numeric digits 200                               /*insure 'nuff decimal digs for results*/
parse arg range power base .                     /*obtain optional arguments from the CL*/
if range=='' | range==","  then range= 0..65     /*Not specified?  Then use the default.*/
if power=='' | power==","  then power= 1..73     /* "      "         "   "   "     "    */
if base =='' | base ==","  then base =     7     /* "      "         "   "   "     "    */
parse var  range   rLO  '..'  rHI;     if rHI==''  then rHI= rLO      /*handle a range? */
parse var  power   pLO  '..'  pHI;     if pHI==''  then pHI= pLO      /*   "   "   "    */
$=
            do j=rLO  to rHI  while rHI>0        /*compute Isqrt for a range of integers*/
            $= $ commas( Isqrt(j) )              /*append the Isqrt to a list for output*/
            end   /*j*/
$= strip($)                                      /*elide the leading blank in the list. */
say center(' Isqrt for numbers: '   rLO   " ──► "  rHI' ',  length($),  "─")
say strip($)                                     /*$  has a leading blank for 1st number*/
say
z= base ** pHI                                   /*compute  max. exponentiation product.*/
Lp= max(30, length( commas(       z) ) )         /*length of "          "          "    */
Lr= max(20, length( commas( Isqrt(z) ) ) )       /* "     "    "  "   "  Isqrt of above.*/
say 'index'   center(base"**index", Lp)       center('Isqrt', Lr)        /*show a title.*/
say '─────'   copies("─",           Lp)       copies('─',     Lr)        /*  "  " header*/

            do j=pLO  to pHI  by 2  while pHI>0;                              x= base ** j
            say center(j, 5)  right( commas(x), Lp)      right( commas( Isqrt(x) ),  Lr)
            end   /*j*/                          /* [↑]  show a bunch of powers & Isqrt.*/
exit 0                                           /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
commas: parse arg _;  do jc=length(_)-3  to 1  by -3; _=insert(',', _, jc); end;  return _
/*──────────────────────────────────────────────────────────────────────────────────────*/
Isqrt: procedure; parse arg x                    /*obtain the only passed argument  X.  */
       x= x % 1                                  /*convert possible real X to an integer*/     /* ◄■■■■■■■  optional. */
       q= 1                                      /*initialize the  Q  variable to unity.*/
                               do until q>x      /*find a  Q  that is greater than  X.  */
                               q= q * 4          /*multiply   Q   by four.              */
                               end   /*until*/
       r= 0                                      /*R:    will be the integer sqrt of X. */
                 do while q>1                    /*keep processing while  Q  is > than 1*/
                 q= q % 4                        /*divide  Q  by four  (no remainder).  */
                 t= x - r - q                    /*compute a temporary variable.        */
                 r= r % 2                        /*divide  R  by two   (no remainder).  */
                 if t >= 0  then do              /*if   T  is non─negative  ...         */
                                 x= t            /*recompute the value of  X            */
                                 r= r + q        /*    "      "    "    "  R            */
                                 end
                 end   /*while*/
       return r                                  /*return the integer square root of X. */


module Commatize
  refine Integer do
    def commatize
      self.to_s.gsub( /(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/, "\\1,\\2")
    end
  end
end

using Commatize
def isqrt(x)
    q, r = 1, 0
    while (q <= x) do q <<= 2 end
    while (q > 1) do
        q >>= 2; t = x-r-q; r >>= 1
        if (t >= 0) then x, r = t, r+q end
    end
    r
end

puts (0..65).map{|n| isqrt(n) }.join(" ")

1.step(73, 2) do |n|
  print "#{n}:\t"
  puts isqrt(7**n).commatize
end


use num::BigUint;
use num::CheckedSub;
use num_traits::{One, Zero};

fn isqrt(number: &BigUint) -> BigUint {
    let mut q: BigUint = One::one();
    while q <= *number {
        q <<= &2;
    }

    let mut z = number.clone();
    let mut result: BigUint = Zero::zero();

    while q > One::one() {
        q >>= &2;
        let t = z.checked_sub(&result).and_then(|diff| diff.checked_sub(&q));
        result >>= &1;

        if let Some(t) = t {
            z = t;
            result += &q;
        }
    }

    result
}

fn with_thousand_separator(s: &str) -> String {
    let digits: Vec<_> = s.chars().rev().collect();
    let chunks: Vec<_> = digits
        .chunks(3)
        .map(|chunk| chunk.iter().collect::<String>())
        .collect();

    chunks.join(",").chars().rev().collect::<String>()
}

fn main() {
    println!("The integer square roots of integers from 0 to 65 are:");
    (0_u32..=65).for_each(|n| print!("{} ", isqrt(&n.into())));

    println!("\nThe integer square roots of odd powers of 7 from 7^1 up to 7^74 are:");
    (1_u32..75).step_by(2).for_each(|exp| {
        println!(
            "7^{:>2}={:>83} ISQRT: {:>42} ",
            exp,
            with_thousand_separator(&BigUint::from(7_u8).pow(exp).to_string()),
            with_thousand_separator(&isqrt(&BigUint::from(7_u8).pow(exp)).to_string())
        )
    });
}


comment
  return integer square root of n using quadratic
  residue algorithm. WARNING: the function will fail
  for x > 16,383.
end
function isqrt(x = integer) = integer
  var q, r, t = integer
  q = 1
  while q <= x do
    q = q * 4       rem overflow may occur here!
  r = 0
  while q > 1 do
    begin
      q = q / 4
      t = x - r - q
      r = r / 2
      if t >= 0 then
        begin
          x = t
          r = r + q
        end
    end
end = r

rem - Exercise the function

var n, pow7 = integer
print "Integer square root of first 65 numbers"
for n=1 to 65
  print using "#####";isqrt(n);
next n
print
print "Integer square root of odd powers of 7"
print "  n    7^n   isqrt"
print "------------------"
for n=1 to 3 step 2
  pow7 = 7^n
  print using "###  ####  ####";n; pow7; isqrt(pow7)
next n

end


(import (scheme base))
(import (scheme write))
(import (format)) ;; Common Lisp formatting for CHICKEN Scheme.

(define (find-a-power-of-4-greater-than-x x)
  (let loop ((q 1))
    (if (< x q)
        q
        (loop (* 4 q)))))

(define (isqrt+remainder x)
  (let loop ((q (find-a-power-of-4-greater-than-x x))
             (z x)
             (r 0))
    (if (= q 1)
        (values r z)
        (let* ((q (truncate-quotient q 4))
               (t (- z r q))
               (r (truncate-quotient r 2)))
          (if (negative? t)
              (loop q z r)
              (loop q t (+ r q)))))))

(define (isqrt x)
  (let-values (((q r) (isqrt+remainder x)))
    q))

(format #t "isqrt(i) for ~D <= i <= ~D:~2%" 0 65)
(do ((i 0 (+ i 1)))
    ((= i 65))
  (format #t "~D " (isqrt i)))
(format #t "~D~3%" (isqrt 65))

(format #t "isqrt(7**i) for ~D <= i <= ~D, i odd:~2%" 1 73)
(format #t "[email protected] [email protected] [email protected]~%" "i" "7**i" "sqrt(7**i)")
(format #t "~A~%" (make-string 131 #\-))
(do ((i 1 (+ i 2)))
    ((= i 75))
  (let ((7**i (expt 7 i)))
    (format #t "~2D ~84:D ~43:D~%" i 7**i (isqrt 7**i))))


$ include "seed7_05.s7i";
  include "bigint.s7i";

const func string: commatize (in bigInteger: bigNum) is func
  result
    var string: stri is "";
  local
    var integer: index is 0;
  begin
    stri := str(bigNum);
    for index range length(stri) - 3 downto 1 step 3 do
      stri := stri[.. index] & "," & stri[succ(index) ..];
    end for;
  end func;

const proc: main is func
  local
    var integer: number is 0;
    var bigInteger: pow7 is 7_;
  begin
    writeln("The integer square roots of integers from 0 to 65 are:");
    for number range 0 to 65 do
      write(sqrt(number) <& " ");
    end for;
    writeln("\n\nThe integer square roots of powers of 7 from 7**1 up to 7**73 are:");
    writeln("power                                    7 ** power                                                integer square root");
    writeln("----- --------------------------------------------------------------------------------- -----------------------------------------");
    for number range 1 to 73 step 2 do
        writeln(number lpad 2 <& commatize(pow7) lpad 85 <& commatize(sqrt(pow7)) lpad 42);
        pow7 := pow7 * 49_;
    end for;
  end func;


(*

  The Rosetta Code integer square root task, in Standard ML.

  Compile with, for example:

     mlton isqrt.sml

*)

val zero = IntInf.fromInt (0)
val one = IntInf.fromInt (1)
val seven = IntInf.fromInt (7)
val word1 = Word.fromInt (1)
val word2 = Word.fromInt (2)

fun
find_a_power_of_4_greater_than_x (x) =
let
  fun
  loop (q) =
  if x < q then
    q
  else
    loop (IntInf.<< (q, word2))
in
  loop (one)
end;

fun
isqrt (x) =
let
  fun
  loop (q, z, r) =
  if q = one then
    r
  else
    let
      val q = IntInf.~>> (q, word2)
      val t = z - r - q
      val r = IntInf.~>> (r, word1)
    in
      if t < zero then
        loop (q, z, r)
      else
        loop (q, t, r + q)
    end
in
  loop (find_a_power_of_4_greater_than_x (x), x, zero)
end;

fun
insert_separators (s, sep) =
(* Insert separator characters (such as #",", #".", #" ") in a numeral
   that is already in string form. *)
let
  fun
  loop (revchars, i, newchars) =
  case (revchars, i) of
      ([], _) => newchars
    | (revchars, 3) => loop (revchars, 0, sep :: newchars)
    | (c :: tail, i) => loop (tail, i + 1, c :: newchars)
in
  implode (loop (rev (explode s), 0, []))
end;

fun
commas (s) =
(* Insert commas in a numeral that is already in string form. *)
insert_separators (s, #",");

val pad_with_spaces = StringCvt.padLeft #" "

fun
main () =
let
  val i = ref 0
in
  print ("isqrt(i) for 0 <= i <= 65:\n\n");

  i := 0;
  while !i < 65 do (
    print (IntInf.toString (isqrt (IntInf.fromInt (!i))));
    print (" ");
    i := !i + 1
  );
  print (IntInf.toString (isqrt (IntInf.fromInt (65))));
  print ("\n\n\n");

  print ("isqrt(7**i) for 1 <= i <= 73, i odd:\n\n");
  print (pad_with_spaces 2 "i");
  print (pad_with_spaces 85 "7**i");
  print (pad_with_spaces 44 "sqrt(7**i)");
  print ("\n");

  i := 1;
  while !i <= 131 do (
    print ("-");
    i := !i + 1
  );
  print ("\n");

  i := 1;
  while !i <= 73 do (
    let
      val pow7 = IntInf.pow (seven, !i)
      val root = isqrt (pow7)
    in
      print (pad_with_spaces 2 (Int.toString (!i)));
      print (pad_with_spaces 85 (commas (IntInf.toString pow7)));
      print (pad_with_spaces 44 (commas (IntInf.toString root)));
      print ("\n");
      i := !i + 2
    end
  )
end;

main ();

(* local variables: *)
(* mode: sml *)
(* sml-indent-level: 2 *)
(* sml-indent-args: 2 *)
(* end: *)


import BigInt

func integerSquareRoot<T: BinaryInteger>(_ num: T) -> T {
    var x: T = num
    var q: T = 1
    while q <= x {
        q <<= 2
    }
    var r: T = 0
    while q > 1 {
        q >>= 2
        let t: T = x - r - q
        r >>= 1
        if t >= 0 {
            x = t
            r += q
        }
    }
    return r
}

func pad(string: String, width: Int) -> String {
    if string.count >= width {
        return string
    }
    return String(repeating: " ", count: width - string.count) + string
}

func commatize<T: BinaryInteger>(_ num: T) -> String {
    let string = String(num)
    var result = String()
    result.reserveCapacity(4 * string.count / 3)
    var i = 0
    for ch in string {
        if i > 0 && i % 3 == string.count % 3 {
            result += ","
        }
        result.append(ch)
        i += 1
    }
    return result
}

print("Integer square root for numbers 0 to 65:")
for n in 0...65 {
    print(integerSquareRoot(n), terminator: " ")
}

let powerWidth = 83
let isqrtWidth = 42
print("\n\nInteger square roots of odd powers of 7 from 1 to 73:")
print(" n |\(pad(string: "7 ^ n", width: powerWidth)) |\(pad(string: "isqrt(7 ^ n)", width: isqrtWidth))")
print(String(repeating: "-", count: powerWidth + isqrtWidth + 6))
var p: BigInt = 7
for n in stride(from: 1, through: 73, by: 2) {
    let power = pad(string: commatize(p), width: powerWidth)
    let isqrt = pad(string: commatize(integerSquareRoot(p)), width: isqrtWidth)
    print("\(pad(string: String(n), width: 2)) |\(power) |\(isqrt)")
    p *= 49
}


10 LET X = 0
20 GOSUB 100
30 PRINT R
40 LET X = X + 1
50 IF X < 66 THEN GOTO 20
70 PRINT "---"
71 LET X = 7
72 GOSUB 100
73 PRINT R
77 LET X = 343
78 GOSUB 100
79 PRINT R
90 END
100 REM integer square root function
110 LET Q = 1
120 IF Q > X THEN GOTO 150
130 LET Q = Q * 4
140 GOTO 120
150 LET Z = X
160 LET R = 0
170 IF Q <= 1 THEN RETURN
180 LET Q = Q / 4
190 LET T = Z - R - Q
200 LET R = R / 2
210 IF T < 0 THEN GOTO 170
220 LET Z = T
230 LET R = R + Q
240 GOTO 170


function isqrt {
  typeset -i x
  for x; do
    typeset -i q=1
    while (( q <= x )); do
      (( q <<= 2 ))
      if (( q <= 0 )); then
        return 1
      fi
    done
    typeset -i z=x
    typeset -i r=0
    typeset -i t
    while (( q > 1 )); do
      (( q >>= 2 ))
      (( t = z - r - q ))
      (( r >>= 1 ))
      if (( t >= 0 )); then
        (( z = t ))
        (( r = r + q ))
      fi
    done
    printf '%d\n' "$r"
  done
}

# demo
printf 'isqrt(n) for n from 0 to 65:\n'
for i in {1..4}; do
  for n in {0..65}; do
    case $i in
     1)
      (( tens=n/10 ))
      if (( tens )); then
        printf '%2d' "$tens"
      else
        printf '  '
      fi
      ;;
     2) printf '%2d' $(( n%10 ));;
     3) printf -- '--';;
     4) printf '%2d' "$(isqrt "$n")";;
    esac
  done
  printf '\n'
done
printf '\n'

printf 'isqrt(7ⁿ) for odd n up to the limit of integer precision:\n'
printf '%2s|%27sⁿ|%14sⁿ)\n' "n" "7" "isqrt(7"
for (( i=0;i<48; ++i )); do printf '-'; done; printf '\n'
for (( p=1; p<=73 && (n=7**p) > 0; p+=2)); do
  if r=$(isqrt $n); then
    printf "%2d|%'28d|%'16d\n" "$p" "$n" "$r"
  else
    break
  fi
done


Imports System
Imports System.Console
Imports BI = System.Numerics.BigInteger

Module Module1
    Function isqrt(ByVal x As BI) As BI
        Dim t As BI, q As BI = 1, r As BI = 0
        While q <= x : q <<= 2 : End While
        While q > 1 : q >>= 2 : t = x - r - q : r >>= 1
            If t >= 0 Then x = t : r += q
        End While : Return r
    End Function

    Sub Main()
        Const max As Integer = 73, smax As Integer = 65
        Dim power_width As Integer = ((BI.Pow(7, max).ToString().Length \ 3) << 2) + 3,
            isqrt_width As Integer = (power_width + 1) >> 1,
            n as Integer
        WriteLine("Integer square root for numbers 0 to {0}:", smax)
        For n = 0 To smax : Write("{0} ", (n \ 10).ToString().Replace("0", " "))
            Next : WriteLine()
        For n = 0 To smax : Write("{0} ", n Mod 10) : Next : WriteLine()
        WriteLine(New String("-"c, (smax << 1) + 1))
        For n = 0 To smax : Write("{0} ", isqrt(n)) : Next
        WriteLine(vbLf & vbLf & "Integer square roots of odd powers of 7 from 1 to {0}:", max)
        Dim s As String = String.Format("[0,2] |[1,{0}:n0] |[2,{1}:n0]",
            power_width, isqrt_width).Replace("[", "{").Replace("]", "}")
        WriteLine(s, "n", "7 ^ n", "isqrt(7 ^ n)")
        WriteLine(New String("-"c, power_width + isqrt_width + 6))
        Dim p As BI = 7 : n = 1 : While n <= max
            WriteLine(s, n, p, isqrt(p)) : n += 2 : p = p * 49
        End While
    End Sub
End Module


import "/big" for BigInt
import "/fmt" for Fmt

var isqrt = Fn.new { |x|
    if (!(x is BigInt && x >= BigInt.zero)) {
        Fiber.abort("Argument must be a non-negative big integer.")
    }
    var q = BigInt.one
    while (q <= x) q = q * 4
    var z = x
    var r = BigInt.zero
    while (q > BigInt.one) {
        q = q >> 2
        var t = z - r - q
        r = r >> 1
        if (t >= 0) {
            z = t
            r = r + q
        }
    }
    return r
}

System.print("The integer square roots of integers from 0 to 65 are:")
for (i in 0..65) System.write("%(isqrt.call(BigInt.new(i))) ")
System.print()

System.print("\nThe integer square roots of powers of 7 from 7^1 up to 7^73 are:\n")
System.print("power                                    7 ^ power                                                 integer square root")
System.print("----- --------------------------------------------------------------------------------- -----------------------------------------")
var pow7 = BigInt.new(7)
var bi49 = BigInt.new(49)
var i = 1
while (i <= 73) {
    Fmt.print("$2d $,84s $,41s", i, pow7, isqrt.call(pow7))
    pow7 = pow7 * bi49
    i = i + 2
}


// Rosetta Code problem: https://rosettacode.org/wiki/Isqrt_(integer_square_root)_of_X
// by Jjuanhdez, 06/2022

print "Integer square root of first 65 numbers:"
for n = 1 to 65
    print isqrt(n) using("##");
next n
print : print
print "Integer square root of odd powers of 7"
print "  n |                7^n |     isqrt "
print "----|--------------------|-----------"
for n = 1 to 21 step 2
    pow7 = 7 ^ n
    print n using("###"), " | ", left$(str$(pow7,"%20.1f"),18), " | ", left$(str$(isqrt(pow7),"%11.1f"),9)

next n
end

sub isqrt(x)
    q = 1
    while q <= x
        q = q * 4
    wend
    r = 0
    while q > 1
        q = q / 4
        t = x - r - q
        r = r / 2
        if t >= 0 then
            x = t
            r = r + q
        end if
    wend
    return int(r)
end sub


DefaultDict[String, Set[String]] index

F parse_file(fname, fcontents)
   L(word) fcontents.split(re:‘\W’)
      :index[word.lowercase()].add(fname)

L(fname, fcontents) [(‘inv1.txt’, ‘It is what it is.’),
                     (‘inv2.txt’, ‘What is it?’),
                     (‘inv3.txt’, ‘It is a banana!’)]
   parse_file(fname, fcontents)

L(w) [‘cat’, ‘is’, ‘banana’, ‘it’, ‘what’]
   print("\nEnter a word to search for: (q to quit): "w)
   I w C index
      print(‘'#.' found in #..’.format(w, sorted(Array(index[w]))))
   E
      print(‘'#.' not found.’.format(w))


with Ada.Text_IO, Generic_Inverted_Index, Ada.Strings.Hash, Parse_Lines;
use Ada.Text_IO;

procedure Inverted_Index is

   type Process_Word is access procedure (Word: String);

   package Inv_Idx is new Generic_Inverted_Index
     (Source_Type => String,
      Item_Type   => String,
      Hash        => Ada.Strings.Hash);

   use Inv_Idx;

   procedure Output(Sources: Source_Vecs.Vector) is
      Any_Output: Boolean := False;

      procedure Print_Source(S: String) is
      begin
         if not Any_Output then -- this is the first source found
            Put("Found in the following files: ");
            Any_Output := True;
         else -- there has been at least one source before
            Put(", ");
         end if;
         Put(S);
      end Print_Source;

      procedure Print is new Inv_Idx.Iterate(Print_Source);

   begin
      Print(Sources);
      if not Any_Output then
         Put("I did not find this in any of the given files!");
      end if;
      New_Line(2);
   end Output;

   procedure Read_From_File(Table: in out Storage_Type;
                            Filename: String) is
      F: File_Type;

      procedure Enter_Word(S: String) is
      begin
         Table.Store(Source => Filename,  Item => S);
      end Enter_Word;

      procedure Store_Words is new
        Parse_Lines.Iterate_Words(Parse_Lines.Word_Pattern, Enter_Word);

   begin
      Open(File => F, Mode => In_File, Name => Filename);
       while not End_Of_File(F) loop
          Store_Words(Get_Line(F));
      end loop;
      Close(F);
   exception
      when others =>
         Put_Line("Something wrong with File '" & Filename & "'");
         Put_Line("I'll ignore this!");
   end Read_From_File;

   procedure Read_Files(Tab: out Storage_Type; Line: in String) is

      procedure Read_File(S: String) is
      begin
         Read_From_File(Tab, S);
      end Read_File;

      procedure Read_All is new
        Parse_Lines.Iterate_Words(Parse_Lines.Filename_Pattern, Read_File);

   begin
      Read_All(Line);
   end Read_Files;

   S: Storage_Type;
   Done: Boolean := False;

begin
   Put_Line("Enter Filenames:");
   Read_Files(S, Get_Line);
   New_Line;

   while not Done loop
      Put_Line("Enter one or more words to search for; <return> to finish:");
      declare
         Words: String := Get_Line;
         First: Boolean := True;
         Vec: Source_Vecs.Vector := Source_Vecs.Empty_Vector;

         procedure Compute_Vector(Item: String) is
         begin
            if First then
               Vec := S.Find(Item);
               First := False;
            else
               Vec := Vec and S.Find(Item);
            end if;
         end Compute_Vector;

         procedure Compute is new
           Parse_Lines.Iterate_Words(Parse_Lines.Word_Pattern, Compute_Vector);

      begin
         if Words = "" then
            Done := True;
         else
            Compute(Words);
            Output(Vec);
         end if;
      end;
   end loop;
end Inverted_Index;


; http://www.autohotkey.com/forum/viewtopic.php?t=41479
inputbox, files, files, file pattern such as c:\files\*.txt

word2docs := object() ; autohotkey_L is needed.

stime := A_tickcount
Loop, %files%, 0,1
{
   tooltip,%A_index%  / 500

   wordList := WordsIn(A_LoopFileFullPath)
   InvertedIndex(wordList, A_loopFileFullpath)
}

tooltip
msgbox, % "total time " (A_tickcount-stime)/1000

gosub, search
return

search:
Loop
{
   InputBox, keyword , input single keyword only
   msgbox, % foundDocs := findword(keyword)
}
return

WordsIn(docpath)
{
   FileRead, content, %docpath%
  spos = 1
   Loop
   {
     if !(spos := Regexmatch(content, "[a-zA-Z]{2,}",match, spos))
       break
     spos += strlen(match)
     this_wordList .= match "`n"
   }

  Sort, this_wordList, U
  return this_wordList
}

InvertedIndex(byref words, docpath)
{
   global word2docs

  loop, parse, words, `n,`r
  {
    if A_loopField =
      continue
    word2docs[A_loopField] := word2docs[A_loopField] docpath "`n"
  }
}

findWord(word2find)
{
  global word2docs

  if (word2docs[word2find] = "")
     return ""
  else
    return word2docs[word2find]
}


      DIM FileList$(4)
      FileList$() = "BBCKEY0.TXT", "BBCKEY1.TXT", "BBCKEY2.TXT", \
      \             "BBCKEY3.TXT", "BBCKEY4.TXT"

      DictSize% = 30000
      DIM Index{(DictSize%-1) word$, link%}

      REM Build inverted index:
      FOR file% = DIM(FileList$(),1) TO 0 STEP -1
        filename$ = FileList$(file%)
        F% = OPENIN(filename$)
        IF F% = 0 ERROR 100, "Failed to open file"

        WHILE NOT EOF#F%
          REPEAT C%=BGET#F% : UNTIL C%>64 OR EOF#F% : word$ = CHR$(C%)
          REPEAT C%=BGET#F% : word$ += CHR$(C%) : UNTIL C%<65
          word$ = FNlower(LEFT$(word$))

          hash% = FNhash(word$)
          WHILE Index{(hash%)}.word$<>"" AND Index{(hash%)}.word$<>word$
            hash% = (hash% + 1) MOD DictSize% : REM Collision
          ENDWHILE
          Index{(hash%)}.word$ = word$
          link% = Index{(hash%)}.link%
          IF link%=0 OR link%!4<>file% THEN
            DIM heap% 7 : heap%!4 = file%
            !heap% = link%
            Index{(hash%)}.link% = heap% : REM Linked list
          ENDIF
        ENDWHILE

        CLOSE #F%
      NEXT file%

      REM Now query the index:
      PRINT FNquery("random")
      PRINT FNquery("envelope")
      PRINT FNquery("zebra")
      PRINT FNquery("the")
      END

      DEF FNquery(A$)
      LOCAL hash%, link%, temp%
      A$ = FNlower(A$)
      hash% = FNhash(A$)
      temp% = hash%
      WHILE Index{(hash%)}.word$ <> A$
        hash% = (hash% + 1) MOD DictSize%
        IF hash% = temp% THEN = """" + A$ + """ not found"
      ENDWHILE
      link% = Index{(hash%)}.link%
      A$ = """" + A$ + """ found in "
      WHILE link%
        A$ += FileList$(link%!4) + ", "
        link% = !link%
      ENDWHILE
      = LEFT$(LEFT$(A$))

      DEF FNhash(A$)
      LOCAL hash%
      IF LEN(A$) < 4 A$ += STRING$(4-LEN(A$),CHR$0)
      hash% = !!^A$
      IF LEN(A$) > 4 hash% EOR= !(!^A$ + LEN(A$) - 4)
      = hash% MOD DictSize%

      DEF FNlower(A$)
      LOCAL A%,C%
      FOR A% = 1 TO LEN(A$)
        C% = ASCMID$(A$,A%)
        IF C% >= 65 IF C% <= 90 MID$(A$,A%,1) = CHR$(C%+32)
      NEXT
      = A$


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

char chr_legal[] = "abcdefghijklmnopqrstuvwxyz0123456789_-./";
int  chr_idx[256] = {0};
char idx_chr[256] = {0};

#define FNAME 0
typedef struct trie_t *trie, trie_t;
struct trie_t {
    trie next[sizeof(chr_legal)]; /* next letter; slot 0 is for file name */
    int eow;
};

trie trie_new() { return calloc(sizeof(trie_t), 1); }

#define find_word(r, w) trie_trav(r, w, 1)
/* tree traversal: returns node if end of word and matches string, optionally
 * create node if doesn't exist
 */
trie trie_trav(trie root, const char * str, int no_create)
{
    int c;
    while (root) {
        if ((c = str[0]) == '\0') {
            if (!root->eow && no_create) return 0;
            break;
        }
        if (! (c = chr_idx[c]) ) {
            str++;
            continue;
        }

        if (!root->next[c]) {
            if (no_create) return 0;
            root->next[c] = trie_new();
        }
        root = root->next[c];
        str++;
    }
    return root;
}

/*  complete traversal of whole tree, calling callback at each end of word node.
 *  similar method can be used to free nodes, had we wanted to do that.
 */
int trie_all(trie root, char path[], int depth, int (*callback)(char *))
{
    int i;
    if (root->eow && !callback(path)) return 0;

    for (i = 1; i < sizeof(chr_legal); i++) {
        if (!root->next[i]) continue;

        path[depth] = idx_chr[i];
        path[depth + 1] = '\0';
        if (!trie_all(root->next[i], path, depth + 1, callback))
            return 0;
    }
    return 1;
}

void add_index(trie root, const char *word, const char *fname)
{
    trie x = trie_trav(root, word, 0);
    x->eow = 1;

    if (!x->next[FNAME])
        x->next[FNAME] = trie_new();
    x = trie_trav(x->next[FNAME], fname, 0);
    x->eow = 1;
}

int print_path(char *path)
{
    printf(" %s", path);
    return 1;
}

/*  pretend we parsed text files and got lower cased words: dealing     *
 *  with text file is a whole other animal and would make code too long */
const char *files[] = { "f1.txt", "source/f2.txt", "other_file" };
const char *text[][5] ={{ "it", "is", "what", "it", "is" },
                { "what", "is", "it", 0 },
                { "it", "is", "a", "banana", 0 }};

trie init_tables()
{
    int i, j;
    trie root = trie_new();
    for (i = 0; i < sizeof(chr_legal); i++) {
        chr_idx[(int)chr_legal[i]] = i + 1;
        idx_chr[i + 1] = chr_legal[i];
    }

/* Enable USE_ADVANCED_FILE_HANDLING to use advanced file handling.
 * You need to have files named like above files[], with words in them
 * like in text[][].  Case doesn't matter (told you it's advanced).
 */
#define USE_ADVANCED_FILE_HANDLING 0
#if USE_ADVANCED_FILE_HANDLING
    void read_file(const char * fname) {
        char cmd[1024];
        char word[1024];
        sprintf(cmd, "perl -p -e 'while(/(\\w+)/g) {print lc($1),\"\\n\"}' %s", fname);
        FILE *in = popen(cmd, "r");
        while (!feof(in)) {
            fscanf(in, "%1000s", word);
            add_index(root, word, fname);
        }
        pclose(in);
    };

    read_file("f1.txt");
    read_file("source/f2.txt");
    read_file("other_file");
#else
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 5; j++) {
            if (!text[i][j]) break;
            add_index(root, text[i][j], files[i]);
        }
    }
#endif /*USE_ADVANCED_FILE_HANDLING*/

    return root;
}

void search_index(trie root, const char *word)
{
    char path[1024];
    printf("Search for \"%s\": ", word);
    trie found = find_word(root, word);

    if (!found) printf("not found\n");
    else {
        trie_all(found->next[FNAME], path, 0, print_path);
        printf("\n");
    }
}

int main()
{
    trie root = init_tables();

    search_index(root, "what");
    search_index(root, "is");
    search_index(root, "banana");
    search_index(root, "boo");
    return 0;
}


using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

class InvertedIndex
{
    static Dictionary<TItem, IEnumerable<TKey>> Invert<TKey, TItem>(Dictionary<TKey, IEnumerable<TItem>> dictionary)
    {
        return dictionary
            .SelectMany(keyValuePair => keyValuePair.Value.Select(item => new KeyValuePair<TItem, TKey>(item, keyValuePair.Key)))
            .GroupBy(keyValuePair => keyValuePair.Key)
            .ToDictionary(group => group.Key, group => group.Select(keyValuePair => keyValuePair.Value));
    }

    static void Main()
    {
        Console.Write("files: ");
        var files = Console.ReadLine();
        Console.Write("find: ");
        var find = Console.ReadLine();
        var dictionary = files.Split().ToDictionary(file => file, file => File.ReadAllText(file).Split().AsEnumerable());
        Console.WriteLine("{0} found in: {1}", find, string.Join(" ", Invert(dictionary)[find]));
    }
}


#include <algorithm>
#include <fstream>
#include <iostream>
#include <vector>
#include <string>

const std::string _CHARS = "abcdefghijklmnopqrstuvwxyz0123456789.:-_/";
const size_t MAX_NODES = 41;

class node
{
public:
    node() { clear(); }
    node( char z ) { clear(); }
    ~node() { for( int x = 0; x < MAX_NODES; x++ ) if( next[x] ) delete next[x]; }
    void clear() { for( int x = 0; x < MAX_NODES; x++ ) next[x] = 0; isWord = false; }
    bool isWord;
    std::vector<std::string> files;
    node* next[MAX_NODES];
};

class index {
public:
    void add( std::string s, std::string fileName ) {
        std::transform( s.begin(), s.end(), s.begin(), tolower );
        std::string h;
        for( std::string::iterator i = s.begin(); i != s.end(); i++ ) {
            if( *i == 32 ) {
                pushFileName( addWord( h ), fileName );
                h.clear();
                continue;
            }
            h.append( 1, *i );
        }
        if( h.length() )
            pushFileName( addWord( h ), fileName );
    }
    void findWord( std::string s ) {
        std::vector<std::string> v = find( s );
        if( !v.size() ) {
            std::cout << s + " was not found!\n";
            return;
        }
        std::cout << s << " found in:\n";
        for( std::vector<std::string>::iterator i = v.begin(); i != v.end(); i++ ) {
            std::cout << *i << "\n";
        }
        std::cout << "\n";
    }
private:
    void pushFileName( node* n, std::string fn ) {
        std::vector<std::string>::iterator i = std::find( n->files.begin(), n->files.end(), fn );
        if( i == n->files.end() ) n->files.push_back( fn );
    }
    const std::vector<std::string>& find( std::string s ) {
        size_t idx;
        std::transform( s.begin(), s.end(), s.begin(), tolower );
        node* rt = &root;
        for( std::string::iterator i = s.begin(); i != s.end(); i++ ) {
            idx = _CHARS.find( *i );
            if( idx < MAX_NODES ) {
                if( !rt->next[idx] ) return std::vector<std::string>();
                rt = rt->next[idx];
            }
        }
        if( rt->isWord ) return rt->files;
        return std::vector<std::string>();
    }
    node* addWord( std::string s ) {
        size_t idx;
        node* rt = &root, *n;
        for( std::string::iterator i = s.begin(); i != s.end(); i++ ) {
            idx = _CHARS.find( *i );
            if( idx < MAX_NODES ) {
                n = rt->next[idx];
                if( n ){
                    rt = n;
                    continue;
                }
                n = new node( *i );
                rt->next[idx] = n;
                rt = n;
            }
        }
        rt->isWord = true;
        return rt;
    }
    node root;
};
int main( int argc, char* argv[] ) {
    index t;
    std::string s;
    std::string files[] = { "file1.txt", "f_text.txt", "text_1b.txt" };

    for( int x = 0; x < 3; x++ ) {
        std::ifstream f;
        f.open( files[x].c_str(), std::ios::in );
        if( f.good() ) {
            while( !f.eof() ) {
                f >> s;
                t.add( s, files[x] );
                s.clear();
            }
            f.close();
        }
    }

    while( true ) {
        std::cout << "Enter one word to search for, return to exit: ";
        std::getline( std::cin, s );
        if( !s.length() ) break;
        t.findWord( s );

    }
    return 0;
}


(ns inverted-index.core
  (:require [clojure.set :as sets]
            [clojure.java.io :as io]))

(def pattern #"\w+")     ; Java regex for a raw term: here a substring of alphanums
(defn normalize [match] (.toLowerCase match))  ; normalization of a raw term

(defn term-seq [text] (map normalize (re-seq pattern text)))

(defn set-assoc
  "Produces map with v added to the set associated with key k in map m"
  [m k v] (assoc m k (conj (get m k #{}) v)))

(defn index-file [index file]
  (with-open [reader (io/reader file)]
    (reduce
      (fn [idx term] (set-assoc idx term file))
      index
      (mapcat term-seq (line-seq reader)))))

(defn make-index [files]
  (reduce index-file {} files))

(defn search [index query]
  (apply sets/intersection (map index (term-seq query))))


fs = require 'fs'

make_index = (fns) ->
  # words are indexed by filename and 1-based line numbers
  index = {}
  for fn in fns
    for line, line_num in fs.readFileSync(fn).toString().split '\n'
      words = get_words line
      for word in words
        word = mangle(word)
        index[word] ||= []
        index[word].push [fn, line_num+1]
  index

grep = (index, word) ->
  console.log "locations for '#{word}':"
  locations = index[mangle(word)] || []
  for location in locations
    [fn, line_num] = location
    console.log "#{fn}:#{line_num}"
  console.log "\n"

get_words = (line) ->
  words = line.replace(/\W/g, ' ').split ' '
  (word for word in words when word != '')

mangle = (word) ->
  # avoid conflicts with words like "constructor"
  '_' + word

do ->
  fns = (fn for fn in fs.readdirSync('.') when fn.match /\.coffee/)
  index = make_index(fns)
  grep index, 'make_index'
  grep index, 'sort'


(defpackage rosettacode.inverted-index
  (:use cl))
(in-package rosettacode.inverted-index)

;; Return a list of tokens in the string LINE.  This is rather
;; complicated as CL has no good standard function to do it.
(defun tokenize (line)
  (let ((start 0) (len (length line)))
    (loop for s = (position-if #'alphanumericp line :start start)
          while s
          for e = (position-if-not #'alphanumericp line :start (1+ s))
          collect (subseq line s e)
          while (and e (< e len))
          do (setq start e))))

(defun index-file (index filename)
  (with-open-file (in filename)
    (loop for line = (read-line in nil nil)
          while line
          do (dolist (token (tokenize line))
               (pushnew filename (gethash token index '()))))))

(defun build-index (filenames)
  (let ((index (make-hash-table :test #'equal)))
    (dolist (f filenames)
      (index-file index f))
    index))

;; Find the files for QUERY.  We use the same tokenizer for the query
;; as for files.
(defun lookup (index query)
  (remove-duplicates (loop for token in (tokenize query)
                           append (gethash token index))
                     :test #'equal))


import std.stdio, std.algorithm, std.string, std.file, std.regex;

void main() {
    string[][string] index;

    void parseFile(in string fn) {
        if (!exists(fn) || !isFile(fn))
            throw new Exception("File not found");

        foreach (word; readText(fn).splitter(regex(r"\W"))) {
            word = word.toLower();
            if (!index.get(word, null).canFind(fn))
                index[word] ~= fn;
        }
    }

    immutable fileNames = ["inv1.txt", "inv2.txt", "inv3.txt"];
    foreach (fName; fileNames)
        parseFile(fName);

    while (true) {
        writef("\nEnter a word to search for: (q to quit): ");
        immutable w = readln().strip().toLower();
        if (w == "q") {
            writeln("quitting.");
            break;
        }
        if (w in index)
            writefln("'%s' found in%( %).", w, index[w]);
        else
            writefln("'%s' not found.", w);
    }
}


program Inverted_index;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  system.Generics.Collections,
  SYstem.IOUtils;

type
  TIndex = class
  private
    FFileNames: TArray<string>;
    FIndexs: TDictionary<string, string>;
    class function SplitWords(Text: string): TArray<string>; static;
    function StoreFileName(FileName: TFileName): string;
    procedure StoreLine(Line, Id: string);
    procedure StoreWord(aWord, Id: string);
    function DecodeFileName(Indexed: string): string;
  public
    procedure AddFile(FileName: TFileName);
    constructor Create;
    destructor Destroy; override;
    function Search(aWord: string): string;
    property Index[aWord: string]: string read Search; default;
  end;

{ TIndex }

constructor TIndex.Create;
begin
  FIndexs := TDictionary<string, string>.Create();
  SetLength(FFileNames, 0);
end;

function TIndex.DecodeFileName(Indexed: string): string;
var
  f: string;
  i: integer;
begin
  Result := Indexed;
  if Length(FFileNames) > 0 then
    for i := 0 to High(FFileNames) do
    begin
      f := FFileNames[i];
      Result := Result.Replace(format('(%x)', [i]), f);
    end;
end;

destructor TIndex.Destroy;
begin
  FIndexs.Free;
  inherited;
end;

function TIndex.Search(aWord: string): string;
begin
  aWord := AnsiLowerCase(aWord);
  if FIndexs.ContainsKey(aWord) then
    Result := 'found in ' + DecodeFileName(FIndexs[aWord])
  else
    Result := 'not found';
end;

class function TIndex.SplitWords(Text: string): TArray<string>;
const
  WORD_CHARSET: set of char = (['a'..'z', 'A'..'Z', 'á', 'é', 'í', 'ó', 'ú', 'ã',
    'õ', 'à', 'è', 'ì', 'ò', 'ù', 'Á', 'É', 'Í', 'Ó', 'Ú', 'Ã', 'Õ', 'À', 'È',
    'Ì', 'Ò', 'Ù', 'ä', 'ë', 'ï', 'ö', 'ü', 'ç', 'Ç', '_', '0'..'9']);

  procedure Append(var value: string);
  begin
    if not value.IsEmpty then
    begin
      SetLength(result, length(result) + 1);
      result[high(result)] := value;
      value := '';
    end;
  end;

var
  c: Char;
  inWord, isWordChar: boolean;
  w: string;
begin
  inWord := False;
  w := '';
  SetLength(result, 0);
  for c in Text do
  begin
    isWordChar := (c in WORD_CHARSET);
    if inWord then
      if isWordChar then
        w := w + c
      else
      begin
        Append(w);
        inWord := false;
        Continue;
      end;

    if not inWord and isWordChar then
    begin
      inWord := True;
      w := c;
    end;
  end;
  if inWord then
    Append(w);
end;

function TIndex.StoreFileName(FileName: TFileName): string;
begin
  SetLength(FFileNames, length(FFileNames) + 1);
  FFileNames[High(FFileNames)] := FileName;
  Result := format('"(%x)"', [High(FFileNames)]);
end;

procedure TIndex.StoreLine(Line, Id: string);
var
  Words: TArray<string>;
  w: string;
begin
  Words := SplitWords(Line);
  for w in Words do
    StoreWord(w, Id);
end;

procedure TIndex.StoreWord(aWord, Id: string);
begin
  aWord := AnsiLowercase(aWord);
  if FIndexs.ContainsKey(aWord) then
  begin
    if (FIndexs[aWord].IndexOf(Id) = -1) then
      FIndexs[aWord] := FIndexs[aWord] + ', ' + Id;
  end
  else
    FIndexs.Add(aWord, Id);
end;

procedure TIndex.AddFile(FileName: TFileName);
var
  Lines: TArray<string>;
  Line: string;
  Id: string;
begin
  if not FileExists(FileName) then
    exit;
  Lines := TFile.ReadAllLines(FileName);

  if length(Lines) = 0 then
    exit;

  // Remove this line if you want full filename
  FileName := ExtractFileName(FileName);

  Id := StoreFileName(FileName);

  for Line in Lines do
    StoreLine(Line, Id);
end;

var
  Index: TIndex;
  FileNames: TArray<TFileName> = ['file1.txt', 'file2.txt', 'file3.txt'];
  FileName: TFileName;
  Querys: TArray<string> = ['Cat', 'is', 'banana', 'it', 'what'];
  Query, Found: string;

begin
  Index := TIndex.Create;

  for FileName in FileNames do
    Index.AddFile(FileName);

  for Query in Querys do
    Writeln(format('"%s" %s'#10, [Query, Index[Query]]));

  repeat
    Writeln('Enter a word to search for: (q to quit)');
    Readln(Query);

    if Query = 'q' then
      Break;

    Writeln(format('"%s" %s'#10, [Query, Index[Query]]));
  until False;

  Index.Free;
end.


;; set of input files
(define FILES {T0.txt T1.txt T2.txt})
;; store name for permanent inverted index
(define INVERT "INVERTED-INDEX")

;; get text for each file, and call (action filename text)
(define (map-files action files)
    (for ((file files))
    (file->string action file)))

;; create store
(local-make-store INVERT)

; invert-word : word -> set of files
(define (invert-word word file store)
        (local-put-value word
            (make-set  (cons file (local-get-value word store))) store))

; parse file text and invert each word
(define (invert-text file text)
        (writeln 'Inverting file text)
        (let ((text (text-parse text)))
        (for ((word text))  (invert-word (string-downcase word) file INVERT))))


-module( inverted_index ).

-export( [from_files/1, search/2, task/0] ).

from_files( Files ) ->
        lists:foldl( fun import_from_file/2, dict:new(), Files ).

search( Binaries, Inverted_index ) ->
        [Files | T] = [dict:fetch(X, Inverted_index) || X <- Binaries],
        lists:foldl( fun search_common/2, Files, T ).

task() ->
       Files_contents = [{"file_1", <<"it is what it is">>}, {"file_2", <<"what is it">>}, {"file_3", <<"it is a banana">>}],
       [file:write_file(X, Y) || {X, Y} <- Files_contents],
       Inverted_index = from_files( [X || {X, _Y} <- Files_contents] ),
       Result = search( [<<"what">>, <<"is">>, <<"it">>], Inverted_index ),
       io:fwrite( "~p~n", [Result] ),
       [file:delete(X) || {X, _Y} <- Files_contents].

import_from_file( File, Dict_acc ) ->
        New_dict = dict:from_list( import_from_file_contents(File, file:read_file(File)) ),
    dict:merge( fun import_from_file_merge/3, Dict_acc, New_dict ).

import_from_file_contents( File, {ok, Binary} ) ->
        [{X, [File]} || X <- binary:split( Binary, binary:compile_pattern([<<" ">>, <<"\n">>]), [global] )];
import_from_file_contents( File, {error, Error} ) ->
    io:fwrite( "Error: could not open file ~p: ~p~nContinuing with the rest of them~n", [File,  Error] ),
    [].

import_from_file_merge( _Key, Files, [New_file] ) -> [New_file | Files].

search_common( Files, Acc ) -> [X || X <- Acc, lists:member(X, Files)].


open System
open System.IO

// Map search terms to associated set of files
type searchIndexMap = Map<string, Set<string>>

let inputSearchCriteria() =
    let readLine prompt =
        printf "%s: " prompt
        Console.ReadLine().Split()

    readLine "Files", (readLine "Find") |> Array.map (fun s -> s.ToLower())

let updateIndex indexMap keyValuePair =
    let k, v = keyValuePair

    match Map.tryFind k indexMap with
        | None     -> Map.add k (Set.singleton v) indexMap
        | Some set -> Map.add k (Set.add v set) indexMap

let buildIndex files =
    let fileData file =
        File.ReadAllText(file).Split() |> Seq.map (fun word -> word.ToLower(), file)

    files |> Seq.collect fileData
          |> Seq.fold updateIndex Map.empty

let searchFiles() =
    let files, terms = inputSearchCriteria()
    let indexes = buildIndex files

    let searchResults = terms |> Seq.map (fun term -> Map.find term indexes)
                              |> Set.intersectMany

    printf "Found in: " ; searchResults |> Set.iter (printf "%s ") ; printfn ""


USING: assocs fry io.encodings.utf8 io.files kernel sequences
sets splitting vectors ;
IN: rosettacode.inverted-index

: file-words ( file -- assoc )
    utf8 file-contents " ,;:!?.()[]{}\n\r" split harvest ;
: add-to-file-list ( files file -- files )
    over [ swap [ adjoin ] keep ] [ nip 1vector ] if ;
: add-to-index ( words index file -- )
    '[ _ [ _ add-to-file-list ] change-at ] each ;
: (index-files) ( files index -- )
   [ [ [ file-words ] keep ] dip swap add-to-index ] curry each ;
: index-files ( files -- index )
    H{ } clone [ (index-files) ] keep ;
: query ( terms index -- files )
    [ at ] curry map [ ] [ intersect ] map-reduce ;


package main

import (
    "bufio"
    "bytes"
    "errors"
    "fmt"
    "io"
    "os"
)

// inverted index representation
var index map[string][]int // ints index into indexed
var indexed []doc

type doc struct {
    file  string
    title string
}

func main() {
    // initialize representation
    index = make(map[string][]int)

    // build index
    if err := indexDir("docs"); err != nil {
        fmt.Println(err)
        return
    }

    // run user interface
    ui()
}

func indexDir(dir string) error {
    df, err := os.Open(dir)
    if err != nil {
        return err
    }
    fis, err := df.Readdir(-1)
    if err != nil {
        return err
    }
    if len(fis) == 0 {
        return errors.New(fmt.Sprintf("no files in %s", dir))
    }
    indexed := 0
    for _, fi := range fis {
        if !fi.IsDir() {
            if indexFile(dir + "/" + fi.Name()) {
                indexed++
            }
        }
    }
    return nil
}

func indexFile(fn string) bool {
    f, err := os.Open(fn)
    if err != nil {
        fmt.Println(err)
        return false // only false return
    }

    // register new file
    x := len(indexed)
    indexed = append(indexed, doc{fn, fn})
    pdoc := &indexed[x]

    // scan lines
    r := bufio.NewReader(f)
    lines := 0
    for {
        b, isPrefix, err := r.ReadLine()
        switch {
        case err == io.EOF:
            return true
        case err != nil:
            fmt.Println(err)
            return true
        case isPrefix:
            fmt.Printf("%s: unexpected long line\n", fn)
            return true
        case lines < 20 && bytes.HasPrefix(b, []byte("Title:")):
            // in a real program you would write code
            // to skip the Gutenberg document header
            // and not index it.
            pdoc.title = string(b[7:])
        }
        // index line of text in b
        // again, in a real program you would write a much
        // nicer word splitter.
    wordLoop:
        for _, bword := range bytes.Fields(b) {
            bword := bytes.Trim(bword, ".,-~?!\"'`;:()<>[]{}\\|/=_+*&^%$#@")
            if len(bword) > 0 {
                word := string(bword)
                dl := index[word]
                for _, d := range dl {
                    if d == x {
                        continue wordLoop
                    }
                }
                index[word] = append(dl, x)
            }
        }
    }
    return true
}

func ui() {
    fmt.Println(len(index), "words indexed in", len(indexed), "files")
    fmt.Println("enter single words to search for")
    fmt.Println("enter a blank line when done")
    var word string
    for {
        fmt.Print("search word: ")
        wc, _ := fmt.Scanln(&word)
        if wc == 0 {
            return
        }
        switch dl := index[word]; len(dl) {
        case 0:
            fmt.Println("no match")
        case 1:
            fmt.Println("one match:")
            fmt.Println("   ", indexed[dl[0]].file, indexed[dl[0]].title)
        default:
            fmt.Println(len(dl), "matches:")
            for _, d := range dl {
                fmt.Println("   ", indexed[d].file, indexed[d].title)
            }
        }
    }
}


import Control.Monad
import Data.Char (isAlpha, toLower)
import qualified Data.Map as M
import qualified Data.IntSet as S
import System.Environment (getArgs)

main =  do
    (files, _ : q) <- liftM (break (== "--")) getArgs
    buildII files >>= mapM_ putStrLn . queryII q

data IIndex = IIndex
    [FilePath]              -- Files in the index
    (M.Map String S.IntSet) -- Maps word to indices of the list
  deriving Show

buildII :: [FilePath] -> IO IIndex
buildII files =
    liftM (IIndex files . foldl f M.empty . zip [0..]) $
    mapM readFile files
  where f m (i, s) =
            foldl g m $ map (lowercase . filter isAlpha) $ words s
          where g m word = M.insertWith S.union word (S.singleton i) m

queryII :: [String] -> IIndex -> [FilePath]
queryII q (IIndex files m) =
    map (files !!) $ S.toList $ intersections $
    map (\word -> M.findWithDefault S.empty (lowercase word) m) q

intersections [] = S.empty
intersections xs = foldl1 S.intersection xs

lowercase = map toLower


procedure main()

  texts := table()     # substitute for read and parse files
  texts["T0.txt"] := ["it", "is", "what", "it", "is"]
  texts["T1.txt"] := ["what", "is", "it"]
  texts["T2.txt"] := ["it", "is", "a", "banana"]

  every textname := key(texts) do  # build index for each 'text'
     SII := InvertedIndex(SII,textname,texts[textname])

  TermSearchUI(SII)  # search UI

end

procedure InvertedIndex(ii,k,words)  #: accumulate a simple inverted index

/ii := table(set())    # create lookup table and null set
every w := !words do {
   if *ii[w] = 0 then ii[w] := set()  # new word, new set
   insert(ii[w],k)
   }

return ii
end

procedure TermSearchUI(ii)    #: search UI, all words must match

repeat {
   writes("Enter search terms (^z to quit) : ")
   terms := map(trim(read() | break))

   x := []
   terms ? while not pos(0) do {
      tab(many(' \t'))
      put(x,tab(upto('\ \t')|0))
      }

   show("Searching for : ",x)
   show("Found in : ",s := TermSearch(ii,x)) | show("Not found : ",x)
   }
write("End of search")
return
end

procedure TermSearch(ii,x)  #: return set of matches or fail
every s := !x do
   ( /u := ii[s] ) | (u **:= ii[s])
if *u > 0 then return u
end

procedure show(s,x) # display helper
every writes(s|!x) do writes(" ")
write()
return
end


require'files regex strings'

rxutf8 0  NB. support latin1 searches for this example, instead of utf8
files=:words=:buckets=:''
wordre=: rxcomp '[\w'']+'
parse=: ,@:rxfrom~ wordre&rxmatches

invert=: verb define
  files=: files,todo=. ~.y-.files
  >invert1 each todo
)

invert1=: verb define
  file=. files i.<y
  words=: ~.words,contents=. ~.parse tolower fread jpath y
  ind=. words i. contents
  buckets=: buckets,(1+words -&# buckets)#a:
  #buckets=: (file,~each ind{buckets) ind}buckets
)

search=: verb define
  hits=. buckets{~words i.~.parse tolower y
  files {~ >([-.-.)each/hits
)


package org.rosettacode;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class InvertedIndex {

    List<String> stopwords = Arrays.asList("a", "able", "about",
            "across", "after", "all", "almost", "also", "am", "among", "an",
            "and", "any", "are", "as", "at", "be", "because", "been", "but",
            "by", "can", "cannot", "could", "dear", "did", "do", "does",
            "either", "else", "ever", "every", "for", "from", "get", "got",
            "had", "has", "have", "he", "her", "hers", "him", "his", "how",
            "however", "i", "if", "in", "into", "is", "it", "its", "just",
            "least", "let", "like", "likely", "may", "me", "might", "most",
            "must", "my", "neither", "no", "nor", "not", "of", "off", "often",
            "on", "only", "or", "other", "our", "own", "rather", "said", "say",
            "says", "she", "should", "since", "so", "some", "than", "that",
            "the", "their", "them", "then", "there", "these", "they", "this",
            "tis", "to", "too", "twas", "us", "wants", "was", "we", "were",
            "what", "when", "where", "which", "while", "who", "whom", "why",
            "will", "with", "would", "yet", "you", "your");

    Map<String, List<Tuple>> index = new HashMap<String, List<Tuple>>();
    List<String> files = new ArrayList<String>();

    public void indexFile(File file) throws IOException {
        int fileno = files.indexOf(file.getPath());
        if (fileno == -1) {
            files.add(file.getPath());
            fileno = files.size() - 1;
        }

        int pos = 0;
        BufferedReader reader = new BufferedReader(new FileReader(file));
        for (String line = reader.readLine(); line != null; line = reader
                .readLine()) {
            for (String _word : line.split("\\W+")) {
                String word = _word.toLowerCase();
                pos++;
                if (stopwords.contains(word))
                    continue;
                List<Tuple> idx = index.get(word);
                if (idx == null) {
                    idx = new LinkedList<Tuple>();
                    index.put(word, idx);
                }
                idx.add(new Tuple(fileno, pos));
            }
        }
        System.out.println("indexed " + file.getPath() + " " + pos + " words");
    }

    public void search(List<String> words) {
        for (String _word : words) {
            Set<String> answer = new HashSet<String>();
            String word = _word.toLowerCase();
            List<Tuple> idx = index.get(word);
            if (idx != null) {
                for (Tuple t : idx) {
                    answer.add(files.get(t.fileno));
                }
            }
            System.out.print(word);
            for (String f : answer) {
                System.out.print(" " + f);
            }
            System.out.println("");
        }
    }

    public static void main(String[] args) {
        try {
            InvertedIndex idx = new InvertedIndex();
            for (int i = 1; i < args.length; i++) {
                idx.indexFile(new File(args[i]));
            }
            idx.search(Arrays.asList(args[0].split(",")));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private class Tuple {
        private int fileno;
        private int position;

        public Tuple(int fileno, int position) {
            this.fileno = fileno;
            this.position = position;
        }
    }
}


# Given an array of [ doc, array_of_distinct_words ]
# construct a lookup table: { word: array_of_docs }
def inverted_index:
  reduce .[] as $pair
    ({};
     $pair[0] as $doc
     | reduce $pair[1][] as $word
       (.; .[$word] += [$doc]));

def search(words):
  def overlap(that): . as $this
  | reduce that[] as $item ([]; if $this|index($item) then . + [$item] else . end);

  . as $dict
  | if (words|length) == 0 then []
    else reduce words[1:][] as $word
      ( $dict[words[0]]; overlap( $dict[$word] ) )
    end ;


function makedoubleindex(files)
    idx = Dict{String, Dict}()
    for file in files
        str = lowercase(read(file, String))
        words = split(str, r"\s+")
        for word in words
            if !haskey(idx, word)
                idx[word] = Dict{String, Int}()
            elseif !haskey(idx[word], file)
                (idx[word])[file] = 1
            else
                (idx[word])[file] += 1
            end
        end
    end
    idx
end

function wordsearch(dict, words::Vector{String})
    for word in words
        if haskey(dict, word)
            for (f, n) in dict[word]
                println("File $f contains $n instances of <$word>.")
            end
        else
            println("No instances of \"$word\" were found.")
        end
    end
end
wordsearch(dict, word::String) = wordsearch(dict, [word])

const filenames = ["file1.txt", "file2.txt", "file3.txt"]
const didx = makedoubleindex(filenames)
const searchterms = ["forehead", "of", "hand", "a", "foot"]
wordsearch(didx, searchterms)


// version 1.1.51

import java.io.File

val invIndex  = mutableMapOf<String, MutableList<Location>>()
val fileNames = mutableListOf<String>()
val splitter  = Regex("""\W+""")

class Location(val fileName: String, val wordNum: Int) {
    override fun toString() = "{$fileName, word number $wordNum}"
}

fun indexFile(fileName: String) {
    if (fileName in fileNames) {
        println("'$fileName' already indexed")
        return
    }
    fileNames.add(fileName)
    File(fileName).forEachLine { line ->
        for ((i, w) in line.toLowerCase().split(splitter).withIndex()) {
            var locations = invIndex[w]
            if (locations == null) {
                locations = mutableListOf<Location>()
                invIndex.put(w, locations)
            }
            locations.add(Location(fileName, i + 1))
        }
    }
    println("'$fileName' has been indexed")
}

fun findWord(word: String) {
    val w = word.toLowerCase()
    val locations = invIndex[w]
    if (locations != null) {
       println("\n'$word' found in the following locations:")
       println(locations.map { "    $it" }.joinToString("\n"))
    }
    else println("\n'$word' not found")
    println()
}

fun main(args: Array<String>) {
    // files to be indexed entered as command line arguments
    if (args.size == 0) {
        println("No file names have been supplied")
        return
    }
    for (arg in args) indexFile(arg)
    println()
    println("Enter word(s) to be searched for in these files or 'q' to quit")
    while (true) {
        print("  ? : ")
        val word = readLine()!!
        if (word.toLowerCase() == "q") return
        findWord(word)
    }
}


si = CreateSearchIndex["ExampleData/Text", Method -> "TFIDF"];
Manipulate[Grid[sr = TextSearch[si, ToString[s]];
  {FileBaseName /@ Normal[Dataset[sr][All, "ReferenceLocation"]],
    Column[#, Frame -> All] & /@ sr[All, "Snippet"]} // Transpose,
  Frame -> All], {s, "tree"}]


import os, strformat, strutils, tables

type

  WordRef = tuple[docnum, linenum: int]

  InvertedIndex = object
    docs: seq[string]
    index: Table[string, seq[WordRef]]

  SearchResult = object
    total: int
    refs: seq[tuple[docname: string; linenums: seq[int]]]

  IndexingError = object of CatchableError

const

  # Characters composing a word (letters + "'").
  WordChars = Letters + {'\''}

  # Words to ignore.
  StopWords = ["about", "above", "after", "again", "against", "all", "am", "an", "and", "any",
               "are", "aren't", "as", "at", "be", "because", "been", "before", "being", "below",
               "between", "both", "but", "by", "can't", "cannot", "could", "couldn't",  "did",
               "didn't", "do", "does", "doesn't", "doing", "don't", "down", "during", "each",
               "few", "for", "from", "further", "had", "hadn't", "has", "hasn't", "have",
               "haven't", "having", "he", "he'd", "he'll", "he's", "her", "here", "here's",
               "hers", "herself", "him", "himself", "his", "how", "how's", "i", "i'd", "i'll",
               "i'm", "i've", "if", "in", "into", "is", "isn't", "it", "it's", "its", "itself",
               "let's", "me", "more", "most", "mustn't", "my", "myself", "no", "nor", "not",
               "of", "off", "on", "once", "only", "or", "other", "ought", "our", "ours",
               "ourselves", "out", "over", "own", "same", "shan't", "she", "she'd", "she'll",
               "she's", "should", "shouldn't", "so", "some", "such", "than", "that", "that's",
               "the", "their", "theirs", "them", "themselves", "then", "there", "there's",
               "these", "they", "they'd", "they'll", "they're", "they've", "this", "those",
               "through", "to", "too", "under", "until", "up", "very", "was", "wasn't", "we",
               "we'd", "we'll", "we're", "we've", "were", "weren't", "what", "what's", "when",
               "when's", "where", "where's", "which", "while", "who", "who's", "whom", "why",
               "why's", "with", "won't", "would", "wouldn't", "you", "you'd", "you'll", "you're",
               "you've", "your", "yours", "yourself", "yourselves"]

template plural(n: int): string = (if n > 1: "s" else: "")

proc add(invIndex: var InvertedIndex; docPath: string) =
  ## Add a document to the index.

  let docName = docPath.extractFilename()
  if docName in invIndex.docs:
    raise newException(IndexingError, &"{docName}: a document with this name is alreadry indexed.")
  invIndex.docs.add docName
  let docIndex = invIndex.docs.high

  var linenum = 0
  var count = 0

  try:
    for line in docPath.lines:
      inc linenum
      var word = ""
      for ch in line:
        if ch in WordChars:
          word.add ch.toLowerAscii()
        else:
          if word.len > 1 and word notin StopWords:
            invIndex.index.mgetOrPut(word, newSeq[WordRef]()).add (docIndex, linenum)
            inc count
          word = ""

  except IOError:
    raise newException(IndexingError, &"{docName}: error while reading file.")

  if count > 0:
    echo &"{docName}: {count} word{plural(count)} indexed."
  else:
    raise newException(IndexingError, &"{docName}: nothing to index.")

func status(invIndex: InvertedIndex): string =
  ## Display information about the inverted index status.
  let words = invIndex.index.len
  let docs = invIndex.docs.len
  &"Index contains {words} word{plural(words)} from {docs} document{plural(docs)}."

proc search(invIndex: InvertedIndex; word: string): SearchResult =
  ## Search a word in the inverted index.
  ## The references are grouped by document.

  let refs = invIndex.index.getOrDefault(word)
  if refs.len == 0: return

  result.total = refs.len
  var prevdoc = ""
  var prevline = 0
  for (docnum, linenum) in refs:
    let docname = invIndex.docs[docnum]
    if docname == prevDoc:
      if linenum != prevline:
        result.refs[^1].linenums.add(linenum)
        prevline = linenum
    else:
      result.refs.add((docname, @[linenum]))
      prevdoc = docname
      prevline = linenum

#———————————————————————————————————————————————————————————————————————————————————————————————————

var invertedIndex: InvertedIndex

if paramCount() != 1 or not paramStr(1).dirExists():
  quit &"Usage: {getAppFileName().lastPathPart} directory"

# Index the documents.
for doc in walkFiles(paramStr(1) / "*.txt"):
  try:
    invertedIndex.add(doc)
  except IndexingError:
    echo getCurrentExceptionMsg()

echo invertedIndex.status()
echo ""

# Enter search interface.
var word: string
while true:
  try:
    stdout.write "Word to search? "
    word = stdin.readLine().toLowerAscii().strip()
    if word == "q":
      echo "Quitting"
      break
    if not word.allCharsInSet(WordChars):
      echo "This word contains invalid characters."
      continue
  except EOFError:
    echo ""
    echo "Quitting"
    break

  # Search word.
  let result = invertedIndex.search(word)
  if result.refs.len == 0:
    echo "No reference found for this word."
    continue

  # Display the references.
  echo &"Found {result.total} reference{plural(result.total)} to “{word}”:"
  for (docname, linenums) in result.refs:
    stdout.write &"... in “{docName}”, line{plural(linenums.len)}"
    for num in linenums: stdout.write ' ', num
    echo ""


ocamlc -c \
  -pp "camlp4o -I `ocamlc -where`/type-conv \
               -I `ocamlc -where`/sexplib \
               pa_type_conv.cmo pa_sexp_conv.cmo" \
  unix.cma bigarray.cma nums.cma -I +sexplib sexplib.cma str.cma \
  inv.ml


use Set::Object 'set';

# given an array of files, returns the index
sub createindex
{
    my @files = @_;

    my %iindex;

    foreach my $file (@files)
    {
    open(F, "<", $file) or die "Can't read file $file: $!";
    while(<F>) {
            s/\A\W+//;
        foreach my $w (map {lc} grep {length() >= 3} split /\W+/)
        {
        if ( exists($iindex{$w}) )
        {
            $iindex{$w}->insert($file);
        } else {
            $iindex{$w} = set($file);
        }
        }
    }
    close(F);
    }
    return %iindex;
}

# given an index, search for words
sub search_words_with_index
{
    my %idx = %{shift()};
    my @words = @_;
    my $res = set();

    foreach my $w (map {lc} @words)
    {
    $w =~ s/\W+//g;            # strip non-words chars
        length $w < 3 and next;
        exists $idx{$w} or return set();
        $res = $res->is_null
          ? set(@{$idx{$w}})
          : $res * $idx{$w};       # set intersection
    }
    return @$res;
}

# TESTING
# USAGE: invidx.pl the,list,of,words file1 file2 .. fileN
my @searchwords = split /,/, shift;
# first arg is a comma-separated list of words to search for
print "$_\n"
    foreach search_words_with_index({createindex(@ARGV)}, @searchwords);


-- demo\rosetta\Inverted_index.exw
without js -- (file i/o)
integer word_count = 0
sequence filenames = {}

function is_ascii(string txt)
    for i=1 to length(txt) do
        integer ch = txt[i]
        if ch='\0' or ch>=#7F then return 0 end if
    end for
    return 1
end function

procedure add_words(string name, sequence words)
    filenames = append(filenames,name)
    integer fn = length(filenames)
    for i=1 to length(words) do
        string word = words[i]
        if word[1]>='a'         -- skip numbers
        and word[1]<='z' then
            integer node = getd_index(word)
            if node=0 then  -- not present
                setd(word,{fn})
                word_count += 1
            else
                sequence val = getd_by_index(node)
                if find(fn,val)=0 then
                    setd(word,append(val,fn))
                end if
            end if
        end if
    end for
end procedure

procedure load_directory()
    sequence d = dir(".")
    for i=1 to length(d) do
        if not find('d',d[i][D_ATTRIBUTES])     -- skip directories
        and d[i][D_SIZE]<1024*1024*1024 then    -- and files > 1GB
            string name = d[i][D_NAME]
            integer fn = open(name,"rb")
            string txt = lower(get_text(fn))
            close(fn)
            if is_ascii(txt) then               -- skip any bitmaps etc
                sequence words = split_any(txt,"\0\r\n\t !\"#$%&\'()*+,-./:;<=>[email protected][\\]^`{|}~")
                add_words(name,words)
            end if
        end if
    end for
end procedure

function lookup(sequence words)
    sequence files = {}     -- indexes to filenames
    for i=1 to length(words) do
        string word = words[i]
        integer node = getd_index(word)
        if node=0 then return {} end if
        sequence val = getd_by_index(node)
        if i=1 then
            files = val
        else
            for j=length(files) to 1 by -1 do
                if not find(files[j],val) then
                    files[j..j] = {}
                end if
            end for
            if length(files)=0 then return {} end if
        end if
    end for
    for i=1 to length(files) do
        files[i] = filenames[files[i]]
    end for
    return files
end function

load_directory()
?word_count
?lookup({"load_directory"})     -- should only be this file
?lookup({"dir"})                -- currently two use this
?lookup({"lower"})              -- currently four use this
?lookup({"lower","dir"})        -- currently two use both
?lookup({"dir","lower"})        -- should be the same two
?lookup({"ban"&"anafish"})      -- should be none ({})


<?php

function buildInvertedIndex($filenames)
{
    $invertedIndex = [];

    foreach($filenames as $filename)
    {
        $data = file_get_contents($filename);

        if($data === false) die('Unable to read file: ' . $filename);

        preg_match_all('/(\w+)/', $data, $matches, PREG_SET_ORDER);

        foreach($matches as $match)
        {
            $word = strtolower($match[0]);

            if(!array_key_exists($word, $invertedIndex)) $invertedIndex[$word] = [];
            if(!in_array($filename, $invertedIndex[$word], true)) $invertedIndex[$word][] = $filename;
        }
    }

    return $invertedIndex;
}

function lookupWord($invertedIndex, $word)
{
    return array_key_exists($word, $invertedIndex) ? $invertedIndex[$word] : false;
}

$invertedIndex = buildInvertedIndex2(['file1.txt', 'file2.txt', 'file3.txt']);

foreach(['cat', 'is', 'banana', 'it'] as $word)
{
    $matches = lookupWord($invertedIndex, $word);

    if($matches !== false)
    {
        echo "Found the word \"$word\" in the following files: " . implode(', ', $matches) . "\n";
    }
    else
    {
        echo "Unable to find the word \"$word\" in the index\n";
    }
}


$ cat file1
it is what it is

$ cat file2
what is it

$ cat file3
it is a banana


function Index-File ( [string[]]$FileList )
    {
    #  Create index hashtable, as needed
    If ( -not $Script:WordIndex ) { $Script:WordIndex = @{} }

    #  For each file to be indexed...
    ForEach ( $File in $FileList )
        {
        #  Find any previously existing entries for this file
        $ExistingEntries = $Script:WordIndex.Keys | Where { $Script:WordIndex[$_] -contains $File }

        #  For any previously existing entries
        #    Delete them (prior to reindexing the file)
        ForEach ( $Key in $ExistingEntries )
            {
            $Script:WordIndex[$Key] = @( $Script:WordIndex[$Key] | Where { $_ -ne $File } )
            }

        #  Get the contents of the file, split on non-alphanumeric characters, and remove duplicates
        $Words = ( Get-Content $File ) -split '[^a-zA-Z\d]' | Sort -Unique

        #  For each word in the file...
        ForEach ( $Word in $Words )
            {
            #  If the entry for the word already exists...
            If ( $Script:WordIndex[$Word] )
                {
                #  Add the file name to the entry
                $Script:WordIndex[$Word] += $File
                }
            Else
                {
                #  Create a new entry
                $Script:WordIndex[$Word] = @( $File )
                }
            }
        }
    }

function Find-Word ( [string]$Word )
    {
    return $WordIndex[$Word]
    }


'''
This implements: http://en.wikipedia.org/wiki/Inverted_index of 28/07/10
'''

from pprint import pprint as pp
from glob import glob
try: reduce
except: from functools import reduce
try:    raw_input
except: raw_input = input

def parsetexts(fileglob='InvertedIndex/T*.txt'):
    texts, words = {}, set()
    for txtfile in glob(fileglob):
        with open(txtfile, 'r') as f:
            txt = f.read().split()
            words |= set(txt)
            texts[txtfile.split('\\')[-1]] = txt
    return texts, words

def termsearch(terms): # Searches simple inverted index
    return reduce(set.intersection,
                  (invindex[term] for term in terms),
                  set(texts.keys()))

texts, words = parsetexts()
print('\nTexts')
pp(texts)
print('\nWords')
pp(sorted(words))

invindex = {word:set(txt
                        for txt, wrds in texts.items() if word in wrds)
            for word in words}
print('\nInverted Index')
pp({k:sorted(v) for k,v in invindex.items()})

terms = ["what", "is", "it"]
print('\nTerm Search for: ' + repr(terms))
pp(sorted(termsearch(terms)))


#!/usr/bin/env racket
#lang racket
(command-line
 #:args (term . files)
 (define rindex (make-hasheq))
 (for ([file files])
   (call-with-input-file file
     (λ(in) (let loop ()
              (define w (regexp-match #px"\\w+" in))
              (when w
                (let* ([w (bytes->string/utf-8 (car w))]
                       [w (string->symbol (string-foldcase w))]
                       [r (hash-ref rindex w '())])
                  (unless (member file r) (hash-set! rindex w (cons file r)))
                  (loop)))))))
 (define res
   (for/list ([w (regexp-match* #px"\\w+" term)])
     (list->set (hash-ref rindex (string->symbol (string-foldcase w)) '()))))
 (define all (set->list (apply set-intersect res)))
 (if (null? all)
   (printf "No matching files.\n")
   (printf "Terms found at: ~a.\n" (string-join all ", "))))


sub MAIN (*@files) {
    my %norm;
    do for @files -> $file {
        %norm.push: $file X=> slurp($file).lc.words;
    }
    (my %inv).push: %norm.invert.unique;

    while prompt("Search terms: ").words -> @words {
        for @words -> $word {
            say "$word => {%inv.{$word.lc}//'(not found)'}";
        }
    }
}


/*REXX program illustrates building a simple inverted index  and  a method of word find.*/
@.=                                              /*a dictionary of words   (so far).    */
!=                                               /*a list of found words   (so far).    */
call invertI 0, 'BURMA0.TXT'                     /*read the file:  BURMA0.TXT  ···      */
call invertI 1, 'BURMA1.TXT'                     /*  "   "    "    BURMA1.TXT  ···      */
call invertI 2, 'BURMA2.TXT'                     /*  "   "    "    BURMA2.TXT  ···      */
call invertI 3, 'BURMA3.TXT'                     /*  "   "    "    BURMA3.TXT  ···      */
call invertI 4, 'BURMA4.TXT'                     /*  "   "    "    BURMA4.TXT  ···      */
call invertI 5, 'BURMA5.TXT'                     /*  "   "    "    BURMA5.TXT  ···      */
call invertI 6, 'BURMA6.TXT'                     /*  "   "    "    BURMA6.TXT  ···      */
call invertI 7, 'BURMA7.TXT'                     /*  "   "    "    BURMA7.TXT  ···      */
call invertI 8, 'BURMA8.TXT'                     /*  "   "    "    BURMA8.TXT  ···      */
call invertI 9, 'BURMA9.TXT'                     /*  "   "    "    BURMA9.TXT  ···      */
call findAword  "huz"                            /*find a word.                         */
call findAword  "60"                             /*find another word.                   */
call findAword  "don't"                          /*and find another word.               */
call findAword  "burma-shave"                    /*and find yet another word.           */
exit                                             /*stick a fork in it,  we're all done. */
/*──────────────────────────────────────────────────────────────────────────────────────*/
findAword: procedure expose @.;  arg x           /*get an uppercase version of the X arg*/
           parse arg ox                          /*get original (as-is)  value of X arg.*/
           [email protected].x;    oxo='───'ox"───"
           if _==''  then do
                          say 'word'   oxo   "not found."
                          return 0
                          end
           [email protected]=_                                  /*save _ text, pass it back to invoker.*/
           say 'word'  oxo  "found in:"
                          do  until _=='';    parse var   _   f  w  _
                          say '       file='f   "  word="w
                          end   /*until ··· */
           return [email protected]
/*──────────────────────────────────────────────────────────────────────────────────────*/
invertI:   procedure expose @. !; parse arg #,fn /*the file number and the filename.    */
           call lineout fn                       /*close the file, ··· just in case.    */
           w=0                                   /*the number of words found  (so far). */
               do  while lines(fn)\==0           /* [↓]   process the entire file.      */
               _=space( linein(fn) )             /*read a line, elide superfluous blanks*/
               if _==''  then iterate            /*if a blank record,  then ignore it.  */
               say 'file' #", record:" _         /*display the record ──► terminal.     */

                  do  until _==''                /*pick off words from record until done*/
                  parse upper var   _   ?  _     /*pick off a word  (it's in uppercase).*/
                  ?=stripper(?)                  /*strip any trailing punctuation.      */
                  if ?=''  then iterate          /*is the word now all blank (or null)? */
                  w=w+1                          /*bump the word counter (index).       */
                  @.[email protected].?  #  w                  /*append the new word to a list.       */
                  if wordpos(?,!)==0  then !=! ? /*add it to the list of words found.   */
                  end   /*until ··· */
               end      /*while ··· */
           say;     call lineout fn              /*close the file, just to be neat&safe.*/
           return w                              /*return the index of word in record.  */
/*──────────────────────────────────────────────────────────────────────────────────────*/
stripper:  procedure;  parse arg q               /*remove punctuation at the end of word*/
           @punctuation= '.,:;?¿!¡∙·';        do j=1  for length(@punctuation)
                                              q=strip(q, 'T', substr(@punctuation, j, 1) )
                                              end   /*j*/
           return q


if File.exist? "index.dat"
  @data = Marshal.load open("index.dat")
else
  @data = {}
end

# Let's give the string class the ability to tokenize itsself into lowercase
# words with no punctuation.
class String
  def index_sanitize
    self.split.collect do |token|
      token.downcase.gsub(/\W/, '')
    end
  end
end

# Just implementing a simple inverted index here.
ARGV.each do |filename|
  open filename do |file|
    file.read.index_sanitize.each do |word|
      @data[word] ||= []
      @data[word] << filename unless @data[word].include? filename
    end
  end
end

open("index.dat", "w") do |index|
  index.write Marshal.dump(@data)
end


// Part 1: Inverted index structure

use std::{
    borrow::Borrow,
    collections::{BTreeMap, BTreeSet},
};

#[derive(Debug, Default)]
pub struct InvertedIndex<T> {
    indexed: BTreeMap<String, BTreeSet<usize>>,
    sources: Vec<T>,
}

impl<T> InvertedIndex<T> {
    pub fn add<I, V>(&mut self, source: T, tokens: I)
    where
        I: IntoIterator<Item = V>,
        V: Into<String>,
    {
        let source_id = self.sources.len();
        self.sources.push(source);
        for token in tokens {
            self.indexed
                .entry(token.into())
                .or_insert_with(BTreeSet::new)
                .insert(source_id);
        }
    }

    pub fn search<'a, I, K>(&self, tokens: I) -> impl Iterator<Item = &T>
    where
        String: Borrow<K>,
        K: Ord + ?Sized + 'a,
        I: IntoIterator<Item = &'a K>,
    {
        let mut tokens = tokens.into_iter();

        tokens
            .next()
            .and_then(|token| self.indexed.get(token).cloned())
            .and_then(|first| {
                tokens.try_fold(first, |found, token| {
                    self.indexed
                        .get(token)
                        .map(|sources| {
                            found
                                .intersection(sources)
                                .cloned()
                                .collect::<BTreeSet<_>>()
                        })
                        .filter(|update| !update.is_empty())
                })
            })
            .unwrap_or_else(BTreeSet::new)
            .into_iter()
            .map(move |source| &self.sources[source])
    }

    pub fn tokens(&self) -> impl Iterator<Item = &str> {
        self.indexed.keys().map(|it| it.as_str())
    }

    pub fn sources(&self) -> &[T] {
        &self.sources
    }
}

// Part 2: File walking and processing

use std::{
    ffi::OsString,
    fmt::{Debug, Display},
    fs::{read_dir, DirEntry, File, ReadDir},
    io::{self, stdin, Read},
    path::{Path, PathBuf},
};

#[derive(Debug)]
pub struct Files {
    dirs: Vec<ReadDir>,
}

impl Files {
    pub fn walk<P: AsRef<Path>>(path: P) -> io::Result<Self> {
        Ok(Files {
            dirs: vec![read_dir(path)?],
        })
    }
}

impl Iterator for Files {
    type Item = DirEntry;

    fn next(&mut self) -> Option<Self::Item> {
        'outer: while let Some(mut current) = self.dirs.pop() {
            while let Some(entry) = current.next() {
                if let Ok(entry) = entry {
                    let path = entry.path();
                    if !path.is_dir() {
                        self.dirs.push(current);
                        return Some(entry);
                    } else if let Ok(dir) = read_dir(path) {
                        self.dirs.push(current);
                        self.dirs.push(dir);
                        continue 'outer;
                    }
                }
            }
        }

        None // No directory left
    }
}

fn tokenize<'a>(input: &'a str) -> impl Iterator<Item = String> + 'a {
    input
        .split(|c: char| !c.is_alphanumeric())
        .filter(|token| !token.is_empty())
        .map(|token| token.to_lowercase())
}

fn tokenize_file<P: AsRef<Path>>(path: P) -> io::Result<BTreeSet<String>> {
    let mut buffer = Vec::new();
    File::open(path)?.read_to_end(&mut buffer)?;
    let text = String::from_utf8_lossy(&buffer);
    Ok(tokenize(&text).collect::<BTreeSet<_>>())
}

fn tokenize_query(input: &str) -> Vec<String> {
    let result = tokenize(input).collect::<BTreeSet<_>>();
    // Make a vector sorted by length, so that longer tokens are processed first.
    // This heuristics should narrow the resulting set faster.
    let mut result = result.into_iter().collect::<Vec<_>>();
    result.sort_by_key(|item| usize::MAX - item.len());
    result
}

// Part 3: Interactive application

fn args() -> io::Result<(OsString, BTreeSet<OsString>)> {
    let mut args = std::env::args_os().skip(1); // Skip the executable's name

    let path = args
        .next()
        .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "missing path"))?;

    let extensions = args.collect::<BTreeSet<_>>();

    Ok((path, extensions))
}

fn print_hits<'a, T>(hits: impl Iterator<Item = T>)
where
    T: Display,
{
    let mut found_none = true;
    for (number, hit) in hits.enumerate() {
        println!("    [{}] {}", number + 1, hit);
        found_none = false;
    }

    if found_none {
        println!("(none)")
    }
}

fn main() -> io::Result<()> {
    let (path, extensions) = args()?;
    let mut files = InvertedIndex::<PathBuf>::default();
    let mut content = InvertedIndex::<PathBuf>::default();

    println!(
        "Indexing {:?} files in '{}'",
        extensions,
        path.to_string_lossy()
    );

    for path in Files::walk(path)?.map(|file| file.path()).filter(|path| {
        path.extension()
            .filter(|&ext| extensions.is_empty() || extensions.contains(ext))
            .is_some()
    }) {
        files.add(path.clone(), tokenize(&path.to_string_lossy()));

        match tokenize_file(&path) {
            Ok(tokens) => content.add(path, tokens),
            Err(e) => eprintln!("Skipping a file {}: {}", path.display(), e),
        }
    }

    println!(
        "Indexed {} tokens in {} files.",
        content.tokens().count(),
        content.sources.len()
    );

    // Run the query UI loop
    let mut query = String::new();

    loop {
        query.clear();
        println!("Enter search query:");
        if stdin().read_line(&mut query).is_err() || query.trim().is_empty() {
            break;
        }

        match query.trim() {
            "/exit" | "/quit" | "" => break,

            "/tokens" => {
                println!("Tokens:");
                for token in content.tokens() {
                    println!("{}", token);
                }

                println!();
            }

            "/files" => {
                println!("Sources:");
                for source in content.sources() {
                    println!("{}", source.display());
                }

                println!();
            }

            _ => {
                let query = tokenize_query(&query);
                println!();
                println!("Found hits:");
                print_hits(content.search(query.iter()).map(|it| it.display()));
                println!("Found file names:");
                print_hits(files.search(query.iter()).map(|it| it.display()));
                println!();
            }
        }
    }

    Ok(())
}


object InvertedIndex extends App {
  import java.io.File

  // indexer
  val WORD = raw"(\w+)".r
  def parse(s: String) = WORD.findAllIn(s).map(_.toString.toLowerCase)
  def invertedIndex(files: Seq[File]): Map[String,Set[File]] = {
    var i = Map[String,Set[File]]() withDefaultValue Set.empty
    files.foreach{f => scala.io.Source.fromFile(f).getLines flatMap parse foreach
      (w => i = i + (w -> (i(w) + f)))}
    i
  }

  // user interface
  args match {
    case _ if args.length < 2 => println("Usage: InvertedIndex ALLSEARCHWORDS FILENAME...")
    case Array(searchwords, filenames @ _*) =>
      val queries = parse(searchwords).toList
      val files = filenames.map(new File(_)).filter{f => if (!f.exists) println(s"Ignoring $f"); f.exists}
      (queries, files) match {
        case (q, _) if q.isEmpty => println("Missing search words")
        case (_, f) if f.isEmpty => println("Missing extant files")
        case _ => val index = invertedIndex(files)
          println(s"""Searching for ${queries map ("\""+_+"\"") mkString " and "} in ${files.size} files:""")
          queries.map(index).foldLeft(files.toSet)(_ intersect _) match {
            case m if m.isEmpty => println("No matching files")
            case m => println(m mkString "\n")
          }
      }
  }
}


package require Tcl 8.5
proc wordsInString str {
    # We define "words" to be "maximal sequences of 'word' characters".
    # The other possible definition is to use 'non-space' characters.
    regexp -all -inline {\w+} $str
}

# Adds a document to the index. The index is a map from words to a map
# from filenames to lists of word locations.
proc addDocumentToIndex {filename} {
    global index
    set f [open $filename]
    set data [read $f]
    close $f

    set i 0
    array set localidx {}
    foreach word [wordsInString $data] {
    lappend localidx($word) $i
    incr i
    }

    # Transcribe into global index
    foreach {word places} [array get localidx] {
    dict set index($word) $filename $places
    }
}

# How to use the index to find files containing a word
proc findFilesForWord {word} {
    global index
    if {[info exists index($word)]} {
    return [dict keys $index($word)]
    }
}
# How to use the index to find files containing all words from a list.
# Note that this does not use the locations within the file.
proc findFilesWithAllWords {words} {
    set files [findFilesForWord [lindex $words 0]]
    foreach w [lrange $words 1 end] {
    set wf [findFilesForWord $w]
    set newfiles {}
    foreach f $files {
        if {$f in $wf} {lappend newfiles $f}
    }
    set files $newfiles
    }
    return $files
}

# How to use the index to find a sequence of words in a file.
proc findFilesWithWordSequence {words} {
    global index
    set files {}
    foreach w $words {
    if {![info exist index($w)]} {
        return
    }
    }
    dict for {file places} $index([lindex $words 0]) {
    if {$file in $files} continue
    foreach start $places {
        set gotStart 1
        foreach w [lrange $words 1 end] {
        incr start
        set gotNext 0
        foreach {f ps} $index($w) {
            if {$f ne $file} continue
            foreach p $ps {
            if {$p == $start} {
                set gotNext 1
                break
            }
            }
            if {$gotNext} break
        }
        if {!$gotNext} {
            set gotStart 0
            break
        }
        }
        if {$gotStart} {
        lappend files $file
        break
        }
    }
    }
    return $files
}


$$ MODE TUSCRIPT

files="file1'file2'file3"
LOOP file=files
ERROR/STOP CREATE (file,seq-o,-std-)
ENDLOOP

content1="it is what it is"
content2="what is it"
content3="it is a banana"

FILE/ERASE "file1" = content1
FILE/ERASE "file2" = content2
FILE/ERASE "file3" = content3

ASK "search for": search=""
IF (search=="") STOP

BUILD R_TABLE/USER/AND search = *
DATA  {search}

LOOP/CLEAR file=files
 ACCESS q: READ/RECORDS $file s.z/u,content,count
  LOOP
  COUNT/NEXT/EXIT q (-; search;-;-)
  IF (count!=0) files=APPEND (files," ",file)
  ENDLOOP
 ENDACCESs q
ENDLOOP
PRINT "-> ",files


#!/bin/ksh

typeset -A INDEX

function index {
  typeset num=0
  for file in "[email protected]"; do
    tr -s '[:punct:]' ' ' < "$file" | while read line; do
      for token in $line; do
        INDEX[$token][$num]=$file
      done
    done
  ((++num))
  done
}

function search {
  for token in "[email protected]"; do
    for file in "${INDEX[$token][@]}"; do
      echo "$file"
    done
  done | sort | uniq -c | while read count file; do
    (( count == $# )) && echo $file
  done
}


import "/ioutil" for FileUtil, Input
import "/pattern" for Pattern
import "/str" for Str
import "os" for Process

var invIndex = {}
var fileNames = []
var splitter = Pattern.new("+1/W")

class Location {
    construct new(fileName, wordNum) {
        _fileName = fileName
        _wordNum = wordNum
    }

    toString { "%(_fileName), word number %(_wordNum)" }
}

var indexFile = Fn.new { |fileName|
    if (fileNames.contains(fileName)) {
        System.print("'%(fileName)' already indexed")
        return
    }
    fileNames.add(fileName)
    var lines = FileUtil.readLines(fileName)
    lines.each { |line|
        line = Str.lower(line)
        var i = 0
        for (w in splitter.splitAll(line)) {
            var locations = invIndex[w]
            if (!locations) {
                locations = []
                invIndex[w] = locations
            }
            locations.add(Location.new(fileName, i + 1))
            i = i + 1
        }
    }
    System.print("'%(fileName)' has been indexed")
}

var findWord = Fn.new { |word|
    var w = Str.lower(word)
    var locations = invIndex[w]
    if (locations) {
       System.print("\n'%(word)' found in the following locations:")
       System.print(locations.map { |l| "    %(l)" }.join("\n"))
    } else {
       System.print("\n'%(word)' not found")
    }
    System.print()
}

// files to be indexed entered as command line arguments
var args = Process.arguments
if (args.count == 0) {
    System.print("No file names have been supplied")
    return
}
for (arg in args) indexFile.call(arg)
System.print("\nEnter word(s) to be searched for in these files or 'q' to quit")
while (true) {
    var word = Input.text("  ? : ", 1)
    if (word == "q" || word == "Q") return
    findWord.call(word)
}


V a = Int(input(‘Enter value of a: ’))
V b = Int(input(‘Enter value of b: ’))

I a < b
   print(‘a is less than b’)
I a > b
   print(‘a is greater than b’)
I a == b
   print(‘a is equal to b’)