Carrot Popularity Statistics

In the PPCG chatroom the Nineteenth Byte, using carets ^ (or carrots) is a way of indicating that you agree with one of the previously made comments just above yours.

A caret message consists solely of N ^ characters (where N is a positive integer) and it means agreement with the Nth previous message. So a single ^ means agreement with the message immediately previous, ^^ means agreement with the message two lines up, ^^^ means agreement with the message three lines up, and so on.

Additionally, when a caret message X is in agreement (a.k.a. pointing towards) another caret message Y, then X is said to be in agreement with what Y is in agreement with. There may be multiple layers of this and, in the end, all caret messages are indicating agreement with one non-caret message.

For example, if a chat transcript looks like this: (one message per line)

I like dogs           [line 1]
I like cats           [line 2]
^                     [line 3]
^^^                   [line 4]
^^                    [line 5]
I like turtles        [line 6]
^                     [line 7]
^^^                   [line 8]
^^                    [line 9]


Then lines 1, 2, and 6 are non-caret messages and all the others are caret messages which point to non-caret messages:

• Line 3 points directly to line 2.
• Line 4 points directly to line 1.
• Line 5 points to line 3, which points to line 2.
• Line 7 points to line 6.
• Line 8 points to line 5, which points to line 3, which points to line 2.
• Line 9 points to line 7, which points to line 6.

Thus, including the users who wrote the non-caret message (and assuming people don't caret their own message) we can conclude that:

• 2 people agree with I like dogs (Lines 1 and 4.)
• 4 people agree with I like cats (Lines 2, 3, 5, and 8.)
• 3 people agree with I like turtles (Lines 6, 7, and 9.)

Challenge

Write a program or function that takes in a multiline string similar to the example above where every line represents a chat message, with older messages coming first.

Every line will have at least one character and there will be at least one line. All messages will either be caret messages consisting solely of ^'s, or be non-caret messages consisting of letters and spaces ([ a-zA-Z]+ in regex).

For every non-caret message, in any order, output the number of people that agree with it in some clear format that contains the message text, e.g.

2 - I like dogs
4 - I like cats
3 - I like turtles


or

I like cats (4)
I like dogs (2)
I like turtles (3)


or

{"I like cats" : 4, "I like turtles" : 3, "I like dogs" : 2}


You can assume that:

• People always agree with their own messages and do not caret themselves.
• No two non-caret messages are identical.
• Caret messages won't point to things before the first message.
• Lines will not contain leading or trailing spaces.

The shortest code in bytes wins.

Test Cases

bread is bread

---

animals are fuzzy
^
^
^
^^^
^^
^^^^^^

7 - animals are fuzzy

---

pie
^
^^
pi
^
^^
^^^^
^
^^^^^
^^^^^
^^^
^^^^
^^
^
^^^^^^^^^

9 - pie
6 - pi

---

a
b
c
^
^
^

1 - a
1 - b
4 - c

---

a
b
c
^
^^
^^^

1 - a
1 - b
4 - c

---

a
b
c
^^^
^^^^
^^^^^

4 - a
1 - b
1 - c

---

W
^
^^
X
^^^
^^^^
Y
^^^^^
^^^^^^
Z
^^^^^^^
^^^^^^^^

1 - Y
3 - X
1 - Z
7 - W

---

ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqretuvwxyz
^
ABCDEFGHIJKLMNOPQRSTUVWXYZ  abcdefghijklmnopqretuvwxyz

2 - ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqretuvwxyz
1 - ABCDEFGHIJKLMNOPQRSTUVWXYZ  abcdefghijklmnopqretuvwxyz

---

I like dogs
I like cats
^
^^^
^^
I like turtles
^
^^^
^^

2 - I like dogs
4 - I like cats
3 - I like turtles

CJam, 18

qN/{_'^e=$\;}%$ep


2 bytes eliminated thanks to Martin :)
Try it online

Explanation:

q         read the input
N/        split into lines
{…}%      transform each line as follows:
_       make a copy
'^e=    count '^' characters in the string
$copy the corresponding earlier line from the stack if 0, it copies the current line again \; discard the current line (from before the copied line) * after the loop, all caret lines have been replaced * with the original messages they agree with$         sort the messages
e        RLE encode
p         pretty print


Pyth, 19 18 bytes

rSu+G@+HG_/H\^.zY8


Demonstration

A similar approach to aditsu, especially the rle part.

rSu+G@+HG_/H\^.zY8
u           .zY      Reduce over the list input lines, starting with [].
G is the working value, H is the next input line.
+G                  Append to the current value
+HG              H prependeded to G
@   _/H\^         Indexed at -(H.count('^')). This is H if no carets are in H,
or the appropiate distance from the end of G otherwise.
S                     Sort
r                 8    Run length encode


JavaScript (ES6), 110 bytes

x=>(r={},l=x.split
,l.map((_,i)=>(a=n=>(m=l[n])[0]=="^"?a(n-m.length):r[m]=r[m]+1||1)(i)),JSON.stringify(r))


Explanation

x=>(
r={},                   // r = results
l=x.split
,                        // l = array of messages
l.map((_,i)=>           // check each message
(a=n=>                // n = index of the message to agree with
(m=l[n])            // m = message
[0]=="^"          // if this is a caret message
?a(n-m.length)  // agree with the message it points to
:r[m]=r[m]+1||1 // else add one to this message's agreements
)(i)
),
JSON.stringify(r)       // return the results as a string
)


Test

<textarea id="input" rows="7">I like dogs
I like cats
^
^^^
^^
I like turtles
^
^^^
^^</textarea>
<br />
<button onclick='result.textContent=(

x=>(r={},l=x.split
,l.map((_,i)=>(a=n=>(m=l[n])[0]=="^"?a(n-m.length):r[m]=r[m]+1||1)(i)),JSON.stringify(r))

)(input.value)'>Go</button>
<pre id="result"></pre>

Mathematica, 83 77 bytes

Tally@#[[Range@Length@#-#~StringCount~"^"//.x_:>x[[x]]]]&@StringSplit[#,"
"]&


Ruby 89

m={}
v={}
i=0
\$<.map{|l|(t=l.chop![/\^+/])?v[m[i]=o=m[i-t.size]]+=1:v[m[i]=l]=1;i+=1}
p v


This is a program that gets input from STDIN and prints out the result. It keeps track of the messages and their vote counts in the variable v, which is a Hash.

Online demos:

Python 2.7 - 122 114 bytes

def c(s):
l=s.split('\n');c=len(l);d=[1]*c
while c:
c-=1
if'^'in l[c]:d[c-len(l[c])]+=d[c]
else:print l[c],d[c]


Pretty much the most straightforward solution there is, and not particularly golfed.

Python 2.7 96 bytes

l=s.split();b={}
for i in l:_=l.index(i);l[_]=l[_-i.count('^')];b[l[_]]=b.get(l[_],0)+1
print b


explanation: in-place overwrite of l, each call of l[_] = ... stores the word pointed to, and a dictionary is used to tally the results by initializing or adding to the current count of b[l[_]]

• You could probably shave off some bytes with for _,i in enumerate(l):. – Mego Nov 25 '15 at 6:27