Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

making a calculation with the elements of an elasticsearch json object, of a contract bridge score, using Python

The data is here:

{'took': 0, 'timed_out': False, '_shards': {'total': 5, 'successful': 5, 'skipped': 0, 'failed': 0}, 'hits': {'total': 16, 'max_score': 1.0, 'hits': [{'_index': 'matchpoints', '_type': 'score', '_id': '6PKYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '1', 'nsp': '4', 'ewp': '11', 'contract': '3NT', 'by': 'N', 'tricks': '11', 'nsscore': '460', 'ewscore ': '0'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '7_KYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '2', 'nsp': '3', 'ewp': '10', 'contract': '3C', 'by': 'E', 'tricks': '10', 'nsscore': '-130', 'ewscore ': '130'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '6fKYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '1', 'nsp': '5', 'ewp': '12', 'contract': '3NT', 'by': 'S', 'tricks': '10', 'nsscore': '400', 'ewscore ': '0'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '8_KYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '2', 'nsp': '7', 'ewp': '14', 'contract': '3C', 'by': 'E', 'tricks': '10', 'nsscore': '-130', 'ewscore ': '130'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '9PKYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '2', 'nsp': '8', 'ewp': '15', 'contract': '3C', 'by': 'E', 'tricks': '11', 'nsscore': '-150', 'ewscore ': '150'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '5fKYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '1', 'nsp': '1', 'ewp': '16', 'contract': '3NT', 'by': 'N', 'tricks': '10', 'nsscore': '430', 'ewscore ': '0'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '6vKYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '1', 'nsp': '6', 'ewp': '13', 'contract': '4S', 'by': 'S', 'tricks': '11', 'nsscore': '480', 'ewscore ': '0'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '6_KYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '1', 'nsp': '7', 'ewp': '14', 'contract': '3NT', 'by': 'S', 'tricks': '8', 'nsscore': '-50', 'ewscore ': '50'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '7fKYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '2', 'nsp': '1', 'ewp': '16', 'contract': '6S', 'by': 'N', 'tricks': '12', 'nsscore': '1430', 'ewscore ': '0'}}, {'_index': 'matchpoints', '_type': 'score', '_id': '7vKYGGgBjpp4O0gQgUu5', '_score': 1.0, '_source': {'board_number': '2', 'nsp': '2', 'ewp': '9', 'contract': '3C', 'by': 'E', 'tricks': '10', 'nsscore': '-130', 'ewscore ': '130'}}]}}

The Python code, incorporating recent changes, is as follows. There is no attempt to loop through different boards as my intermediate attempt. This data is just produced by a search all query.

@application.route('/', methods=['GET', 'POST'])
def index():
    search = {"query": {"match_all": {}}}
    resp = es.search(index="matchpoints", doc_type="score", body = search)
    rows = extract_rows(resp)
    for board in rows:
        scores = score_board(board)
        report(scores)
        print(report(scores))
    return 'ok'

def extract_rows(resp):                                                                                                          
    """Extract the rows for the board from the query response."""                                                                
    # Based on the data structure provided by the OP.                                                          
    rows = [row["_source"] for row in resp["hits"]["hits"]]
    # We want to return the group the data by board number
    # so that we can score each board.                                                                       
    keyfunc = lambda row: int(row['board_number'])                                                                               
    rows.sort(key=keyfunc)                                                                                                       
    for _, group in itertools.groupby(rows, keyfunc):                                                                            
        yield list(group)

def compute_mp(scores, score):
    """Compute the match point score for a pair."""
    mp_score = sum(v for k, v in scores.items() if score > k) * 2
    # The pair's own score will always compare equal - remove it.
    mp_score += sum(v for k, v in scores.items() if score == k) - 1
    return mp_score

def score_board(tables):
    """Build the scores for each pair."""
    scores = []
    top = 2 * (len(tables) - 1)
    # Store the scores for each N-S partnership.
    ns_scores = collections.Counter(int(table["nsscore"]) for table in tables)
    # Build the output for each pair.
    for table in tables:
        output = {
            "board": table["board_number"],
            "nsp": table["nsp"],
            "ewp": table["ewp"],
        }
        ns_score = int(table["nsscore"])
        ns_mp_score = compute_mp(ns_scores, ns_score)
        output["ns_mp_score"] = ns_mp_score
        ew_mp_score = top - ns_mp_score
        output["ew_mp_score"] = ew_mp_score
        scores.append(output)
    return scores

# Replace this function with one that adds the rows to
# the new search index
def report(scores):
    """Print the scores."""
    for row in scores:
        print(row)

which produces, as before, the correct dictionary where the scoring is correct but there are duplication of results and too many lines. Also, there are two instances of "None" and I don't know where that comes from. :

{'board': '1', 'nsp': '4', 'ewp': '11', 'ns_mp_score': 6, 'ew_mp_score': 2}
{'board': '1', 'nsp': '5', 'ewp': '12', 'ns_mp_score': 2, 'ew_mp_score': 6}
{'board': '1', 'nsp': '1', 'ewp': '16', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '1', 'nsp': '6', 'ewp': '13', 'ns_mp_score': 8, 'ew_mp_score': 0}
{'board': '1', 'nsp': '7', 'ewp': '14', 'ns_mp_score': 0, 'ew_mp_score': 8}
{'board': '1', 'nsp': '4', 'ewp': '11', 'ns_mp_score': 6, 'ew_mp_score': 2}
{'board': '1', 'nsp': '5', 'ewp': '12', 'ns_mp_score': 2, 'ew_mp_score': 6}
{'board': '1', 'nsp': '1', 'ewp': '16', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '1', 'nsp': '6', 'ewp': '13', 'ns_mp_score': 8, 'ew_mp_score': 0}
{'board': '1', 'nsp': '7', 'ewp': '14', 'ns_mp_score': 0, 'ew_mp_score': 8}
None
{'board': '2', 'nsp': '3', 'ewp': '10', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '2', 'nsp': '7', 'ewp': '14', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '2', 'nsp': '8', 'ewp': '15', 'ns_mp_score': 0, 'ew_mp_score': 8}
{'board': '2', 'nsp': '1', 'ewp': '16', 'ns_mp_score': 8, 'ew_mp_score': 0}
{'board': '2', 'nsp': '2', 'ewp': '9', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '2', 'nsp': '3', 'ewp': '10', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '2', 'nsp': '7', 'ewp': '14', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '2', 'nsp': '8', 'ewp': '15', 'ns_mp_score': 0, 'ew_mp_score': 8}
{'board': '2', 'nsp': '1', 'ewp': '16', 'ns_mp_score': 8, 'ew_mp_score': 0}
{'board': '2', 'nsp': '2', 'ewp': '9', 'ns_mp_score': 4, 'ew_mp_score': 4}
None

The scoring is correct but there are, again, multiple cases of duplication of the same pairs' results.

like image 607
user1903663 Avatar asked Dec 24 '18 18:12

user1903663


1 Answers

This code will calculate the scores. The code is fairly straightforward.

Rather than iterate over the input dictionary to compute the scores for each pair, the the North-South scores are stored in a collections.Counter instance that keeps a count of the number of pairs that made each score. This makes it easier to compute the match point score for each pair - we just double the number of lower scores made and add the number of equal scores made, minus one to account for the score of the current partnership.

import collections                                                                                                               
import itertools                                                                                                                                                                                                                                    


def extract_rows(resp):                                                                                                          
    """Extract the rows for the board from the query response."""                                                                
    # Based on the data structure provided by the OP.                                                          
    rows = [row["_source"] for row in resp["hits"]["hits"]]
    # We want to return the group the data by board number
    # so that we can score each board.                                                                       
    keyfunc = lambda row: int(row['board_number'])                                                                               
    rows.sort(key=keyfunc)                                                                                                       
    for _, group in itertools.groupby(rows, keyfunc):                                                                            
        yield list(group)


def compute_mp(scores, score):
    """Compute the match point score for a pair."""
    mp_score = sum(v for k, v in scores.items() if score > k) * 2
    # The pair's own score will always compare equal - remove it.
    mp_score += sum(v for k, v in scores.items() if score == k) - 1
    return mp_score


def score_board(tables):
    """Build the scores for each pair."""
    scores = []

    # Store the scores for each N-S partnership.
    ns_scores = collections.Counter(int(table["nsscore"]) for table in tables)
    # The top score is (2 * number of tables) - 2, then reduced by one for each 
    # equal top score.
    top = 2 * (len(tables) - 1) - (ns_scores[max(ns_scores)] - 1)
    # Build the output for each pair.
    for table in tables:
        output = {
            "board": table["board_number"],
            "nsp": table["nsp"],
            "ewp": table["ewp"],
        }
        ns_score = int(table["nsscore"])
        ns_mp_score = compute_mp(ns_scores, ns_score)
        output["ns_mp_score"] = ns_mp_score
        ew_mp_score = top - ns_mp_score
        output["ew_mp_score"] = ew_mp_score
        scores.append(output)
    return scores

# Replace this function with one that adds the rows to
# the new search index
def report(scores):
    """Print the scores."""
    for row in scores:
        print(row)

Running the code:

rows = extract_rows(resp)
scores = [score for rows in extract_rows(resp) for score in score_board(rows)]
report(scores)

Produces this output:

{'board': '1', 'nsp': '4', 'ewp': '11', 'ns_mp_score': 6, 'ew_mp_score': 2}
{'board': '1', 'nsp': '5', 'ewp': '12', 'ns_mp_score': 2, 'ew_mp_score': 6}
{'board': '1', 'nsp': '1', 'ewp': '16', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '1', 'nsp': '6', 'ewp': '13', 'ns_mp_score': 8, 'ew_mp_score': 0}
{'board': '1', 'nsp': '7', 'ewp': '14', 'ns_mp_score': 0, 'ew_mp_score': 8}
{'board': '2', 'nsp': '3', 'ewp': '10', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '2', 'nsp': '7', 'ewp': '14', 'ns_mp_score': 4, 'ew_mp_score': 4}
{'board': '2', 'nsp': '8', 'ewp': '15', 'ns_mp_score': 0, 'ew_mp_score': 8}
{'board': '2', 'nsp': '1', 'ewp': '16', 'ns_mp_score': 8, 'ew_mp_score': 0}
{'board': '2', 'nsp': '2', 'ewp': '9', 'ns_mp_score': 4, 'ew_mp_score': 4}
like image 169
snakecharmerb Avatar answered Oct 24 '22 01:10

snakecharmerb