{
  "id": "P092",
  "slug": "friend-network",
  "key": "P092-friend-network",
  "title": "Friend network",
  "summary": "Who is friends with whom: the groups that hang together by depth-first search, degrees of separation by breadth-first search, whether everyone fits in two teams with no friends together (or an odd ring that prevents it), and friend suggestions.",
  "entry": "main.eml",
  "ui": "terminal",
  "readme": "# P092 - Friend network\n\nWho is friends with whom. The program finds the groups that hang together,\nthe degrees of separation between two people, whether everyone fits in two\nteams with no two friends together, and friend suggestions. It starts with\nten people: a group of six, a triangle of three, and Jo on their own.\n\n- `main.eml` - the menu, reading names, and the reports\n- `network.eml` - adding and removing friendships, depth-first groups,\n  breadth-first paths, two-colouring, odd rings, and suggestions\n\nHow each part works:\n\n- Friendships go both ways; each person keeps a sorted list of friends.\n- Groups, by depth-first search as in the corpus case\n  `graph-dfs-recursive`: mark a person, then go into each friend not yet\n  marked, recursively. Starting again from each unmarked person gives the\n  groups - everyone reachable through friends, and nobody else.\n- Degrees of separation, by breadth-first search: everyone one step away,\n  then two, and so on. The first time the other person is reached, the\n  chain found is as short as any.\n- Two teams: colour people as the search reaches them, each in the other\n  team from the friend it came by. If two friends end up in the same team,\n  the paths from them back up the search meet, and with their own\n  friendship they make a ring of odd length. Going round an odd ring, the\n  teams would have to swap an odd number of times, which cannot end where\n  it began. With no odd ring, the colouring is the two teams.\n- Suggestions: people who are not your friends, ranked by how many friends\n  you share.\n\nWhat is checked: menu choices 1 to 8; names of letters, up to 10, of\npeople in the network (adding a friendship may bring in a new person, up\nto 15 in all); nobody befriending themselves; removing only a friendship\nthat exists. An empty answer cancels.\n\nSessions: `sessions/basic.in` gives:\n- the network and its three groups;\n- Ana to Fay (4 steps), Ana to herself, and Ana to Gus (not connected);\n- two teams refused because of the ring Ivo - Gus - Hana;\n- suggestions for Ana (Dev, 2 in common) and for Jo (none);\n- Ivo and Gus no longer friends, after which two teams work; Fay and Gus\n  joining the two groups; and Kim joining as Jo's friend.\n\n`sessions/bad-input.in` gives:\n- menu choices 0 and x;\n- Zed, who is not in the network;\n- Ana with herself, Ana and Ben again, the name \"Ana1\", and removing a\n  friendship that does not exist, then cancels.\n\nBuilt on the verified corpus case `graph-dfs-recursive` (recursive\ndepth-first search, marking as it goes).\n",
  "modules": [
    {
      "name": "main.eml",
      "eml": "# P092 friend network: who is friends with whom, the groups that hang\n# together, degrees of separation, whether everyone fits in two teams with\n# no friends together, and friend suggestions.\nimport network\n\ndef plain(s):\n    0 => i\n    len(s) => j\n    while i < j and s[i] == \" \":\n        i + 1 => i\n    while j > i and s[j - 1] == \" \":\n        j - 1 => j\n    return s[i:j]\n\ndef is_name(s):\n    if len(s) == 0 or len(s) > 10:\n        return False\n    for c in s:\n        if not ((c >= \"a\" and c <= \"z\") or (c >= \"A\" and c <= \"Z\")):\n            return False\n    return True\n\ndef names_text(names, xs):\n    \"\" => s\n    for i in [0:len(xs) - 1]:\n        if i > 0 and i == len(xs) - 1:\n            s + \" and \" => s\n        elif i > 0:\n            s + \", \" => s\n        s + names[xs[i]] => s\n    return s\n\ndef ask_person(net, prompt):\n    while True:\n        plain(input(prompt)) => s\n        if s == \"\":\n            return -1\n        network.index_of(net[0], s) => i\n        if i >= 0:\n            return i\n        (\"Nobody called \" + s + \" is in the network.\") ^0\n\ndef show(net):\n    net[0] => names\n    net[1] => fr\n    for i in [0:len(names) - 1]:\n        if len(fr[i]) == 0:\n            (\"  \" + names[i] + \": no friends here\") ^0\n        else:\n            (\"  \" + names[i] + \": \" + names_text(names, fr[i])) ^0\n\ndef show_groups(net):\n    network.groups(net) => gs\n    for g in gs:\n        (\"  \" + names_text(net[0], g)) ^0\n    (str(len(gs)) + \" groups: each holds everyone its members can reach through friends, and nobody else.\") ^0\n\ndef separation(net):\n    ask_person(net, \"from> \") => a\n    if a == -1:\n        \"Cancelled.\" ^0\n        return\n    ask_person(net, \"to> \") => b\n    if b == -1:\n        \"Cancelled.\" ^0\n        return\n    network.path(net, a, b) => p\n    if len(p) == 0:\n        (net[0][a] + \" and \" + net[0][b] + \" are not connected, however far you go.\") ^0\n        return\n    \"\" => chain\n    for i in p:\n        if chain != \"\":\n            chain + \" - \" => chain\n        chain + net[0][i] => chain\n    (\"  \" + chain) ^0\n    if len(p) == 1:\n        \"That is the same person.\" ^0\n    else:\n        \"steps\" => w\n        if len(p) == 2:\n            \"step\" => w\n        (str(len(p) - 1) + \" \" + w + \" apart: no shorter chain of friends exists.\") ^0\n\ndef teams(net):\n    network.two_teams(net) => r\n    if r[0]:\n        [] => one\n        [] => two\n        for i in [0:len(r[1]) - 1]:\n            if r[1][i] == 0:\n                one + [i] => one\n            else:\n                two + [i] => two\n        (\"  team 1: \" + names_text(net[0], one)) ^0\n        (\"  team 2: \" + names_text(net[0], two)) ^0\n        \"No two friends are in the same team: the network is two-sided.\" ^0\n    else:\n        \"\" => chain\n        for i in r[1]:\n            if chain != \"\":\n                chain + \" - \" => chain\n            chain + net[0][i] => chain\n        (\"No: \" + chain + \" - \" + net[0][r[1][0]] + \" is a ring of \" + str(len(r[1])) + \", an odd number, and going round it the teams would have to swap an odd number of times.\") ^0\n\ndef suggest(net):\n    ask_person(net, \"person> \") => a\n    if a == -1:\n        \"Cancelled.\" ^0\n        return\n    network.suggestions(net, a) => s\n    if len(s) == 0:\n        (\"No suggestions for \" + net[0][a] + \": nobody is a friend of a friend who is not a friend already.\") ^0\n        return\n    for x in s:\n        \"friends\" => w\n        if x[1] == 1:\n            \"friend\" => w\n        (\"  \" + net[0][x[0]] + \": \" + str(x[1]) + \" \" + w + \" in common\") ^0\n\ndef change(net, adding):\n    plain(input(\"first person> \")) => a\n    if a == \"\":\n        \"Cancelled.\" ^0\n        return net\n    plain(input(\"second person> \")) => b\n    if b == \"\":\n        \"Cancelled.\" ^0\n        return net\n    if not is_name(a) or not is_name(b):\n        \"Names are letters, up to 10.\" ^0\n        return net\n    if a == b:\n        \"Someone cannot be their own friend here.\" ^0\n        return net\n    network.index_of(net[0], a) => i\n    network.index_of(net[0], b) => j\n    if not adding:\n        if i == -1 or j == -1 or not network.has(net[1][i], j):\n            (a + \" and \" + b + \" are not friends.\") ^0\n            return net\n        (a + \" and \" + b + \" are no longer friends.\") ^0\n        return network.unfriend(net, i, j)\n    net[0] => names\n    net[1] => fr\n    for nm in [a, b]:\n        if network.index_of(names, nm) == -1:\n            if len(names) == 15:\n                \"The network holds 15 people at most.\" ^0\n                return net\n            names + [nm] => names\n            fr + [[]] => fr\n            (nm + \" joins the network.\") ^0\n    [names, fr] => net\n    network.index_of(names, a) => i\n    network.index_of(names, b) => j\n    if network.has(fr[i], j):\n        (a + \" and \" + b + \" are friends already.\") ^0\n        return net\n    (a + \" and \" + b + \" are now friends.\") ^0\n    return network.befriend(net, i, j)\n\n[\"Ana\", \"Ben\", \"Chloe\", \"Dev\", \"Eli\", \"Fay\", \"Gus\", \"Hana\", \"Ivo\", \"Jo\"] => names\n[[], [], [], [], [], [], [], [], [], []] => fr\n[names, fr] => net\nfor pair in [[0, 1], [0, 2], [1, 3], [2, 3], [3, 4], [4, 5], [6, 7], [7, 8], [8, 6]]:\n    network.befriend(net, pair[0], pair[1]) => net\n\n\"== Friend network ==\" ^0\n\"Who is friends with whom: groups, degrees of separation, two teams, suggestions.\" ^0\nTrue => running\nwhile running:\n    \"\" ^0\n    \"1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\" ^0\n    plain(input(\"choice> \")) => choice\n    if choice == \"1\":\n        show(net)\n    elif choice == \"2\":\n        show_groups(net)\n    elif choice == \"3\":\n        separation(net)\n    elif choice == \"4\":\n        teams(net)\n    elif choice == \"5\":\n        suggest(net)\n    elif choice == \"6\":\n        change(net, True) => net\n    elif choice == \"7\":\n        change(net, False) => net\n    elif choice == \"8\":\n        False => running\n    else:\n        \"Pick a number from 1 to 8.\" ^0\n\"Bye.\" ^0\n",
      "python": "import network\n\ndef plain(s):\n    i = 0\n    j = len(s)\n    while i < j and s[i] == \" \":\n        i = i + 1\n    while j > i and s[j - 1] == \" \":\n        j = j - 1\n    return s[i:j]\n\ndef is_name(s):\n    if len(s) == 0 or len(s) > 10:\n        return False\n    for c in s:\n        if not (c >= \"a\" and c <= \"z\" or c >= \"A\" and c <= \"Z\"):\n            return False\n    return True\n\ndef names_text(names, xs):\n    s = \"\"\n    for i in range(0, len(xs)):\n        if i > 0 and i == len(xs) - 1:\n            s = s + \" and \"\n        elif i > 0:\n            s = s + \", \"\n        s = s + names[xs[i]]\n    return s\n\ndef ask_person(net, prompt):\n    while True:\n        s = plain(input(prompt))\n        if s == \"\":\n            return -1\n        i = network.index_of(net[0], s)\n        if i >= 0:\n            return i\n        print(\"Nobody called \" + s + \" is in the network.\")\n\ndef show(net):\n    names = net[0]\n    fr = net[1]\n    for i in range(0, len(names)):\n        if len(fr[i]) == 0:\n            print(\"  \" + names[i] + \": no friends here\")\n        else:\n            print(\"  \" + names[i] + \": \" + names_text(names, fr[i]))\n\ndef show_groups(net):\n    gs = network.groups(net)\n    for g in gs:\n        print(\"  \" + names_text(net[0], g))\n    print(str(len(gs)) + \" groups: each holds everyone its members can reach through friends, and nobody else.\")\n\ndef separation(net):\n    a = ask_person(net, \"from> \")\n    if a == -1:\n        print(\"Cancelled.\")\n        return\n    b = ask_person(net, \"to> \")\n    if b == -1:\n        print(\"Cancelled.\")\n        return\n    p = network.path(net, a, b)\n    if len(p) == 0:\n        print(net[0][a] + \" and \" + net[0][b] + \" are not connected, however far you go.\")\n        return\n    chain = \"\"\n    for i in p:\n        if chain != \"\":\n            chain = chain + \" - \"\n        chain = chain + net[0][i]\n    print(\"  \" + chain)\n    if len(p) == 1:\n        print(\"That is the same person.\")\n    else:\n        w = \"steps\"\n        if len(p) == 2:\n            w = \"step\"\n        print(str(len(p) - 1) + \" \" + w + \" apart: no shorter chain of friends exists.\")\n\ndef teams(net):\n    r = network.two_teams(net)\n    if r[0]:\n        one = []\n        two = []\n        for i in range(0, len(r[1])):\n            if r[1][i] == 0:\n                one = one + [i]\n            else:\n                two = two + [i]\n        print(\"  team 1: \" + names_text(net[0], one))\n        print(\"  team 2: \" + names_text(net[0], two))\n        print(\"No two friends are in the same team: the network is two-sided.\")\n    else:\n        chain = \"\"\n        for i in r[1]:\n            if chain != \"\":\n                chain = chain + \" - \"\n            chain = chain + net[0][i]\n        print(\"No: \" + chain + \" - \" + net[0][r[1][0]] + \" is a ring of \" + str(len(r[1])) + \", an odd number, and going round it the teams would have to swap an odd number of times.\")\n\ndef suggest(net):\n    a = ask_person(net, \"person> \")\n    if a == -1:\n        print(\"Cancelled.\")\n        return\n    s = network.suggestions(net, a)\n    if len(s) == 0:\n        print(\"No suggestions for \" + net[0][a] + \": nobody is a friend of a friend who is not a friend already.\")\n        return\n    for x in s:\n        w = \"friends\"\n        if x[1] == 1:\n            w = \"friend\"\n        print(\"  \" + net[0][x[0]] + \": \" + str(x[1]) + \" \" + w + \" in common\")\n\ndef change(net, adding):\n    a = plain(input(\"first person> \"))\n    if a == \"\":\n        print(\"Cancelled.\")\n        return net\n    b = plain(input(\"second person> \"))\n    if b == \"\":\n        print(\"Cancelled.\")\n        return net\n    if not is_name(a) or not is_name(b):\n        print(\"Names are letters, up to 10.\")\n        return net\n    if a == b:\n        print(\"Someone cannot be their own friend here.\")\n        return net\n    i = network.index_of(net[0], a)\n    j = network.index_of(net[0], b)\n    if not adding:\n        if i == -1 or j == -1 or not network.has(net[1][i], j):\n            print(a + \" and \" + b + \" are not friends.\")\n            return net\n        print(a + \" and \" + b + \" are no longer friends.\")\n        return network.unfriend(net, i, j)\n    names = net[0]\n    fr = net[1]\n    for nm in [a, b]:\n        if network.index_of(names, nm) == -1:\n            if len(names) == 15:\n                print(\"The network holds 15 people at most.\")\n                return net\n            names = names + [nm]\n            fr = fr + [[]]\n            print(nm + \" joins the network.\")\n    net = [names, fr]\n    i = network.index_of(names, a)\n    j = network.index_of(names, b)\n    if network.has(fr[i], j):\n        print(a + \" and \" + b + \" are friends already.\")\n        return net\n    print(a + \" and \" + b + \" are now friends.\")\n    return network.befriend(net, i, j)\n\nnames = [\"Ana\", \"Ben\", \"Chloe\", \"Dev\", \"Eli\", \"Fay\", \"Gus\", \"Hana\", \"Ivo\", \"Jo\"]\nfr = [[], [], [], [], [], [], [], [], [], []]\nnet = [names, fr]\nfor pair in [[0, 1], [0, 2], [1, 3], [2, 3], [3, 4], [4, 5], [6, 7], [7, 8], [8, 6]]:\n    net = network.befriend(net, pair[0], pair[1])\nprint(\"== Friend network ==\")\nprint(\"Who is friends with whom: groups, degrees of separation, two teams, suggestions.\")\nrunning = True\nwhile running:\n    print(\"\")\n    print(\"1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\")\n    choice = plain(input(\"choice> \"))\n    if choice == \"1\":\n        show(net)\n    elif choice == \"2\":\n        show_groups(net)\n    elif choice == \"3\":\n        separation(net)\n    elif choice == \"4\":\n        teams(net)\n    elif choice == \"5\":\n        suggest(net)\n    elif choice == \"6\":\n        net = change(net, True)\n    elif choice == \"7\":\n        net = change(net, False)\n    elif choice == \"8\":\n        running = False\n    else:\n        print(\"Pick a number from 1 to 8.\")\nprint(\"Bye.\")\n"
    },
    {
      "name": "network.eml",
      "eml": "# P092 friend network - people and who is friends with whom (both ways).\n# A network is [names, friends], friends[i] the sorted list of i's\n# friends' indices.\n\ndef index_of(names, name):\n    for i in [0:len(names) - 1]:\n        if names[i] == name:\n            return i\n    return -1\n\ndef has(xs, x):\n    for y in xs:\n        if y == x:\n            return True\n    return False\n\ndef insert_sorted(xs, x):\n    [] => out\n    False => done\n    for y in xs:\n        if not done and x < y:\n            out + [x] => out\n            True => done\n        out + [y] => out\n    if not done:\n        out + [x] => out\n    return out\n\ndef befriend(net, a, b):\n    net[1] => fr\n    if a == b or has(fr[a], b):\n        return net\n    insert_sorted(fr[a], b) => fr[a]\n    insert_sorted(fr[b], a) => fr[b]\n    return [net[0], fr]\n\ndef unfriend(net, a, b):\n    net[1] => fr\n    [] => xa\n    for y in fr[a]:\n        if y != b:\n            xa + [y] => xa\n    [] => xb\n    for y in fr[b]:\n        if y != a:\n            xb + [y] => xb\n    xa => fr[a]\n    xb => fr[b]\n    return [net[0], fr]\n\ndef visit(fr, i, group, seen):\n    # The corpus case graph-dfs-recursive: mark i, then go into each friend\n    # not yet seen. `seen` is changed in place; the group is returned.\n    1 => seen[i]\n    group + [i] => group\n    for j in fr[i]:\n        if seen[j] == 0:\n            visit(fr, j, group, seen) => group\n    return group\n\ndef groups(net):\n    # The connected groups: everyone reachable from someone, by depth-first\n    # search, starting from each person not yet reached.\n    net[1] => fr\n    [0] * len(fr) => seen\n    [] => out\n    for i in [0:len(fr) - 1]:\n        if seen[i] == 0:\n            out + [visit(fr, i, [], seen)] => out\n    return out\n\ndef path(net, a, b):\n    # A shortest chain of friends from a to b by breadth-first search, or []\n    # if they are not connected: the degrees of separation are its length\n    # minus one.\n    net[1] => fr\n    [-1] * len(fr) => came\n    [0] * len(fr) => seen\n    [a] => queue\n    1 => seen[a]\n    0 => head\n    while head < len(queue):\n        queue[head] => i\n        head + 1 => head\n        if i == b:\n            [b] => out\n            while out[0] != a:\n                [came[out[0]]] + out => out\n            return out\n        for j in fr[i]:\n            if seen[j] == 0:\n                1 => seen[j]\n                i => came[j]\n                queue + [j] => queue\n    return []\n\ndef two_teams(net):\n    # Can everyone be put in one of two teams with no friends in the same\n    # team? Colour by depth-first search, alternating; a friend already\n    # coloured the same closes an odd cycle. Returns [True, colours] or\n    # [False, the odd cycle].\n    net[1] => fr\n    [-1] * len(fr) => colour\n    [-1] * len(fr) => parent\n    for s in [0:len(fr) - 1]:\n        if colour[s] == -1:\n            0 => colour[s]\n            [s] => stack\n            while len(stack) > 0:\n                stack[len(stack) - 1] => i\n                stack[0:len(stack) - 1] => stack\n                for j in fr[i]:\n                    if colour[j] == -1:\n                        1 - colour[i] => colour[j]\n                        i => parent[j]\n                        stack + [j] => stack\n                    elif colour[j] == colour[i]:\n                        return [False, odd_cycle(parent, i, j)]\n    return [True, colour]\n\ndef odd_cycle(parent, i, j):\n    # i and j are friends with the same colour: their paths up the search\n    # tree meet, and the two paths with the edge i-j make an odd cycle.\n    [i] => up_i\n    while parent[up_i[len(up_i) - 1]] != -1:\n        up_i + [parent[up_i[len(up_i) - 1]]] => up_i\n    [j] => up_j\n    while parent[up_j[len(up_j) - 1]] != -1:\n        up_j + [parent[up_j[len(up_j) - 1]]] => up_j\n    # drop the shared part above the meeting point\n    while len(up_i) > 1 and len(up_j) > 1 and up_i[len(up_i) - 2] == up_j[len(up_j) - 2]:\n        up_i[0:len(up_i) - 1] => up_i\n        up_j[0:len(up_j) - 1] => up_j\n    [] => back\n    len(up_j) - 2 => k\n    while k >= 0:\n        back + [up_j[k]] => back\n        k - 1 => k\n    return up_i + back\n\ndef suggestions(net, a):\n    # People a is not friends with, ranked by how many friends they share\n    # with a: [[person, shared], ...], most shared first, then by name order.\n    net[1] => fr\n    [0] * len(fr) => shared\n    for f in fr[a]:\n        for g in fr[f]:\n            if g != a and not has(fr[a], g):\n                shared[g] + 1 => shared[g]\n    [] => out\n    for i in [0:len(fr) - 1]:\n        if shared[i] > 0:\n            out + [[i, shared[i]]] => out\n    for i in [1:len(out) - 1]:\n        out[i] => x\n        i - 1 => j\n        while j >= 0 and out[j][1] < x[1]:\n            out[j] => out[j + 1]\n            j - 1 => j\n        x => out[j + 1]\n    return out\n",
      "python": "def index_of(names, name):\n    for i in range(0, len(names)):\n        if names[i] == name:\n            return i\n    return -1\n\ndef has(xs, x):\n    for y in xs:\n        if y == x:\n            return True\n    return False\n\ndef insert_sorted(xs, x):\n    out = []\n    done = False\n    for y in xs:\n        if not done and x < y:\n            out = out + [x]\n            done = True\n        out = out + [y]\n    if not done:\n        out = out + [x]\n    return out\n\ndef befriend(net, a, b):\n    fr = net[1]\n    if a == b or has(fr[a], b):\n        return net\n    fr[a] = insert_sorted(fr[a], b)\n    fr[b] = insert_sorted(fr[b], a)\n    return [net[0], fr]\n\ndef unfriend(net, a, b):\n    fr = net[1]\n    xa = []\n    for y in fr[a]:\n        if y != b:\n            xa = xa + [y]\n    xb = []\n    for y in fr[b]:\n        if y != a:\n            xb = xb + [y]\n    fr[a] = xa\n    fr[b] = xb\n    return [net[0], fr]\n\ndef visit(fr, i, group, seen):\n    seen[i] = 1\n    group = group + [i]\n    for j in fr[i]:\n        if seen[j] == 0:\n            group = visit(fr, j, group, seen)\n    return group\n\ndef groups(net):\n    fr = net[1]\n    seen = [0] * len(fr)\n    out = []\n    for i in range(0, len(fr)):\n        if seen[i] == 0:\n            out = out + [visit(fr, i, [], seen)]\n    return out\n\ndef path(net, a, b):\n    fr = net[1]\n    came = [-1] * len(fr)\n    seen = [0] * len(fr)\n    queue = [a]\n    seen[a] = 1\n    head = 0\n    while head < len(queue):\n        i = queue[head]\n        head = head + 1\n        if i == b:\n            out = [b]\n            while out[0] != a:\n                out = [came[out[0]]] + out\n            return out\n        for j in fr[i]:\n            if seen[j] == 0:\n                seen[j] = 1\n                came[j] = i\n                queue = queue + [j]\n    return []\n\ndef two_teams(net):\n    fr = net[1]\n    colour = [-1] * len(fr)\n    parent = [-1] * len(fr)\n    for s in range(0, len(fr)):\n        if colour[s] == -1:\n            colour[s] = 0\n            stack = [s]\n            while len(stack) > 0:\n                i = stack[len(stack) - 1]\n                stack = stack[0:len(stack) - 1]\n                for j in fr[i]:\n                    if colour[j] == -1:\n                        colour[j] = 1 - colour[i]\n                        parent[j] = i\n                        stack = stack + [j]\n                    elif colour[j] == colour[i]:\n                        return [False, odd_cycle(parent, i, j)]\n    return [True, colour]\n\ndef odd_cycle(parent, i, j):\n    up_i = [i]\n    while parent[up_i[len(up_i) - 1]] != -1:\n        up_i = up_i + [parent[up_i[len(up_i) - 1]]]\n    up_j = [j]\n    while parent[up_j[len(up_j) - 1]] != -1:\n        up_j = up_j + [parent[up_j[len(up_j) - 1]]]\n    while len(up_i) > 1 and len(up_j) > 1 and up_i[len(up_i) - 2] == up_j[len(up_j) - 2]:\n        up_i = up_i[0:len(up_i) - 1]\n        up_j = up_j[0:len(up_j) - 1]\n    back = []\n    k = len(up_j) - 2\n    while k >= 0:\n        back = back + [up_j[k]]\n        k = k - 1\n    return up_i + back\n\ndef suggestions(net, a):\n    fr = net[1]\n    shared = [0] * len(fr)\n    for f in fr[a]:\n        for g in fr[f]:\n            if g != a and not has(fr[a], g):\n                shared[g] = shared[g] + 1\n    out = []\n    for i in range(0, len(fr)):\n        if shared[i] > 0:\n            out = out + [[i, shared[i]]]\n    for i in range(1, len(out)):\n        x = out[i]\n        j = i - 1\n        while j >= 0 and out[j][1] < x[1]:\n            out[j + 1] = out[j]\n            j = j - 1\n        out[j + 1] = x\n    return out\n"
    }
  ],
  "sessions": [
    {
      "name": "bad-input",
      "input": "0\nx\n3\nZed\n\n6\nAna\nAna\n6\nAna\nBen\n6\nAna1\nBen\n7\nAna\nFay\n7\n\n5\n\n8\n",
      "screen": "== Friend network ==\nWho is friends with whom: groups, degrees of separation, two teams, suggestions.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 0\nPick a number from 1 to 8.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> x\nPick a number from 1 to 8.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 3\nfrom> Zed\nNobody called Zed is in the network.\nfrom> \nCancelled.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 6\nfirst person> Ana\nsecond person> Ana\nSomeone cannot be their own friend here.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 6\nfirst person> Ana\nsecond person> Ben\nAna and Ben are friends already.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 6\nfirst person> Ana1\nsecond person> Ben\nNames are letters, up to 10.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 7\nfirst person> Ana\nsecond person> Fay\nAna and Fay are not friends.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 7\nfirst person> \nCancelled.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 5\nperson> \nCancelled.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 8\nBye.\n",
      "interpreter": "equal"
    },
    {
      "name": "basic",
      "input": "1\n2\n3\nAna\nFay\n3\nAna\nAna\n3\nAna\nGus\n4\n5\nAna\n5\nJo\n7\nIvo\nGus\n4\n6\nFay\nGus\n2\n6\nJo\nKim\n1\n8\n",
      "screen": "== Friend network ==\nWho is friends with whom: groups, degrees of separation, two teams, suggestions.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 1\n  Ana: Ben and Chloe\n  Ben: Ana and Dev\n  Chloe: Ana and Dev\n  Dev: Ben, Chloe and Eli\n  Eli: Dev and Fay\n  Fay: Eli\n  Gus: Hana and Ivo\n  Hana: Gus and Ivo\n  Ivo: Gus and Hana\n  Jo: no friends here\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 2\n  Ana, Ben, Dev, Chloe, Eli and Fay\n  Gus, Hana and Ivo\n  Jo\n3 groups: each holds everyone its members can reach through friends, and nobody else.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 3\nfrom> Ana\nto> Fay\n  Ana - Ben - Dev - Eli - Fay\n4 steps apart: no shorter chain of friends exists.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 3\nfrom> Ana\nto> Ana\n  Ana\nThat is the same person.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 3\nfrom> Ana\nto> Gus\nAna and Gus are not connected, however far you go.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 4\nNo: Ivo - Gus - Hana - Ivo is a ring of 3, an odd number, and going round it the teams would have to swap an odd number of times.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 5\nperson> Ana\n  Dev: 2 friends in common\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 5\nperson> Jo\nNo suggestions for Jo: nobody is a friend of a friend who is not a friend already.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 7\nfirst person> Ivo\nsecond person> Gus\nIvo and Gus are no longer friends.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 4\n  team 1: Ana, Dev, Fay, Gus, Ivo and Jo\n  team 2: Ben, Chloe, Eli and Hana\nNo two friends are in the same team: the network is two-sided.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 6\nfirst person> Fay\nsecond person> Gus\nFay and Gus are now friends.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 2\n  Ana, Ben, Dev, Chloe, Eli, Fay, Gus, Hana and Ivo\n  Jo\n2 groups: each holds everyone its members can reach through friends, and nobody else.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 6\nfirst person> Jo\nsecond person> Kim\nKim joins the network.\nJo and Kim are now friends.\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 1\n  Ana: Ben and Chloe\n  Ben: Ana and Dev\n  Chloe: Ana and Dev\n  Dev: Ben, Chloe and Eli\n  Eli: Dev and Fay\n  Fay: Eli and Gus\n  Gus: Fay and Hana\n  Hana: Gus and Ivo\n  Ivo: Hana\n  Jo: Kim\n  Kim: Jo\n\n1) network  2) groups  3) degrees of separation  4) two teams  5) suggestions  6) add a friendship  7) remove one  8) quit\nchoice> 8\nBye.\n",
      "interpreter": "equal"
    }
  ],
  "builtOn": [
    {
      "slug": "graph-dfs-recursive",
      "caseId": "086-graph-dfs-recursive",
      "title": "Graph DFS traversal (recursive)"
    }
  ],
  "updated": "2026-10-15"
}
