13
\$\begingroup\$

Write an indefinitely-running program that reports how many instances of itself are currently running. Each instance of the program should also report the order in which it was opened out of all other currently-running instances.

Example

The user launches the program for the first time - we'll call this Instance 1. Instance 1 displays 1/1, because it is the first instance to be launched out of a total of 1 currently-running instances.

While Instance 1 is running, the user launches the program a second time to become Instance 2. Instance 1 now displays 1/2, being the first instance out of a total of 2 currently-running instances. Instance 2 displays 2/2, because it is the second instance out of a total of 2 currently-running instances.

Let's say the user continues to spawn more instances until there are 5 of them. In order of launch, their outputs are: 1/5 2/5 3/5 4/5 5/5.

Now, let's say the user decides to terminate Instance 3. Instance 4 then becomes the new Instance 3 and Instance 5 the new Instance 4, because they are respectively the third and fourth instances to have been launched out of what is now a total of 4 instances. So each instance's change in output would be as follows:

  • 1/51/4
  • 2/52/4
  • 3/5 → (Terminated)
  • 4/53/4
  • 5/54/4

Rules

  • You may output the two numbers (instance number, total instances) in any reasonable format.
  • Whenever an instance is launched or terminated, all other instances must update their respective outputs within 100 milliseconds.
  • If you choose to update output by printing to a new line (or other "appending" output format; as opposed to replacement), you must print only when the number of instances changes, and not at any other time.
  • This is code golf. Shortest program in bytes wins.
  • In your answer, you are encouraged to specify what the user must do to open more than one instance, and/or record a screencast to demonstrate.
\$\endgroup\$
  • \$\begingroup\$ Anyone have suggestions for tags to include? \$\endgroup\$ – darrylyeo Dec 10 '17 at 16:10
  • \$\begingroup\$ Such a program would be OS-specific. \$\endgroup\$ – user202729 Dec 10 '17 at 16:20
  • \$\begingroup\$ Is "Whenever an instance is launched or terminated, all other instances must update their respective outputs within 100 milliseconds" even within our control given that we must rely on the OS to communicate (and the fact that we could spawn many, many processes can't help)? \$\endgroup\$ – Jonathan Allan Dec 10 '17 at 19:16
  • \$\begingroup\$ @Ouros process interoperation cannot be independent of the OS \$\endgroup\$ – edc65 Dec 11 '17 at 7:39
3
\$\begingroup\$

APL (Dyalog Unicode), 39 bytesSBCS

Anonymous prefix function. Call by spawning on the dummy argument (empty numeric vector), i.e. f&⍬. Query currently running threads with ⎕TNUMS and kill one or more threads with ⎕TKILL n. Threads output changes in [own number, total number] as soon as they get processor time, i.e. pretty much instantly.

{⍵≡n←n[⍋n←⎕TNUMS~0]:∇n⋄∇n⊣⎕←n⍳⎕TID,⊢/n}

Try it online!

{} anonymous lambda where is the argument (initially , the empty numeric vector)

n[] index n (to be defined) with:

  ⎕TNUMS~0 all Thread Numbers except number 0 (the REPL)

   n← store as n

    permutation which would sort ascending

  now we have the active threads in order

  ⍵≡ if the argument is identical to that…

  : then:

   ∇⍵ tail recurse on the argument

   else:

   ⊢/n the rightmost thread number

   ⎕TID, this Thread's ID (thread number) prepended to that

   n⍳ find the ɩndices of those two

   ⎕← print that to STDOUT

   n⊣ discard that in favour of n

    recurse on that

\$\endgroup\$
2
\$\begingroup\$

Python 3, 694 691 bytes

main.py

from requests import post as u
from _thread import*
import os
os.system("start cmd /C python s")
def l():
 def p(q):
  while 1:print(u(*q).text,end="\r")
 q=['http://localhost']
 q+=[u(q[0],'*').text]
 start_new_thread(p,(q,))
 input()
 u(q[0],'-'+q[1])
while 1:
 try:l();break
 except:0

s (short for server.py)

from bottle import*
from requests import post as q
try:
 q("http://localhost")
except:
 ids=["0"]
 @post('/')
 def _():
  content = request.body.read().decode('utf-8')
  if len(content)==0:return""
  if content[0]=="*":ids.append(str(int(ids[-1])+1));return str(ids[-1])
  elif content[0]=="-":del ids[ids.index(content[1:])]
  else:return str(ids.index(content)) + "/" + str(len(ids)-1)
 run(port="80")

Why is it so long?

Unfortunately, this functionality doesn't seem to be built into Python. I was tempted to use multiprocessing, but that didn't quite seem to be the right fit for what we're doing (letting a user open a program from anywhere).

So, I took the advice of a StackOverflow post I saw (I misplaced the link) and I implemented it using bottle. (I am open to new suggestions).

I used the Bottle library to run my own mini http server so all the different instances can communicate with each other. I suppose I could have used a socket, although I'm not convinced that would have reduced the byte count.

I have two seperate files, s and main.py. s is short of server and because it appears in the code, I figured I should make the name as short as possible.

Communication Web Server's API

The web server only accepts POST requests and only responds to input inside the POST's body.

All requests go through / (or localhost/).

Valid input:

  • * in the post body will request for the server to return a new id to assign the client.
  • -<id> in the post body will remove the id from the active list of id's, decreasing all relevant id's and the total count.
  • An empty request in the post body will simply return an empty string. This is what is used for testing to see if the server is online.

Closing the program

I implemented multi-threading so closing the program is as simple as pressing enter.

Opening the program

If you do not have Python setup correctly inside your environmental variables simply create a .bat file and put it in the same folder as main.py and s with the following code (if you installed Python for all users, it may be at a different location):

set PATH=%userprofile%\AppData\Local\Programs\Python\Python36
python main.py

Credits

From 694 to 691 bytes Adám.

\$\endgroup\$
  • \$\begingroup\$ Can't you remove :8080/? \$\endgroup\$ – Adám Dec 11 '17 at 8:26
  • \$\begingroup\$ If I were to assign the port to port 80, then yes; otherwise, no. The default port for webbrowsers (and requests) is port 80, but I can remove the /. \$\endgroup\$ – Neil Dec 11 '17 at 8:29
  • \$\begingroup\$ @Adám I did update it with the port change, saved 1 byte that way. \$\endgroup\$ – Neil Dec 11 '17 at 8:36
1
\$\begingroup\$

sh + linux/unix tools, 128 bytes

if sleep supports floating point numbers

trap '(flock 9;grep -vw $$ p>t;mv t p)9>l' exit;(flock 9;echo $$>>p)9>l;f(){ echo $(sed -n /^$$\$/= p)/$(wc -l<p);sleep .1;f;};f

otherwise, 159 bytes

trap '(flock 9;grep -vw $$ p>t;mv t p)9>l' exit;(flock 9;echo $$>>p)9>l;perl -MTime::HiRes=usleep -nE/^$$'$/&&say("$./",$.+(@a=<>)),usleep 1e5,$.=-(@ARGV=p)' p

or sleep can be replaced with : (no-op), but it will make active waiting.

\$\endgroup\$
  • \$\begingroup\$ This is really close - "You must print only when the number of instances changes, and not at any other time." \$\endgroup\$ – darrylyeo Dec 11 '17 at 16:07
  • \$\begingroup\$ @darrylyeo just fix, but was looking for shorter solution but didn't have the time, also to sleep 100ms, i have a solution but longer \$\endgroup\$ – Nahuel Fouilleul Dec 11 '17 at 19:13
0
\$\begingroup\$

Java 8, (199+301=) 500 bytes

M.jar: (the main program)

import javafx.collections.*;class M{static ObservableList o=FXCollections.observableArrayList();static int j,F;int i,f;{F=0;ListChangeListener e=(ListChangeListener.Change c)->{if(f<1)System.out.println((F>0&i>F?--i:i)+"/"+j);};o.addListener(e);o.add(i=++j);}public void f(){F=f=i;j--;o.remove(--i);}}

S.jar: (the server to control the program-flow)

import java.util.*;interface S{static void main(String[]a){List<M>l=new Stack();for(Scanner s=new Scanner(System.in);;){Float n=s.nextFloat();if(n%1==0)l.add(new M());else{int t=(int)(n*10-1);l.get(t).f();l.remove(t);}}}}

Explanation of the code:

import javafx.collections.*;
                  // Required import for ObservableList, FXCollections, and ListChangeListener
class M{          // Program-class
  static ObservableList o=FXCollections.observableArrayList(); 
                  //  Static list to keep record of all instances
  static int j,   //  Static integer (total number of instances)
             F;   //  Static flag (remove occurred?)
  int i,          //  Non-static integer (id of this instance)
      f;          //  Non-static flag (has been removed)
  {               //  Non-static initializer-block (shorter than constructor)
    F=0;          //   Reset the static flag remove_occurred, because we add a new instance
    o.addListener((ListChangeListener.Change c)->{
                  //   Add a change listener for the ObservableList
                  //   This will monitor any additions or removes on the List
       if(f<1)    //    If this instance is not removed yet:
         System.out.println(
                  //     Print:
           (F>0&i>F?
                  //      If a removed occurred and this id is larger than the removed instance
             --i  //       Decrease its id by 1 before printing it
            :     //      Else:
             i)   //       Just print its id
           +"/"+j);
                  //      Plus the total number of instances left
    });
    o.add(        //   Add anything to the Observable list to trigger the listener
     i=++j);      //    Increase the total amount of instances, and set the id of this instance to the last one
  }               //  End of non-static initializer-block
  public void f(){//  Finalize-method
    F=f=i;        //   Set both flags to the current id
    j--;          //   Decrease the total amount of instances
    o.remove(--i);//   Remove the current instance from the list to trigger the listener
  }               //  End of Finalize-method
}                 // End of Program-class

import java.util.*;
                  // Required import for List, Stack and Scanner
interface S{      // Server-class
  static void main(String[]a){
                  //  Mandatory main-method
    List<M>l=new Stack();
                  //   List of programs
    for(Scanner s=new Scanner(System.in);
                  //   Create a STDIN-listener for user input
        ;){       //   Loop indefinitely
      int t=s.nextInt();
                  //    Get the next integer inputted
      if(t<1)     //    If it's 0:
        l.add(new M());
                  //     Startup a new program, and add its instance to the list
      else{       //    Else:
        l.get(t).f();
                  //     Close the program with this integer as id
        l.remove(t);}
                  //     And remove it from the list of programs
    }             //   End of loop
  }               //  End of main-method
}                 // End of Server-class

General explanation:

All programs will keep a record of their own id; the total number of instances left; whether a remove occurred; and which programs have closed.

The server is just a wrapper-class to start and stop programs. When a user inputs 0, it will startup a new program. When the used inputs a positive integer (i.e. 2), it will close the program with that id. (Note: S.jar has M.jar as library to access it.)

Gif to see it in action:

enter image description here

Thoughts to golf it further:

I just noticed while writing the explanation that I only use the ObservableList for it's add/remove-ListChangeListener, and don't use its content at all. Removing this and using another type of static Listener might be shorter.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.